Skip to content
This repository was archived by the owner on May 9, 2024. It is now read-only.

Commit a88a723

Browse files
AndreyPavlenkoalexbaden
authored andcommitted
Fixed timestamp literals serialization.
The timestamp literals must be converted to TimestampString, otherwise, Calcite fails with ClassCastException: class java.math.BigDecimal cannot be cast to class java.lang.Long Signed-off-by: Andrey Pavlenko <[email protected]>
1 parent a01e3d2 commit a88a723

File tree

3 files changed

+236
-13
lines changed

3 files changed

+236
-13
lines changed

omniscidb/Calcite/java/calcite/src/main/java/org/apache/calcite/rel/externalize/MapDRelJson.java

Lines changed: 93 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,12 @@
1616

1717
package org.apache.calcite.rel.externalize;
1818

19-
import com.fasterxml.jackson.databind.type.TypeFactory;
2019
import com.google.common.collect.ImmutableList;
2120
import com.google.common.collect.ImmutableSet;
2221
import com.mapd.calcite.parser.MapDSqlOperatorTable;
2322

24-
import org.apache.calcite.avatica.AvaticaUtils;
2523
import org.apache.calcite.avatica.util.TimeUnitRange;
2624
import org.apache.calcite.plan.RelOptCluster;
27-
import org.apache.calcite.plan.RelTraitSet;
2825
import org.apache.calcite.rel.RelCollation;
2926
import org.apache.calcite.rel.RelCollationImpl;
3027
import org.apache.calcite.rel.RelCollations;
@@ -55,20 +52,21 @@
5552
import org.apache.calcite.sql.JoinType;
5653
import org.apache.calcite.sql.SqlAggFunction;
5754
import org.apache.calcite.sql.SqlFunction;
58-
import org.apache.calcite.sql.SqlIdentifier;
5955
import org.apache.calcite.sql.SqlKind;
6056
import org.apache.calcite.sql.SqlOperator;
61-
import org.apache.calcite.sql.SqlOperatorTable;
6257
import org.apache.calcite.sql.SqlSyntax;
6358
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
6459
import org.apache.calcite.sql.type.SqlTypeName;
6560
import org.apache.calcite.util.ImmutableBitSet;
6661
import org.apache.calcite.util.JsonBuilder;
62+
import org.apache.calcite.util.TimestampString;
6763
import org.apache.calcite.util.Util;
6864

6965
import java.lang.reflect.Constructor;
7066
import java.lang.reflect.InvocationTargetException;
7167
import java.math.BigDecimal;
68+
import java.time.LocalDateTime;
69+
import java.time.ZoneOffset;
7270
import java.util.ArrayList;
7371
import java.util.Collections;
7472
import java.util.HashMap;
@@ -78,6 +76,8 @@
7876
import java.util.Set;
7977
import java.util.stream.Collectors;
8078

