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

mbudiu 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 4b0ebd45da [CALCITE-7149] Constant TIMESTAMPADD expression causes 
assertion failure in validator
4b0ebd45da is described below

commit 4b0ebd45da1431557e7c844b06b558d620571131
Author: Mihai Budiu <[email protected]>
AuthorDate: Thu Aug 28 14:58:49 2025 -0700

    [CALCITE-7149] Constant TIMESTAMPADD expression causes assertion failure in 
validator
    
    Signed-off-by: Mihai Budiu <[email protected]>
---
 .../apache/calcite/runtime/CalciteResource.java    |  3 +++
 .../calcite/sql/fun/SqlTimestampAddFunction.java   | 12 ++++++++++--
 .../calcite/sql/fun/SqlTimestampDiffFunction.java  | 22 ++++++++++++++++++++--
 .../calcite/sql2rel/StandardConvertletTable.java   |  9 ++++++---
 .../calcite/runtime/CalciteResource.properties     |  1 +
 .../org/apache/calcite/test/SqlValidatorTest.java  | 10 ++++++++++
 6 files changed, 50 insertions(+), 7 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java 
b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
index dea1d15052..2eaf952d15 100644
--- a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
+++ b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
@@ -645,6 +645,9 @@ ExInst<CalciteException> 
illegalArgumentForTableFunctionCall(String a0,
   @BaseMessage("''{0}'' is not a valid time frame")
   ExInst<SqlValidatorException> invalidTimeFrame(String a0);
 
+  @BaseMessage("''{0}'' is not a valid time frame in ''{1}''")
+  ExInst<SqlValidatorException> invalidTimeFrameInOperation(String a0, String 
a1);
+
   @BaseMessage("Cannot INSERT into generated column ''{0}''")
   ExInst<SqlValidatorException> insertIntoAlwaysGenerated(String a0);
 
diff --git 
a/core/src/main/java/org/apache/calcite/sql/fun/SqlTimestampAddFunction.java 
b/core/src/main/java/org/apache/calcite/sql/fun/SqlTimestampAddFunction.java
index 08bf7ce0f5..ec886d2013 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlTimestampAddFunction.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlTimestampAddFunction.java
@@ -38,6 +38,7 @@
 
 import java.util.Map;
 
+import static org.apache.calcite.util.Static.RESOURCE;
 import static org.apache.calcite.util.Util.first;
 
 /**
@@ -129,8 +130,15 @@ static RelDataType deduceType(RelDataTypeFactory 
typeFactory,
     //    with startUnit = EPOCH and timeFrameName = 'MINUTE15'.
     //
     // If the latter, check that timeFrameName is valid.
-    validator.validateTimeFrame(
-        (SqlIntervalQualifier) call.getOperandList().get(0));
+    SqlIntervalQualifier op0 = call.operand(0);
+    validator.validateTimeFrame(op0);
+    if (op0.timeFrameName == null
+        && op0.timeUnitRange.startUnit.multiplier == null) {
+      // Not all time frames can be used in date arithmetic, e.g., DOW
+      throw validator.newValidationError(op0,
+          RESOURCE.invalidTimeFrameInOperation(
+              op0.timeUnitRange.toString(), call.getOperator().getName()));
+    }
   }
 
   /** Creates a SqlTimestampAddFunction. */
diff --git 
a/core/src/main/java/org/apache/calcite/sql/fun/SqlTimestampDiffFunction.java 
b/core/src/main/java/org/apache/calcite/sql/fun/SqlTimestampDiffFunction.java
index 7e658a8794..7f280f6c7e 100644
--- 
a/core/src/main/java/org/apache/calcite/sql/fun/SqlTimestampDiffFunction.java
+++ 
b/core/src/main/java/org/apache/calcite/sql/fun/SqlTimestampDiffFunction.java
@@ -33,6 +33,8 @@
 import org.apache.calcite.sql.validate.SqlValidator;
 import org.apache.calcite.sql.validate.SqlValidatorScope;
 
+import static org.apache.calcite.util.Static.RESOURCE;
+
 /**
  * The <code>TIMESTAMPDIFF</code> function, which calculates the difference
  * between two timestamps.
@@ -110,9 +112,25 @@ private static RelDataType 
inferReturnType2(SqlOperatorBinding opBinding) {
     //
     // If the latter, check that timeFrameName is valid.
     if (call.operand(2) instanceof SqlIntervalQualifier) {
-      validator.validateTimeFrame(call.operand(2));
+      SqlIntervalQualifier op2 = call.operand(2);
+      validator.validateTimeFrame(op2);
+      if (op2.timeFrameName == null
+          && op2.timeUnitRange.startUnit.multiplier == null) {
+        // Not all time frames can be used in date arithmetic, e.g., DOW
+        throw validator.newValidationError(op2,
+            RESOURCE.invalidTimeFrameInOperation(
+                op2.timeUnitRange.toString(), call.getOperator().getName()));
+      }
     } else {
-      validator.validateTimeFrame(call.operand(0));
+      SqlIntervalQualifier op0 = call.operand(0);
+      validator.validateTimeFrame(op0);
+      if (op0.timeFrameName == null
+          && op0.timeUnitRange.startUnit.multiplier == null) {
+        // Not all time frames can be used in date arithmetic, e.g., DOW
+        throw validator.newValidationError(op0,
+            RESOURCE.invalidTimeFrameInOperation(
+                op0.timeUnitRange.toString(), call.getOperator().getName()));
+      }
     }
   }
 
diff --git 
a/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java 
b/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
index 6ea9585aee..eee3d330e9 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
@@ -2350,6 +2350,10 @@ private static class TimestampAddConvertlet implements 
SqlRexConvertlet {
       }
 
       RexNode interval2Add;
+      BigDecimal multiplier = unit.multiplier;
+      if (multiplier == null) {
+        throw new IllegalArgumentException("Impossible conversion to " + unit);
+      }
       switch (unit) {
       case MICROSECOND:
       case NANOSECOND:
@@ -2357,13 +2361,12 @@ private static class TimestampAddConvertlet implements 
SqlRexConvertlet {
             divide(pos, rexBuilder,
                 multiply(pos, rexBuilder,
                     rexBuilder.makeIntervalLiteral(BigDecimal.ONE, qualifier), 
op1),
-                BigDecimal.ONE.divide(unit.multiplier,
-                    RoundingMode.UNNECESSARY));
+                BigDecimal.ONE.divide(multiplier, RoundingMode.UNNECESSARY));
         break;
       default:
         interval2Add =
             multiply(pos, rexBuilder,
-                rexBuilder.makeIntervalLiteral(unit.multiplier, qualifier), 
op1);
+                rexBuilder.makeIntervalLiteral(multiplier, qualifier), op1);
       }
 
       return rexBuilder.makeCall(pos, SqlStdOperatorTable.DATETIME_PLUS,
diff --git 
a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties 
b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
index 055233677a..3628d6a982 100644
--- 
a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
+++ 
b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
@@ -216,6 +216,7 @@ DelimiterIsRequired=Call to function ''{0}'' with argument 
of type ''{1}'' requi
 IllegalArgumentForTableFunctionCall=Wrong arguments for table function ''{0}'' 
call. Expected ''{1}'', actual ''{2}''
 CannotCallTableFunctionHere=Cannot call table function here: ''{0}''
 InvalidTimeFrame=''{0}'' is not a valid time frame
+InvalidTimeFrameInOperation=''{0}'' is not a valid time frame in ''{1}''
 InsertIntoAlwaysGenerated=Cannot INSERT into generated column ''{0}''
 ArgumentMustHaveScaleZero=Argument to function ''{0}'' must have a scale of 0
 PreparationAborted=Statement preparation aborted
diff --git a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java 
b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
index 8928861e5d..de7cd015ff 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -1898,6 +1898,16 @@ void testLikeAndSimilarFails() {
         .fails("No match found for function signature FOO..");
   }
 
+  /** Test case for <a 
href="https://issues.apache.org/jira/browse/CALCITE-7149";>[CALCITE-7149]
+   * Constant TIMESTAMPADD expression causes assertion failure in 
validator</a>. */
+  @Test void testTimestampAdd() {
+    sql("SELECT TIMESTAMPADD(^DOY^, 2, TIMESTAMP '2020-06-21 14:23:44.123')")
+        .fails("'DOY' is not a valid time frame in 'TIMESTAMPADD'");
+    sql("SELECT TIMESTAMPDIFF(^EPOCH^, TIMESTAMP '2020-06-21 14:23:44.123', "
+        + "TIMESTAMP '2022-06-21 14:23:44.123')")
+        .fails("'EPOCH' is not a valid time frame in 'TIMESTAMPDIFF'");
+  }
+
   @Test void testInvalidTableFunction() {
     // A table function at most have one input table with row semantics
     sql("select * from table(^invalid(table orders, table emp)^)")

Reply via email to