You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pinot.apache.org by ya...@apache.org on 2026/10/15 21:45:22 UTC
(pinot) branch master updated: Fix MSE constant folding of year-month INTERVAL arithmetic (#19788)
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 <ya...@gmail.com>
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: commits-unsubscribe@pinot.apache.org
For additional commands, e-mail: commits-help@pinot.apache.org