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

gianm pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git


The following commit(s) were added to refs/heads/master by this push:
     new 89e0c91076a fix(sql): classify invalid timestamp constants (#20328)
89e0c91076a is described below

commit 89e0c91076a41f44b5b47639ccc05bd9721e8863
Author: Frank Chen <[email protected]>
AuthorDate: Fri Sep 11 22:03:40 2026 +0800

    fix(sql): classify invalid timestamp constants (#20328)
---
 .../sql/calcite/planner/DruidRexExecutor.java      | 40 ++++++++++++---
 .../apache/druid/sql/calcite/CalciteQueryTest.java |  2 +-
 .../org/apache/druid/sql/http/SqlResourceTest.java | 60 ++++++++++++++++++++++
 3 files changed, 94 insertions(+), 8 deletions(-)

diff --git 
a/sql/src/main/java/org/apache/druid/sql/calcite/planner/DruidRexExecutor.java 
b/sql/src/main/java/org/apache/druid/sql/calcite/planner/DruidRexExecutor.java
index 6edb94a0518..378a89f25b8 100644
--- 
a/sql/src/main/java/org/apache/druid/sql/calcite/planner/DruidRexExecutor.java
+++ 
b/sql/src/main/java/org/apache/druid/sql/calcite/planner/DruidRexExecutor.java
@@ -20,8 +20,11 @@
 package org.apache.druid.sql.calcite.planner;
 
 import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
 import org.apache.calcite.rex.RexExecutor;
+import org.apache.calcite.rex.RexLiteral;
 import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.druid.error.InvalidSqlInput;
 import org.apache.druid.java.util.common.DateTimes;
@@ -115,15 +118,27 @@ public class DruidRexExecutor implements RexExecutor
               // There can be implicit casts of VARCHAR to TIMESTAMP where the 
VARCHAR is an invalid timestamp, but the
               // TIMESTAMP type is not nullable. In this case it's best to 
throw an error, since it likely means the
               // user's SQL query contains an invalid literal.
-              throw InvalidSqlInput.exception("Illegal TIMESTAMP constant 
[%s]", constExp);
+              throw InvalidSqlInput.exception(
+                  "Invalid TIMESTAMP value [%s]",
+                  getInputValueOrExpression(constExp)
+              );
             }
           } else {
-            literal = Calcites.jodaToCalciteTimestampLiteral(
-                rexBuilder,
-                DateTimes.utc(exprResult.asLong()),
-                plannerContext.getTimeZone(),
-                constExp.getType().getPrecision()
-            );
+            try {
+              literal = Calcites.jodaToCalciteTimestampLiteral(
+                  rexBuilder,
+                  DateTimes.utc(exprResult.asLong()),
+                  plannerContext.getTimeZone(),
+                  constExp.getType().getPrecision()
+              );
+            }
+            catch (IllegalArgumentException e) {
+              throw InvalidSqlInput.exception(
+                  e,
+                  "Invalid TIMESTAMP value [%s]",
+                  getInputValueOrExpression(constExp)
+              );
+            }
           }
         } else if (SqlTypeName.NUMERIC_TYPES.contains(sqlTypeName)) {
           final BigDecimal bigDecimal;
@@ -220,4 +235,15 @@ public class DruidRexExecutor implements RexExecutor
       }
     }
   }