79+
import static java.lang.Integer.parseUnsignedInt;
80+
8181
/**
8282
* Utilities for converting {@link org.apache.calcite.rel.RelNode} into JSON
8383
* format.
@@ -345,8 +345,16 @@ private Object toJson(RexNode node) {
345345
return map;
346346
case LITERAL:
347347
final RexLiteral literal = (RexLiteral) node;
348-
final Object value2 = literal.getValue2();
348+
final Object value2;
349349
map = jsonBuilder.map();
350+
351+
if (literal.getTypeName() == SqlTypeName.TIMESTAMP) {
352+
value2 = timestampStringToLong(literal.getValueAs(TimestampString.class),
353+
literal.getType().getPrecision());
354+
} else {
355+
value2 = literal.getValue2();
356+
}
357+
350358
if (value2 instanceof TimeUnitRange) {
351359
map.put("literal", value2.toString());
352360
} else {
@@ -529,16 +537,23 @@ RexNode toRex(RelInput relInput, Object o) {
529537
if (literal instanceof Number) {
530538
final SqlTypeName targetTypeName =
531539
Util.enumVal(SqlTypeName.class, (String) map.get("target_type"));
540+
final RelDataTypeFactory typeFactory = cluster.getTypeFactory();
541+
542+
if (targetTypeName == SqlTypeName.TIMESTAMP) {
543+
final long value = ((Number) literal).longValue();
544+
final int precision = ((Number) map.get("precision")).intValue();
545+
final TimestampString ts = longToTimestampString(value, precision);
546+
return rexBuilder.makeLiteral(ts,
547+
typeFactory.createSqlType(targetTypeName, precision), false);
548+
}
549+
532550
final long scale = ((Number) map.get("scale")).longValue();
533-
final long precision = ((Number) map.get("precision")).longValue();
534551
final long typeScale = ((Number) map.get("type_scale")).longValue();
535-
final long typePrecision = ((Number) map.get("type_precision")).longValue();
536-
RelDataTypeFactory typeFactory = cluster.getTypeFactory();
537-
538-
BigDecimal value =
552+
final BigDecimal value =
539553
BigDecimal.valueOf(((Number) literal).longValue(), (int) scale);
540554

541555
if (typeScale != 0 && typeScale != -2147483648) {
556+
final long typePrecision = ((Number) map.get("type_precision")).longValue();
542557
return rexBuilder.makeLiteral(value,
543558
typeFactory.createSqlType(
544559
SqlTypeName.DECIMAL, (int) typePrecision, (int) typeScale),
@@ -568,6 +583,73 @@ RexNode toRex(RelInput relInput, Object o) {
568583
}
569584
}
570585

586+
static TimestampString longToTimestampString(long value, int precision) {
587+
assert (precision == 0) || (precision == 3) || (precision == 6) || (precision == 9);
588+
589+
long pow = (long) Math.pow(10, precision);
590+
long seconds = value / pow;
591+
LocalDateTime dt = LocalDateTime.ofEpochSecond(seconds, 0, ZoneOffset.UTC);
592+
int year = dt.getYear();
593+
assert year < 10000;
594+
int month = dt.getMonthValue();
595+
int day = dt.getDayOfMonth();
596+
int hour = dt.getHour();
597+
int minute = dt.getMinute();
598+
int second = dt.getSecond();
599+
600+
long fraction = value - (seconds * pow);
601+
for (; (precision > 0) && ((fraction % 10) == 0); precision--, fraction /= 10) ; // Cut trailing zeros
602+
603+
char[] ts = new char[precision == 0 ? 19 : 20 + precision];
604+
ts[0] = (char) ('0' + (year / 1000));
605+
ts[1] = (char) ('0' + ((year / 100) % 10));
606+
ts[2] = (char) ('0' + ((year / 10) % 10));
607+
ts[3] = (char) ('0' + (year % 10));
608+
ts[4] = '-';
609+
ts[5] = (char) ('0' + (month / 10));
610+
ts[6] = (char) ('0' + (month % 10));
611+
ts[7] = '-';
612+
ts[8] = (char) ('0' + (day / 10));
613+
ts[9] = (char) ('0' + (day % 10));
614+
ts[10] = ' ';
615+
ts[11] = (char) ('0' + (hour / 10));
616+
ts[12] = (char) ('0' + (hour % 10));
617+
ts[13] = ':';
618+
ts[14] = (char) ('0' + (minute / 10));
619+
ts[15] = (char) ('0' + (minute % 10));
620+
ts[16] = ':';
621+
ts[17] = (char) ('0' + (second / 10));
622+
ts[18] = (char) ('0' + (second % 10));
623+
624+
if (precision > 0) {
625+
ts[19] = '.';
626+
do {
627+
ts[19 + precision] = (char) ('0' + (fraction % 10));
628+
precision--;
629+
fraction /= 10;
630+
} while (precision > 0);
631+
}
632+
633+
return new TimestampString(new String(ts));
634+
}
635+
636+
static long timestampStringToLong(TimestampString ts, int precision) {
637+
String v = ts.toString();
638+
int year = parseUnsignedInt(v, 0, 4, 10);
639+
int month = parseUnsignedInt(v, 5, 7, 10);
640+
int day = parseUnsignedInt(v, 8, 10, 10);
641+
int hour = parseUnsignedInt(v, 11, 13, 10);
642+
int minute = parseUnsignedInt(v, 14, 16, 10);
643+
int second = parseUnsignedInt(v, 17, 19, 10);
644+
long seconds = LocalDateTime.of(year, month, day, hour, minute, second)
645+
.toInstant(ZoneOffset.UTC).getEpochSecond();
646+
int pow = (int) Math.pow(10, precision);
647+
int len = v.length();
648+
if (len == 19) return seconds * pow;
649+
int fraction = parseUnsignedInt(v, 20, len, 10);
650+
return seconds * pow + fraction * (pow / ((int) Math.pow(10, len - 20)));
651+
}
652+
571653
private List<RexNode> toRexList(RelInput relInput, List operands) {
572654
final List<RexNode> list = new ArrayList<RexNode>();
573655
for (Object operand : operands) {
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
package org.apache.calcite.rel.externalize;
2+
3+
import org.apache.calcite.util.TimestampString;
4+
import org.junit.Ignore;
5+
import org.junit.Test;
6+
7+
import java.util.Random;
8+
import java.util.function.BiConsumer;
9+
import java.util.function.LongSupplier;
10+
11+
import static org.apache.calcite.rel.externalize.MapDRelJson.longToTimestampString;
12+
import static org.apache.calcite.rel.externalize.MapDRelJson.timestampStringToLong;
13+
import static org.junit.Assert.assertEquals;
14+
15+
public class MapDRelJsonTest {
16+
private static final long maxSeconds = new TimestampString("9999-12-31 23:59:59").getMillisSinceEpoch() / 1000;
17+
private static final long[] MAX_VALUES = new long[]{ // Maximum value for each precision
18+
maxSeconds,
19+
maxSeconds * 1000 + 999,
20+
maxSeconds * 1000_000 + 999999,
21+
0x7FFFFFFFFFFFFFFFL,
22+
};
23+
private static final int[] PRECISIONS = new int[]{0, 3, 6, 9};
24+
25+
@Test
26+
public void testLongToTimestampString() {
27+
benchOrTestLongToTimestampString(MapDRelJsonTest::testLongToTimestampString);
28+
}
29+
30+
@Test
31+
@Ignore
32+
public void benchLongToTimestampString() {
33+
benchOrTestLongToTimestampString(MapDRelJsonTest::benchLongToTimestampString);
34+
}
35+
36+
private static void benchOrTestLongToTimestampString(BiConsumer<Long, Integer> func) {
37+
for (int i = 0; i < PRECISIONS.length; i++) {
38+
int precision = PRECISIONS[i];
39+
func.accept(0L, precision);
40+
func.accept(1L, precision);
41+
func.accept(MAX_VALUES[i], precision);
42+
43+
for (int j = 0; j <= precision; j++) {
44+
func.accept((long) Math.pow(10, j), precision);
45+
}
46+
47+
long max = MAX_VALUES[i];
48+
Random rnd = new Random();
49+
for (int j = 0; j < 10; j++) {
50+
func.accept(rnd.nextLong() & max, precision);
51+
}
52+
}
53+
}
54+
55+
private static void testLongToTimestampString(long value, int precision) {
56+
TimestampString ts;
57+
switch (precision) {
58+
case 0:
59+
ts = TimestampString.fromMillisSinceEpoch(value * 1000);
60+
break;
61+
case 3:
62+
ts = TimestampString.fromMillisSinceEpoch(value);
63+
break;
64+
case 6:
65+
long seconds = value / 1000_000;
66+
int nanos = (int) (value - (seconds * 1000_000)) * 1000;
67+
ts = TimestampString.fromMillisSinceEpoch(seconds * 1000).withNanos(nanos);
68+
break;
69+
case 9:
70+
seconds = value / 1000_000_000;
71+
nanos = (int) (value - (seconds * 1000_000_000));
72+
ts = TimestampString.fromMillisSinceEpoch(seconds * 1000).withNanos(nanos);
73+
break;
74+
default:
75+
throw new IllegalArgumentException();
76+
}
77+
78+
TimestampString ltts = longToTimestampString(value, precision);
79+
assertEquals(ts.toString(), ltts.toString());
80+
assertEquals(value, timestampStringToLong(ltts, precision));
81+
}
82+
83+
private static void benchLongToTimestampString(long value, int precision) {
84+
System.out.print("Value=" + value + ", precision=" + precision);
85+
86+
int nRepeat = 100000;
87+
int nWarmUp = 0;
88+
LongSupplier ltts = () -> timestampStringToLong(longToTimestampString(value, precision), precision);
89+
LongSupplier ts;
90+
switch (precision) {
91+
case 0:
92+
ts = () -> TimestampString.fromMillisSinceEpoch(value * 1000).getMillisSinceEpoch() / 1000;
93+
break;
94+
case 3:
95+
ts = () -> TimestampString.fromMillisSinceEpoch(value).getMillisSinceEpoch();
96+
break;
97+
case 6:
98+
ts = () -> {
99+
long seconds = value / 1000_000;
100+
int nanos = (int) (value - (seconds * 1000_000)) * 1000;
101+
long v = TimestampString.fromMillisSinceEpoch(seconds * 1000).withNanos(nanos)
102+
.getMillisSinceEpoch() * 1000;
103+
assertEquals(value / 1000 * 1000, v);
104+
return value;
105+
};
106+
break;
107+
case 9:
108+
ts = () -> {
109+
long seconds = value / 1000_000_000;
110+
int nanos = (int) (value - (seconds * 1000_000_000));
111+
long v = TimestampString.fromMillisSinceEpoch(seconds * 1000).withNanos(nanos)
112+
.getMillisSinceEpoch() * 1000_000;
113+
assertEquals(value / 1000_000 * 1000_000, v);
114+
return value;
115+
};
116+
break;
117+
default:
118+
throw new IllegalArgumentException();
119+
}
120+
121+
for (int i = 0; i < nWarmUp; i++) {
122+
ts.getAsLong();
123+
ltts.getAsLong();
124+
}
125+
126+
long tsTime = System.currentTimeMillis();
127+
for (int i = 0; i < nRepeat; i++) {
128+
long v = ts.getAsLong();
129+
assertEquals(value, v);
130+
}
131+
tsTime = System.currentTimeMillis() - tsTime;
132+
133+
long lttsTime = System.currentTimeMillis();
134+
for (int i = 0; i < nRepeat; i++) {
135+
long v = ltts.getAsLong();
136+
assertEquals(value, v);
137+
}
138+
lttsTime = System.currentTimeMillis() - lttsTime;
139+
140+
System.out.println(", tsTime=" + tsTime + ", lttsTime=" + lttsTime);
141+
}
142+
}

omniscidb/QueryEngine/RelAlgDagBuilder.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -395,8 +395,7 @@ hdk::ir::ExprPtr parseLiteral(const rapidjson::Value& expr) {
395395
case hdk::ir::Type::kTime:
396396
case hdk::ir::Type::kTimestamp: {
397397
Datum d;
398-
d.bigintval = lit_type->isTimestamp() && precision > 0 ? json_i64(literal)
399-
: json_i64(literal) / 1000;
398+
d.bigintval = json_i64(literal);
400399
return hdk::ir::makeExpr<hdk::ir::Constant>(lit_type, false, d);
401400
}
402401
case hdk::ir::Type::kDate: {

0 commit comments

Comments
 (0)