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]