This is an automated email from the ASF dual-hosted git repository.

mihaibudiu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git


The following commit(s) were added to refs/heads/main by this push:
     new ad273f029b [CALCITE-7491] Literals of type TIMESTAMP WITH TIME ZONE 
cause crashes
ad273f029b is described below

commit ad273f029b9ae1fd110aeea5d4c2ef0fcf411ea5
Author: Mihai Budiu <[email protected]>
AuthorDate: Mon Apr 27 21:57:21 2026 -0700

    [CALCITE-7491] Literals of type TIMESTAMP WITH TIME ZONE cause crashes
    
    Signed-off-by: Mihai Budiu <[email protected]>
---
 babel/src/test/resources/sql/big-query.iq          | 37 ++++++++++++++++++++++
 .../adapter/enumerable/RexToLixTranslator.java     |  2 ++
 .../java/org/apache/calcite/rex/RexLiteral.java    | 18 +++++++++--
 .../calcite/sql2rel/SqlNodeToRexConverterImpl.java |  3 +-
 .../calcite/util/TimestampWithTimeZoneString.java  |  2 +-
 site/_docs/reference.md                            |  1 +
 6 files changed, 59 insertions(+), 4 deletions(-)

diff --git a/babel/src/test/resources/sql/big-query.iq 
b/babel/src/test/resources/sql/big-query.iq
index 007e68cb87..a988eac1d7 100755
--- a/babel/src/test/resources/sql/big-query.iq
+++ b/babel/src/test/resources/sql/big-query.iq
@@ -30,6 +30,43 @@
 !use scott-big-query
 !set outputformat mysql
 
+# Test case for [CALCITE-7491] 
https://issues.apache.org/jira/browse/CALCITE-7491
+# Literals of type TIMESTAMP WITH TIME ZONE cause crashes
+# This will change once we fix [CALCITE-7494]
+# Avatica conversion to string of TIMESTAMP WITH TIME ZONE
+# does not include time zone
+select TIMESTAMP WITH TIME ZONE '2020-01-01 00:00:00 America/New_York';
++---------------------+
+| EXPR$0              |
++---------------------+
+| 2020-01-01 05:00:00 |
++---------------------+
+(1 row)
+
+!ok
+
+# Two timestamps with time zone are equal if they represent the same UTC time
+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';
++--------+
+| EXPR$0 |
++--------+
+| true   |
++--------+
+(1 row)
+
+!ok
+
+# Two equal timestamps in different time zones are different if they represent 
different UTC times
+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';
++--------+
+| EXPR$0 |
++--------+
+| false  |
++--------+
+(1 row)
+
+!ok
+
 # Two tests for [CALCITE-7094] Using a type alias as a constructor function
 # causes a validator assertion failure
 select int64();
diff --git 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
index f88170b1b5..61fa2b0d71 100644
--- 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
+++ 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
@@ -1077,6 +1077,7 @@ public static Expression translateLiteral(
                   () -> "value for " + literal).toString()));
     case DATE:
     case TIME:
+    case TIME_TZ:
     case TIME_WITH_LOCAL_TIME_ZONE:
     case INTERVAL_YEAR:
     case INTERVAL_YEAR_MONTH:
@@ -1085,6 +1086,7 @@ public static Expression translateLiteral(
       javaClass = int.class;
       break;
     case TIMESTAMP:
+    case TIMESTAMP_TZ:
     case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
     case INTERVAL_DAY:
     case INTERVAL_DAY_HOUR:
diff --git a/core/src/main/java/org/apache/calcite/rex/RexLiteral.java 
b/core/src/main/java/org/apache/calcite/rex/RexLiteral.java
index 7fd05901e7..545e81995d 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexLiteral.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexLiteral.java
@@ -60,6 +60,11 @@
 import java.nio.ByteBuffer;
 import java.nio.charset.Charset;
 import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
 import java.util.Calendar;
 import java.util.List;
 import java.util.Locale;
@@ -1183,9 +1188,18 @@ public boolean isNull() {
       break;
     case TIMESTAMP_TZ:
       if (clazz == Long.class) {
-        return clazz.cast(((TimestampWithTimeZoneString) value)
+        TimestampWithTimeZoneString tstz = (TimestampWithTimeZoneString) value;
+        long ms = tstz
             .getLocalTimestampString()
-            .getMillisSinceEpoch());
+            .getMillisSinceEpoch();
+        // Interpret the timestamp part as a UTC timestamp
+        LocalDateTime local = 
Instant.ofEpochMilli(ms).atZone(ZoneOffset.UTC).toLocalDateTime();
+        // Adjust for the time zone
+        ZoneId id = tstz.getTimeZone().toZoneId();
+        ZonedDateTime zoned = local.atZone(id);
+        ZonedDateTime utc = zoned.withZoneSameInstant(ZoneOffset.UTC);
+        ms = utc.toInstant().toEpochMilli();
+        return clazz.cast(ms);
       } else if (clazz == Calendar.class) {
         TimestampWithTimeZoneString ts = (TimestampWithTimeZoneString) value;
         return 
clazz.cast(ts.getLocalTimestampString().toCalendar(ts.getTimeZone()));
diff --git 
a/core/src/main/java/org/apache/calcite/sql2rel/SqlNodeToRexConverterImpl.java 
b/core/src/main/java/org/apache/calcite/sql2rel/SqlNodeToRexConverterImpl.java
index ff0d1dfa74..081a81cbc0 100644
--- 
a/core/src/main/java/org/apache/calcite/sql2rel/SqlNodeToRexConverterImpl.java
+++ 
b/core/src/main/java/org/apache/calcite/sql2rel/SqlNodeToRexConverterImpl.java
@@ -26,6 +26,7 @@
 import org.apache.calcite.sql.SqlLiteral;
 import org.apache.calcite.sql.SqlTimeLiteral;
 import org.apache.calcite.sql.SqlTimestampLiteral;
+import org.apache.calcite.sql.SqlTimestampTzLiteral;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.util.BitString;
 import org.apache.calcite.util.DateString;
@@ -131,7 +132,7 @@ public class SqlNodeToRexConverterImpl implements 
SqlNodeToRexConverter {
     case TIMESTAMP_TZ:
       return rexBuilder.makeTimestampTzLiteral(
           literal.getValueAs(TimestampWithTimeZoneString.class),
-          ((SqlTimestampLiteral) literal).getPrec());
+          ((SqlTimestampTzLiteral) literal).getPrec());
     case TIME:
       return rexBuilder.makeTimeLiteral(
           literal.getValueAs(TimeString.class),
diff --git 
a/core/src/main/java/org/apache/calcite/util/TimestampWithTimeZoneString.java 
b/core/src/main/java/org/apache/calcite/util/TimestampWithTimeZoneString.java
index 14195469ab..5816b12a65 100644
--- 
a/core/src/main/java/org/apache/calcite/util/TimestampWithTimeZoneString.java
+++ 
b/core/src/main/java/org/apache/calcite/util/TimestampWithTimeZoneString.java
@@ -173,7 +173,7 @@ public TimestampWithTimeZoneString withTimeZone(TimeZone 
timeZone) {
   }
 
   @Override public int compareTo(TimestampWithTimeZoneString o) {
-    return v.compareTo(o.v);
+    return this.pt.getCalendar().compareTo(o.pt.getCalendar());
   }
 
   public TimestampWithTimeZoneString round(int precision) {
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index 96eb1a25b5..5f12b22149 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -1682,6 +1682,7 @@ #### Explicit Type Conversion
 
 timeZone:
       WITHOUT TIME ZONE
+  |   WITH TIME ZONE
   |   WITH LOCAL TIME ZONE
 {% endhighlight %}
 

Reply via email to