Skip to content

Commit ad273f0

Browse files
committed
[CALCITE-7491] Literals of type TIMESTAMP WITH TIME ZONE cause crashes
Signed-off-by: Mihai Budiu <mbudiu@feldera.com>
1 parent 3505dba commit ad273f0

6 files changed

Lines changed: 59 additions & 4 deletions

File tree

babel/src/test/resources/sql/big-query.iq

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,43 @@
3030
!use scott-big-query
3131
!set outputformat mysql
3232

33+
# Test case for [CALCITE-7491] https://issues.apache.org/jira/browse/CALCITE-7491
34+
# Literals of type TIMESTAMP WITH TIME ZONE cause crashes
35+
# This will change once we fix [CALCITE-7494]
36+
# Avatica conversion to string of TIMESTAMP WITH TIME ZONE
37+
# does not include time zone
38+
select TIMESTAMP WITH TIME ZONE '2020-01-01 00:00:00 America/New_York';
39+
+---------------------+
40+
| EXPR$0 |
41+
+---------------------+
42+
| 2020-01-01 05:00:00 |
43+
+---------------------+
44+
(1 row)
45+
46+
!ok
47+
48+
# Two timestamps with time zone are equal if they represent the same UTC time
49+
SELECT TIMESTAMP WITH TIME ZONE '2020-01-01 08:10:10 America/New_York' = TIMESTAMP WITH TIME ZONE '2020-01-01 05:10:10 America/Los_Angeles';
50+
+--------+
51+
| EXPR$0 |
52+
+--------+
53+
| true |
54+
+--------+
55+
(1 row)
56+
57+
!ok
58+
59+
# Two equal timestamps in different time zones are different if they represent different UTC times
60+
SELECT TIMESTAMP WITH TIME ZONE '2020-01-01 08:10:10 America/New_York' = TIMESTAMP WITH TIME ZONE '2020-01-01 08:10:10 America/Los_Angeles';
61+
+--------+
62+
| EXPR$0 |
63+
+--------+
64+
| false |
65+
+--------+
66+
(1 row)
67+
68+
!ok
69+
3370
# Two tests for [CALCITE-7094] Using a type alias as a constructor function
3471
# causes a validator assertion failure
3572
select int64();

core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1077,6 +1077,7 @@ public static Expression translateLiteral(
10771077
() -> "value for " + literal).toString()));
10781078
case DATE:
10791079
case TIME:
1080+
case TIME_TZ:
10801081
case TIME_WITH_LOCAL_TIME_ZONE:
10811082
case INTERVAL_YEAR:
10821083
case INTERVAL_YEAR_MONTH:
@@ -1085,6 +1086,7 @@ public static Expression translateLiteral(
10851086
javaClass = int.class;
10861087
break;
10871088
case TIMESTAMP:
1089+
case TIMESTAMP_TZ:
10881090
case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
10891091
case INTERVAL_DAY:
10901092
case INTERVAL_DAY_HOUR:

core/src/main/java/org/apache/calcite/rex/RexLiteral.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@
6060
import java.nio.ByteBuffer;
6161
import java.nio.charset.Charset;
6262
import java.text.SimpleDateFormat;
63+
import java.time.Instant;
64+
import java.time.LocalDateTime;
65+
import java.time.ZoneId;
66+
import java.time.ZoneOffset;
67+
import java.time.ZonedDateTime;
6368
import java.util.Calendar;
6469
import java.util.List;
6570
import java.util.Locale;
@@ -1183,9 +1188,18 @@ public boolean isNull() {
11831188
break;
11841189
case TIMESTAMP_TZ:
11851190
if (clazz == Long.class) {
1186-
return clazz.cast(((TimestampWithTimeZoneString) value)
1191+
TimestampWithTimeZoneString tstz = (TimestampWithTimeZoneString) value;
1192+
long ms = tstz
11871193
.getLocalTimestampString()
1188-
.getMillisSinceEpoch());
1194+
.getMillisSinceEpoch();
1195+
// Interpret the timestamp part as a UTC timestamp
1196+
LocalDateTime local = Instant.ofEpochMilli(ms).atZone(ZoneOffset.UTC).toLocalDateTime();
1197+
// Adjust for the time zone
1198+
ZoneId id = tstz.getTimeZone().toZoneId();
1199+
ZonedDateTime zoned = local.atZone(id);
1200+
ZonedDateTime utc = zoned.withZoneSameInstant(ZoneOffset.UTC);
1201+
ms = utc.toInstant().toEpochMilli();
1202+
return clazz.cast(ms);
11891203
} else if (clazz == Calendar.class) {
11901204
TimestampWithTimeZoneString ts = (TimestampWithTimeZoneString) value;
11911205
return clazz.cast(ts.getLocalTimestampString().toCalendar(ts.getTimeZone()));

core/src/main/java/org/apache/calcite/sql2rel/SqlNodeToRexConverterImpl.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.apache.calcite.sql.SqlLiteral;
2727
import org.apache.calcite.sql.SqlTimeLiteral;
2828
import org.apache.calcite.sql.SqlTimestampLiteral;
29+
import org.apache.calcite.sql.SqlTimestampTzLiteral;
2930
import org.apache.calcite.sql.type.SqlTypeName;
3031
import org.apache.calcite.util.BitString;
3132
import org.apache.calcite.util.DateString;
@@ -131,7 +132,7 @@ public class SqlNodeToRexConverterImpl implements SqlNodeToRexConverter {
131132
case TIMESTAMP_TZ:
132133
return rexBuilder.makeTimestampTzLiteral(
133134
literal.getValueAs(TimestampWithTimeZoneString.class),
134-
((SqlTimestampLiteral) literal).getPrec());
135+
((SqlTimestampTzLiteral) literal).getPrec());
135136
case TIME:
136137
return rexBuilder.makeTimeLiteral(
137138
literal.getValueAs(TimeString.class),

core/src/main/java/org/apache/calcite/util/TimestampWithTimeZoneString.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ public TimestampWithTimeZoneString withTimeZone(TimeZone timeZone) {
173173
}
174174

175175
@Override public int compareTo(TimestampWithTimeZoneString o) {
176-
return v.compareTo(o.v);
176+
return this.pt.getCalendar().compareTo(o.pt.getCalendar());
177177
}
178178

179179
public TimestampWithTimeZoneString round(int precision) {

site/_docs/reference.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1682,6 +1682,7 @@ charSet:
16821682

16831683
timeZone:
16841684
WITHOUT TIME ZONE
1685+
| WITH TIME ZONE
16851686
| WITH LOCAL TIME ZONE
16861687
{% endhighlight %}
16871688

0 commit comments

Comments
 (0)