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)^)")