+
+  private static String getInputValueOrExpression(final RexNode constExp)
+  {
+    if (constExp.isA(SqlKind.CAST)) {
+      final RexNode operand = ((RexCall) constExp).getOperands().get(0);
+      if (operand instanceof RexLiteral && 
SqlTypeName.STRING_TYPES.contains(operand.getType().getSqlTypeName())) {
+        return RexLiteral.stringValue(operand);
+      }
+    }
+    return constExp.toString();
+  }
 }
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java 
b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java
index 7ab2628271d..ac412d23feb 100644
--- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java
+++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java
@@ -6693,7 +6693,7 @@ public class CalciteQueryTest extends BaseCalciteQueryTest
     catch (DruidException e) {
       assertDruidException(
           e,
-          invalidSqlIs("Illegal TIMESTAMP constant [CAST('z2000-01-01 
00:00:00'):TIMESTAMP(3) NOT NULL]")
+          invalidSqlIs("Invalid TIMESTAMP value [z2000-01-01 00:00:00]")
       );
     }
     catch (Exception e) {
diff --git a/sql/src/test/java/org/apache/druid/sql/http/SqlResourceTest.java 
b/sql/src/test/java/org/apache/druid/sql/http/SqlResourceTest.java
index 8aa72e7b67a..0909be0671c 100644
--- a/sql/src/test/java/org/apache/druid/sql/http/SqlResourceTest.java
+++ b/sql/src/test/java/org/apache/druid/sql/http/SqlResourceTest.java
@@ -1599,6 +1599,66 @@ public class SqlResourceTest extends CalciteTestBase
     Assertions.assertEquals(400, 
stubServiceEmitter.getMetricEvents("sqlQuery/time").get(0).toMap().get(DruidMetrics.STATUS_CODE));
   }
 
+  @Test
+  public void testInvalidTimestampLiteral() throws Exception
+  {
+    final ErrorResponse errorResponse = postSyncForException(
+        "SELECT * FROM druid.foo WHERE __time BETWEEN '2026-09-10 00:00:00' 
AND '20260-09-11 00:00:00' LIMIT 1",
+        Status.BAD_REQUEST.getStatusCode()
+    );
+
+    validateInvalidSqlError(
+        errorResponse,
+        "Invalid TIMESTAMP value [20260-09-11 00:00:00]"
+    );
+    Assertions.assertTrue(lifecycleManager.getAll("id").isEmpty());
+    stubServiceEmitter.verifyEmitted("sqlQuery/time", 1);
+    Assertions.assertEquals(
+        Status.BAD_REQUEST.getStatusCode(),
+        
stubServiceEmitter.getMetricEvents("sqlQuery/time").get(0).toMap().get(DruidMetrics.STATUS_CODE)
+    );
+  }
+
+  @Test
+  public void testInvalidTimestampExpressionWithoutCast() throws Exception
+  {
+    final ErrorResponse errorResponse = postSyncForException(
+        "SELECT MILLIS_TO_TIMESTAMP(253402300800000)",
+        Status.BAD_REQUEST.getStatusCode()
+    );
+
+    validateInvalidSqlError(
+        errorResponse,
+        "Invalid TIMESTAMP value [MILLIS_TO_TIMESTAMP(253402300800000:BIGINT)]"
+    );
+    Assertions.assertTrue(lifecycleManager.getAll("id").isEmpty());
+    stubServiceEmitter.verifyEmitted("sqlQuery/time", 1);
+    Assertions.assertEquals(
+        Status.BAD_REQUEST.getStatusCode(),
+        
stubServiceEmitter.getMetricEvents("sqlQuery/time").get(0).toMap().get(DruidMetrics.STATUS_CODE)
+    );
+  }
+
+  @Test
+  public void testInvalidTimestampLiteralWithExplicitCast() throws Exception
+  {
+    final ErrorResponse errorResponse = postSyncForException(
+        "SELECT CAST('20260-09-11 00:00:00' AS TIMESTAMP)",
+        Status.BAD_REQUEST.getStatusCode()
+    );
+
+    validateInvalidSqlError(
+        errorResponse,
+        "Invalid TIMESTAMP value [20260-09-11 00:00:00]"
+    );
+    Assertions.assertTrue(lifecycleManager.getAll("id").isEmpty());
+    stubServiceEmitter.verifyEmitted("sqlQuery/time", 1);
+    Assertions.assertEquals(
+        Status.BAD_REQUEST.getStatusCode(),
+        
stubServiceEmitter.getMetricEvents("sqlQuery/time").get(0).toMap().get(DruidMetrics.STATUS_CODE)
+    );
+  }
+
   @Test
   public void testResourceLimitExceeded() throws Exception
   {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to