This is an automated email from the ASF dual-hosted git repository.
yashmayya pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git
The following commit(s) were added to refs/heads/master by this push:
new 95c071c0b95 Fix MSE constant folding of year-month INTERVAL arithmetic
(#19788)
95c071c0b95 is described below
commit 95c071c0b95d0ca2a7f725d961a4095193d1e709
Author: Yash Mayya <[email protected]>
AuthorDate: Thu Oct 15 14:45:16 2026 -0700
Fix MSE constant folding of year-month INTERVAL arithmetic (#19788)
---
.../rel/rules/PinotEvaluateLiteralRule.java | 131 ++++++++++
.../resources/queries/LiteralEvaluationPlans.json | 265 +++++++++++++++++++++
2 files changed, 396 insertions(+)
diff --git
a/pinot-query-planner/src/main/java/org/apache/pinot/calcite/rel/rules/PinotEvaluateLiteralRule.java
b/pinot-query-planner/src/main/java/org/apache/pinot/calcite/rel/rules/PinotEvaluateLiteralRule.java
index 1c721f203ae..856b776c966 100644
---
a/pinot-query-planner/src/main/java/org/apache/pinot/calcite/rel/rules/PinotEvaluateLiteralRule.java
+++
b/pinot-query-planner/src/main/java/org/apache/pinot/calcite/rel/rules/PinotEvaluateLiteralRule.java
@@ -21,14 +21,18 @@ package org.apache.pinot.calcite.rel.rules;
import com.google.common.base.Preconditions;
import java.lang.reflect.Array;
import java.math.BigDecimal;
+import java.math.MathContext;
+import java.math.RoundingMode;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Objects;
import javax.annotation.Nullable;
import org.apache.calcite.avatica.util.ByteString;
+import org.apache.calcite.avatica.util.DateTimeUtils;
import org.apache.calcite.avatica.util.TimeUnitRange;
import org.apache.calcite.plan.RelOptRule;
import org.apache.calcite.plan.RelOptRuleCall;
@@ -43,11 +47,14 @@ import org.apache.calcite.rex.RexShuttle;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.type.ArraySqlType;
import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.sql.type.SqlTypeUtil;
import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.DateString;
import org.apache.calcite.util.NlsString;
import org.apache.pinot.common.function.FunctionInfo;
import org.apache.pinot.common.function.FunctionRegistry;
import org.apache.pinot.common.function.QueryFunctionInvoker;
+import org.apache.pinot.common.function.scalar.DateTimeFunctions;
import org.apache.pinot.common.utils.DataSchema.ColumnDataType;
import org.apache.pinot.query.planner.logical.RelToPlanNodeConverter;
import org.apache.pinot.spi.utils.TimestampUtils;
@@ -171,6 +178,12 @@ public class PinotEvaluateLiteralRule {
arguments[i] = getLiteralValue(rexLiteral);
}
+ if (isDatetimeIntervalArithmetic(rexCall)) {
+ return evaluateDatetimeIntervalArithmetic(rexCall, rexBuilder);
+ }
+ if (isIntervalArithmetic(rexCall)) {
+ return evaluateIntervalArithmetic(rexCall, rexBuilder);
+ }
if (rexCall.getKind() == SqlKind.CAST) {
// Handle separately because the CAST operator only has one operand (the
value to be cast) and the type to be cast
// to is determined by the operator's return type. Pinot's CAST function
implementation requires two arguments:
@@ -248,6 +261,114 @@ public class PinotEvaluateLiteralRule {
}
}
+ /// Returns whether the call is `datetime + interval` (Calcite's
`DATETIME_PLUS`) or `datetime - interval`
+ /// (`MINUS_DATE`) with a `TIMESTAMP` or `DATE` result. Calcite puts the
interval second for both.
+ private static boolean isDatetimeIntervalArithmetic(RexCall rexCall) {
+ SqlKind kind = rexCall.getKind();
+ SqlTypeName resultType = rexCall.getType().getSqlTypeName();
+ List<RexNode> operands = rexCall.getOperands();
+ return (kind == SqlKind.PLUS || kind == SqlKind.MINUS) && (resultType ==
SqlTypeName.TIMESTAMP
+ || resultType == SqlTypeName.DATE) && operands.size() == 2 &&
SqlTypeUtil.isInterval(operands.get(1).getType());
+ }
+
+ /// Evaluates `datetime + interval` or `datetime - interval` with the same
semantics as Calcite. It cannot use Pinot's
+ /// `plus` / `minus` functions, because Calcite stores a day-time interval
as milliseconds but a year-month interval
+ /// as months: they would add `INTERVAL '1' MONTH` as 1 millisecond. Months
are added with the same calendar
+ /// arithmetic as `TIMESTAMPADD(MONTH, ...)`, so `2019-01-31 + INTERVAL '1'
MONTH` is `2019-02-28`. Like Calcite, only
+ /// the whole days of a day-time interval are added to a `DATE`.
+ private static RexNode evaluateDatetimeIntervalArithmetic(RexCall rexCall,
RexBuilder rexBuilder) {
+ RexLiteral datetime = (RexLiteral) rexCall.getOperands().get(0);
+ RexLiteral interval = (RexLiteral) rexCall.getOperands().get(1);
+ RelDataType resultType = rexCall.getType();
+ if (datetime.isNull() || interval.isNull()) {
+ return rexBuilder.makeNullLiteral(resultType);
+ }
+ Object resultValue;
+ try {
+ long millis =
Objects.requireNonNull(datetime.getValueAs(Calendar.class)).getTimeInMillis();
+ // Number of months for a year-month interval, number of milliseconds
for a day-time interval
+ long intervalValue =
Objects.requireNonNull(interval.getValueAs(BigDecimal.class)).longValueExact();
+ if (rexCall.getKind() == SqlKind.MINUS) {
+ intervalValue = Math.negateExact(intervalValue);
+ }
+ boolean isDate = resultType.getSqlTypeName() == SqlTypeName.DATE;
+ long resultMillis;
+ if
(SqlTypeName.YEAR_INTERVAL_TYPES.contains(interval.getType().getSqlTypeName()))
{
+ resultMillis = DateTimeFunctions.timestampAdd("MONTH", intervalValue,
millis);
+ } else {
+ if (isDate) {
+ intervalValue = intervalValue / DateTimeUtils.MILLIS_PER_DAY *
DateTimeUtils.MILLIS_PER_DAY;
+ }
+ resultMillis = Math.addExact(millis, intervalValue);
+ }
+ resultValue = isDate ? DateString.fromDaysSinceEpoch(
+ Math.toIntExact(Math.floorDiv(resultMillis,
DateTimeUtils.MILLIS_PER_DAY))) : resultMillis;
+ } catch (Exception e) {
+ throw new SqlCompilationException(
+ "Caught exception while evaluating: " + rexCall + ": " +
e.getMessage(), e);
+ }
+ return rexBuilder.makeLiteral(resultValue, resultType, false);
+ }
+
+ /// Returns whether the call is `+`, `-`, `*` or `/` with an interval result
and only interval or numeric operands,
+ /// e.g. `INTERVAL '1' MONTH * 2`.
+ private static boolean isIntervalArithmetic(RexCall rexCall) {
+ SqlKind kind = rexCall.getKind();
+ List<RexNode> operands = rexCall.getOperands();
+ return (kind == SqlKind.PLUS || kind == SqlKind.MINUS || kind ==
SqlKind.TIMES || kind == SqlKind.DIVIDE)
+ && SqlTypeUtil.isInterval(rexCall.getType()) && operands.size() == 2
&& operands.stream()
+ .allMatch(operand -> SqlTypeUtil.isInterval(operand.getType()) ||
SqlTypeUtil.isNumeric(operand.getType()));
+ }
+
+ /// Evaluates interval arithmetic exactly. Pinot's `times` / `divide`
functions compute in DOUBLE, so
+ /// `INTERVAL '100' MONTH * 0.29` would be 28.999999999999996 months instead
of 29, and `INTERVAL '1' DAY * 0.7` would
+ /// be 1 millisecond short.
+ private static RexNode evaluateIntervalArithmetic(RexCall rexCall,
RexBuilder rexBuilder) {
+ RexLiteral left = (RexLiteral) rexCall.getOperands().get(0);
+ RexLiteral right = (RexLiteral) rexCall.getOperands().get(1);
+ RelDataType resultType = rexCall.getType();
+ if (left.isNull() || right.isNull()) {
+ return rexBuilder.makeNullLiteral(resultType);
+ }
+ BigDecimal resultValue;
+ try {
+ // An interval literal holds its number of months or milliseconds
+ BigDecimal leftValue =
Objects.requireNonNull(left.getValueAs(BigDecimal.class));
+ BigDecimal rightValue =
Objects.requireNonNull(right.getValueAs(BigDecimal.class));
+ switch (rexCall.getKind()) {
+ case PLUS:
+ resultValue = leftValue.add(rightValue);
+ break;
+ case MINUS:
+ resultValue = leftValue.subtract(rightValue);
+ break;
+ case TIMES:
+ resultValue = leftValue.multiply(rightValue);
+ break;
+ default:
+ resultValue = leftValue.divide(rightValue, MathContext.DECIMAL128);
+ break;
+ }
+ resultValue = toIntervalValue(resultValue, resultType);
+ } catch (Exception e) {
+ throw new SqlCompilationException(
+ "Caught exception while evaluating: " + rexCall + ": " +
e.getMessage(), e);
+ }
+ return rexBuilder.makeLiteral(resultValue, resultType, false);
+ }
+
+ /// Converts a number to the value that Calcite stores for an interval: a
whole number of months for a year-month
+ /// interval, and milliseconds for a day-time interval (like Calcite,
fractions of a millisecond are dropped).
+ private static BigDecimal toIntervalValue(BigDecimal value, RelDataType
intervalType) {
+ if
(SqlTypeName.YEAR_INTERVAL_TYPES.contains(intervalType.getSqlTypeName())) {
+ // Intentionally stricter than Calcite, which truncates to whole months
(INTERVAL '1' MONTH / 2 is 0 months):
+ // reject the query instead of silently dropping a fraction of a month
+ Preconditions.checkArgument(value.stripTrailingZeros().scale() <= 0,
+ "Year-month interval must be a whole number of months, got: %s",
value);
+ }
+ return value.setScale(0, RoundingMode.DOWN);
+ }
+
private static RelDataType convertDecimalType(RelDataType relDataType,
RexBuilder rexBuilder) {
Preconditions.checkArgument(relDataType.getSqlTypeName() ==
SqlTypeName.DECIMAL);
return
RelToPlanNodeConverter.convertToColumnDataType(relDataType).toType(rexBuilder.getTypeFactory());
@@ -307,6 +428,16 @@ public class PinotEvaluateLiteralRule {
if (relDataType.getSqlTypeName() == SqlTypeName.DECIMAL) {
return new BigDecimal(resultValue.toString());
}
+ // Other functions with an interval result, e.g. ABS(INTERVAL '-1' MONTH).
Interval arithmetic is evaluated exactly
+ // in evaluateIntervalArithmetic().
+ if (SqlTypeUtil.isInterval(relDataType)) {
+ if (resultValue instanceof BigDecimal) {
+ return toIntervalValue((BigDecimal) resultValue, relDataType);
+ }
+ double value = ((Number) resultValue).doubleValue();
+ Preconditions.checkArgument(Double.isFinite(value), "Invalid interval
value: %s", value);
+ return toIntervalValue(BigDecimal.valueOf(value), relDataType);
+ }
// Use double for FLOAT / DOUBLE literals
if (relDataType.getSqlTypeName() == SqlTypeName.FLOAT ||
relDataType.getSqlTypeName() == SqlTypeName.DOUBLE
|| relDataType.getSqlTypeName() == SqlTypeName.REAL) {
diff --git
a/pinot-query-planner/src/test/resources/queries/LiteralEvaluationPlans.json
b/pinot-query-planner/src/test/resources/queries/LiteralEvaluationPlans.json
index 5decbcd0408..2f67b0d0bff 100644
--- a/pinot-query-planner/src/test/resources/queries/LiteralEvaluationPlans.json
+++ b/pinot-query-planner/src/test/resources/queries/LiteralEvaluationPlans.json
@@ -283,10 +283,275 @@
"\n"
]
},
+ {
+ "description": "timestamp plus year interval",
+ "sql": "EXPLAIN PLAN FOR SELECT TIMESTAMP '2019-01-01 00:00:00' +
INTERVAL '1' YEAR FROM a",
+ "output": [
+ "Execution Plan",
+ "\nLogicalProject(EXPR$0=[2020-01-01 00:00:00])",
+ "\n PinotLogicalTableScan(table=[[default, a]])",
+ "\n"
+ ]
+ },
+ {
+ "description": "timestamp plus month interval",
+ "sql": "EXPLAIN PLAN FOR SELECT TIMESTAMP '2019-01-01 00:00:00' +
INTERVAL '1' MONTH FROM a",
+ "output": [
+ "Execution Plan",
+ "\nLogicalProject(EXPR$0=[2019-02-01 00:00:00])",
+ "\n PinotLogicalTableScan(table=[[default, a]])",
+ "\n"
+ ]
+ },
+ {
+ "description": "timestamp plus month interval at end of month",
+ "sql": "EXPLAIN PLAN FOR SELECT TIMESTAMP '2019-01-31 10:00:00' +
INTERVAL '1' MONTH FROM a",
+ "output": [
+ "Execution Plan",
+ "\nLogicalProject(EXPR$0=[2019-02-28 10:00:00])",
+ "\n PinotLogicalTableScan(table=[[default, a]])",
+ "\n"
+ ]
+ },
+ {
+ "description": "timestamp plus year to month interval",
+ "sql": "EXPLAIN PLAN FOR SELECT TIMESTAMP '2019-01-01 00:00:00' +
INTERVAL '1-2' YEAR TO MONTH FROM a",
+ "output": [
+ "Execution Plan",
+ "\nLogicalProject(EXPR$0=[2020-03-01 00:00:00])",
+ "\n PinotLogicalTableScan(table=[[default, a]])",
+ "\n"
+ ]
+ },
+ {
+ "description": "month interval plus timestamp",
+ "sql": "EXPLAIN PLAN FOR SELECT INTERVAL '1' MONTH + TIMESTAMP
'2019-01-01 00:00:00' FROM a",
+ "output": [
+ "Execution Plan",
+ "\nLogicalProject(EXPR$0=[2019-02-01 00:00:00])",
+ "\n PinotLogicalTableScan(table=[[default, a]])",
+ "\n"
+ ]
+ },
+ {
+ "description": "timestamp minus month interval",
+ "sql": "EXPLAIN PLAN FOR SELECT TIMESTAMP '2019-03-31 00:00:00' -
INTERVAL '1' MONTH FROM a",
+ "output": [
+ "Execution Plan",
+ "\nLogicalProject(EXPR$0=[2019-02-28 00:00:00])",
+ "\n PinotLogicalTableScan(table=[[default, a]])",
+ "\n"
+ ]
+ },
+ {
+ "description": "timestamp plus negative month interval",
+ "sql": "EXPLAIN PLAN FOR SELECT TIMESTAMP '2019-03-01 00:00:00' +
INTERVAL '-1' MONTH FROM a",
+ "output": [
+ "Execution Plan",
+ "\nLogicalProject(EXPR$0=[2019-02-01 00:00:00])",
+ "\n PinotLogicalTableScan(table=[[default, a]])",
+ "\n"
+ ]
+ },
+ {
+ "description": "timestamp plus sum of year and month intervals",
+ "sql": "EXPLAIN PLAN FOR SELECT TIMESTAMP '2019-01-01 00:00:00' +
(INTERVAL '1' YEAR + INTERVAL '2' MONTH) FROM a",
+ "output": [
+ "Execution Plan",
+ "\nLogicalProject(EXPR$0=[2020-03-01 00:00:00])",
+ "\n PinotLogicalTableScan(table=[[default, a]])",
+ "\n"
+ ]
+ },
+ {
+ "description": "timestamp plus multiplied month interval",
+ "sql": "EXPLAIN PLAN FOR SELECT TIMESTAMP '2019-01-01 00:00:00' +
INTERVAL '1' MONTH * 2 FROM a",
+ "output": [
+ "Execution Plan",
+ "\nLogicalProject(EXPR$0=[2019-03-01 00:00:00])",
+ "\n PinotLogicalTableScan(table=[[default, a]])",
+ "\n"
+ ]
+ },
+ {
+ "description": "timestamp plus month interval times a decimal",
+ "sql": "EXPLAIN PLAN FOR SELECT TIMESTAMP '2019-01-01 00:00:00' +
INTERVAL '100' MONTH * 0.29 FROM a",
+ "output": [
+ "Execution Plan",
+ "\nLogicalProject(EXPR$0=[2021-06-01 00:00:00])",
+ "\n PinotLogicalTableScan(table=[[default, a]])",
+ "\n"
+ ]
+ },
+ {
+ "description": "timestamp plus month interval times another decimal",
+ "sql": "EXPLAIN PLAN FOR SELECT TIMESTAMP '2019-01-01 00:00:00' +
INTERVAL '100' MONTH * 1.11 FROM a",
+ "output": [
+ "Execution Plan",
+ "\nLogicalProject(EXPR$0=[2028-04-01 00:00:00])",
+ "\n PinotLogicalTableScan(table=[[default, a]])",
+ "\n"
+ ]
+ },
+ {
+ "description": "timestamp plus year interval divided by an integer",
+ "sql": "EXPLAIN PLAN FOR SELECT TIMESTAMP '2019-01-01 00:00:00' +
INTERVAL '1' YEAR / 4 FROM a",
+ "output": [
+ "Execution Plan",
+ "\nLogicalProject(EXPR$0=[2019-04-01 00:00:00])",
+ "\n PinotLogicalTableScan(table=[[default, a]])",
+ "\n"
+ ]
+ },
+ {
+ "description": "timestamp plus difference of year and month intervals",
+ "sql": "EXPLAIN PLAN FOR SELECT TIMESTAMP '2019-01-01 00:00:00' +
(INTERVAL '1' YEAR - INTERVAL '1' MONTH) FROM a",
+ "output": [
+ "Execution Plan",
+ "\nLogicalProject(EXPR$0=[2019-12-01 00:00:00])",
+ "\n PinotLogicalTableScan(table=[[default, a]])",
+ "\n"
+ ]
+ },
+ {
+ "description": "null timestamp plus month interval",
+ "sql": "EXPLAIN PLAN FOR SELECT fromDateTime(CAST(NULL AS VARCHAR),
'yyyy-MM-dd') + INTERVAL '1' MONTH FROM a",
+ "output": [
+ "Execution Plan",
+ "\nLogicalProject(EXPR$0=[null:TIMESTAMP(3)])",
+ "\n PinotLogicalTableScan(table=[[default, a]])",
+ "\n"
+ ]
+ },
+ {
+ "description": "timestamp plus null month interval",
+ "sql": "EXPLAIN PLAN FOR SELECT TIMESTAMP '2019-01-01 00:00:00' +
INTERVAL '1' MONTH * toEpochDays(fromDateTime(CAST(NULL AS VARCHAR),
'yyyy-MM-dd')) FROM a",
+ "output": [
+ "Execution Plan",
+ "\nLogicalProject(EXPR$0=[null:TIMESTAMP(0)])",
+ "\n PinotLogicalTableScan(table=[[default, a]])",
+ "\n"
+ ]
+ },
+ {
+ "description": "timestamp plus day interval",
+ "sql": "EXPLAIN PLAN FOR SELECT TIMESTAMP '2019-01-01 00:00:00' +
INTERVAL '1' DAY FROM a",
+ "output": [
+ "Execution Plan",
+ "\nLogicalProject(EXPR$0=[2019-01-02 00:00:00])",
+ "\n PinotLogicalTableScan(table=[[default, a]])",
+ "\n"
+ ]
+ },
+ {
+ "description": "timestamp plus hour interval",
+ "sql": "EXPLAIN PLAN FOR SELECT TIMESTAMP '2019-01-01 00:00:00' +
INTERVAL '1' HOUR FROM a",
+ "output": [
+ "Execution Plan",
+ "\nLogicalProject(EXPR$0=[2019-01-01 01:00:00])",
+ "\n PinotLogicalTableScan(table=[[default, a]])",
+ "\n"
+ ]
+ },
+ {
+ "description": "timestamp minus day to second interval",
+ "sql": "EXPLAIN PLAN FOR SELECT TIMESTAMP '2019-01-02 00:00:00' -
INTERVAL '1 01:30:15' DAY TO SECOND FROM a",
+ "output": [
+ "Execution Plan",
+ "\nLogicalProject(EXPR$0=[2018-12-31 22:29:45])",
+ "\n PinotLogicalTableScan(table=[[default, a]])",
+ "\n"
+ ]
+ },
+ {
+ "description": "timestamp plus multiplied day interval",
+ "sql": "EXPLAIN PLAN FOR SELECT TIMESTAMP '2019-01-01 00:00:00' +
INTERVAL '1' DAY * 2 FROM a",
+ "output": [
+ "Execution Plan",
+ "\nLogicalProject(EXPR$0=[2019-01-03 00:00:00])",
+ "\n PinotLogicalTableScan(table=[[default, a]])",
+ "\n"
+ ]
+ },
+ {
+ "description": "timestamp plus day interval times a decimal",
+ "sql": "EXPLAIN PLAN FOR SELECT TIMESTAMP '2019-01-01 00:00:00' +
INTERVAL '1' DAY * 0.7 FROM a",
+ "output": [
+ "Execution Plan",
+ "\nLogicalProject(EXPR$0=[2019-01-01 16:48:00])",
+ "\n PinotLogicalTableScan(table=[[default, a]])",
+ "\n"
+ ]
+ },
+ {
+ "description": "filter with timestamp minus month interval",
+ "sql": "EXPLAIN PLAN FOR SELECT * FROM a WHERE ts_timestamp >
TIMESTAMP '2019-03-01 00:00:00' - INTERVAL '1' MONTH",
+ "output": [
+ "Execution Plan",
+ "\nLogicalFilter(condition=[>($8, 2019-02-01 00:00:00)])",
+ "\n PinotLogicalTableScan(table=[[default, a]])",
+ "\n"
+ ]
+ },
+ {
+ "description": "filter with timestamp minus day interval",
+ "sql": "EXPLAIN PLAN FOR SELECT * FROM a WHERE ts_timestamp >
TIMESTAMP '2019-03-01 00:00:00' - INTERVAL '1' DAY",
+ "output": [
+ "Execution Plan",
+ "\nLogicalFilter(condition=[>($8, 2019-02-28 00:00:00)])",
+ "\n PinotLogicalTableScan(table=[[default, a]])",
+ "\n"
+ ]
+ },
+ {
+ "description": "date plus day interval",
+ "sql": "EXPLAIN PLAN FOR SELECT DATE '2019-01-01' + INTERVAL '1' DAY
FROM a",
+ "output": [
+ "Execution Plan",
+ "\nLogicalProject(EXPR$0=[2019-01-02])",
+ "\n PinotLogicalTableScan(table=[[default, a]])",
+ "\n"
+ ]
+ },
+ {
+ "description": "date plus month interval",
+ "sql": "EXPLAIN PLAN FOR SELECT DATE '2019-01-31' + INTERVAL '1' MONTH
FROM a",
+ "output": [
+ "Execution Plan",
+ "\nLogicalProject(EXPR$0=[2019-02-28])",
+ "\n PinotLogicalTableScan(table=[[default, a]])",
+ "\n"
+ ]
+ },
+ {
+ "description": "date minus year interval",
+ "sql": "EXPLAIN PLAN FOR SELECT DATE '2020-02-29' - INTERVAL '1' YEAR
FROM a",
+ "output": [
+ "Execution Plan",
+ "\nLogicalProject(EXPR$0=[2019-02-28])",
+ "\n PinotLogicalTableScan(table=[[default, a]])",
+ "\n"
+ ]
+ },
+ {
+ "description": "date minus hour interval adds only whole days",
+ "sql": "EXPLAIN PLAN FOR SELECT DATE '2019-01-01' - INTERVAL '25' HOUR
FROM a",
+ "output": [
+ "Execution Plan",
+ "\nLogicalProject(EXPR$0=[2018-12-31])",
+ "\n PinotLogicalTableScan(table=[[default, a]])",
+ "\n"
+ ]
+ },
{
"description": "select non-exist literal function",
"sql": "EXPLAIN PLAN FOR Select nonExistFun(1,2) FROM a",
"expectedException": ".*No match found for function signature
nonExistFun.*"
+ },
+ {
+ "description": "timestamp plus fraction of a month interval",
+ "sql": "EXPLAIN PLAN FOR SELECT TIMESTAMP '2019-01-01 00:00:00' +
INTERVAL '1' MONTH / 2 FROM a",
+ "expectedException": ".*Year-month interval must be a whole number of
months, got: 0.5.*"
}
]
},
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]