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 92e0739624f..bff49f3924e 100644 --- a/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java +++ b/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java @@ -830,36 +830,44 @@ protected RexNode convertCast( protected RexNode convertFloorCeil(SqlRexContext cx, SqlCall call) { final boolean floor = call.getKind() == SqlKind.FLOOR; final SqlParserPos pos = call.getParserPosition(); - // Rewrite floor, ceil of interval - if (call.operandCount() == 1 - && call.operand(0) instanceof SqlIntervalLiteral) { - final SqlIntervalLiteral literal = call.operand(0); - SqlIntervalLiteral.IntervalValue interval = - literal.getValueAs(SqlIntervalLiteral.IntervalValue.class); - BigDecimal val = - interval.getIntervalQualifier().getStartUnit().multiplier; - RexNode rexInterval = cx.convertExpression(literal); - + // Rewrite floor, ceil of an interval as arithmetic that rounds to a + // multiple of the interval's leading unit. + if (call.operandCount() == 1) { final RexBuilder rexBuilder = cx.getRexBuilder(); - RexNode zero = rexBuilder.makeExactLiteral(BigDecimal.valueOf(0)); - RexNode cond = ge(pos, rexBuilder, rexInterval, zero); - - RexNode pad = - rexBuilder.makeExactLiteral(val.subtract(BigDecimal.ONE)); - RexNode cast = - rexBuilder.makeReinterpretCast(pos, rexInterval.getType(), pad, - rexBuilder.makeLiteral(false)); - RexNode sum = - floor ? minus(pos, rexBuilder, rexInterval, cast) - : plus(pos, rexBuilder, rexInterval, cast); - - RexNode kase = floor - ? case_(rexBuilder, rexInterval, cond, sum) - : case_(rexBuilder, sum, cond, rexInterval); - - RexNode factor = rexBuilder.makeExactLiteral(val); - RexNode div = divideInt(pos, rexBuilder, kase, factor); - return multiply(pos, rexBuilder, div, factor); + final RexNode rexInterval = cx.convertExpression(call.operand(0)); + final SqlIntervalQualifier qualifier = + rexInterval.getType().getIntervalQualifier(); + if (qualifier != null) { + if (qualifier.timeFrameName != null) { + throw new UnsupportedOperationException((floor ? "FLOOR" : "CEIL") + + " of an interval with custom time frame '" + + qualifier.timeFrameName + "' is not supported"); + } + if (!RexUtil.isDeterministic(rexInterval)) { + throw new UnsupportedOperationException((floor ? "FLOOR" : "CEIL") + + " of a non-deterministic interval expression is not" + + " supported"); + } + BigDecimal val = qualifier.getStartUnit().multiplier; + RexNode zero = rexBuilder.makeExactLiteral(BigDecimal.valueOf(0)); + RexNode cond = ge(pos, rexBuilder, rexInterval, zero); + + RexNode pad = + rexBuilder.makeIntervalLiteral(val.subtract(BigDecimal.ONE), + qualifier); + RexNode sum = + floor ? minus(pos, rexBuilder, rexInterval, pad) + : plus(pos, rexBuilder, rexInterval, pad); + + // CASE operands are (when, then, else) + RexNode kase = floor + ? case_(rexBuilder, cond, rexInterval, sum) + : case_(rexBuilder, cond, sum, rexInterval); + + RexNode factor = rexBuilder.makeExactLiteral(val); + RexNode div = divideInt(pos, rexBuilder, kase, factor); + return multiply(pos, rexBuilder, div, factor); + } } // normal floor, ceil function diff --git a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java index 6ce401502c9..1ce00a043dc 100644 --- a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java +++ b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java @@ -82,6 +82,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.hasSize; +import static org.junit.jupiter.api.Assertions.assertThrows; /** * Unit test for {@link org.apache.calcite.sql2rel.SqlToRelConverter}. @@ -6309,6 +6310,36 @@ void checkUserDefinedOrderByOver(NullCollation nullCollation) { assertThat(plan, containsString("FLOOR($4, FLAG(WEEK))")); } + /** Test case for + * [CALCITE-7692] + * FLOOR/CEIL of INTERVAL produces wrong results. + * + *
FLOOR and CEIL of an interval expression, literal or not, are rewritten + * as arithmetic that rounds to a multiple of the interval's leading unit. */ + @Test void testFloorCeilOfInterval() { + final String sql = "select floor(x) as f, ceil(x) as c\n" + + "from (values (interval '3:4:5' hour to second)) as t(x)"; + sql(sql).ok(); + } + + /** Test case for + * [CALCITE-7692] + * FLOOR/CEIL of INTERVAL produces wrong results. + * + *
The rewrite evaluates its operand more than once, which is unsound
+ * for a non-deterministic operand; conversion must fail rather than
+ * produce incorrect results. */
+ @Test void testFloorOfNonDeterministicInterval() {
+ final String sql = "select floor(x * rand()) as f\n"
+ + "from (values (interval '3:4:5' hour to second)) as t(x)";
+ final UnsupportedOperationException e =
+ assertThrows(UnsupportedOperationException.class,
+ () -> sql(sql).toRel());
+ assertThat(e.getMessage(),
+ is("FLOOR of a non-deterministic interval expression is not"
+ + " supported"));
+ }
+
/** Test case of
* [CALCITE-5406]
* Support the SELECT DISTINCT ON statement for PostgreSQL dialect. */
diff --git a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
index 6e98c11baa8..343c9ace258 100644
--- a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
@@ -2624,6 +2624,18 @@ LogicalSort(fetch=[+(1, ABS(-2))])
+
+
+