diff --git a/core/src/main/codegen/templates/Parser.jj b/core/src/main/codegen/templates/Parser.jj index 1d8465e977ea..279fa3863697 100644 --- a/core/src/main/codegen/templates/Parser.jj +++ b/core/src/main/codegen/templates/Parser.jj @@ -3770,7 +3770,7 @@ SqlNode AtomicRowExpression() : } { ( - e = Literal() + e = LiteralOrIntervalExpression() | e = DynamicParam() | @@ -4007,11 +4007,29 @@ SqlDrop SqlDrop() : * Usually returns an SqlLiteral, but a continued string literal * is an SqlCall expression, which concatenates 2 or more string * literals; the validator reduces this. + * + *

If the context allows both literals and expressions, + * use {@link #LiteralOrIntervalExpression}, which requires less + * lookahead. */ SqlNode Literal() : { SqlNode e; } +{ + ( + e = NonIntervalLiteral() + | + e = IntervalLiteral() + ) + { return e; } +} + +/** Parses a literal that is not an interval literal. */ +SqlNode NonIntervalLiteral() : +{ + final SqlNode e; +} { ( e = NumericLiteral() @@ -4021,8 +4039,6 @@ SqlNode Literal() : e = SpecialLiteral() | e = DateTimeLiteral() - | - e = IntervalLiteral() <#-- additional literal parser methods are included here --> <#list parser.literalParserMethods as method> | @@ -4032,8 +4048,25 @@ SqlNode Literal() : { return e; } +} - +/** Parses a literal or an interval expression. + * + *

We include them in the same production because it is difficult to + * distinguish interval literals from interval expression (both of which + * start with the {@code INTERVAL} keyword); this way, we can use less + * LOOKAHEAD. */ +SqlNode LiteralOrIntervalExpression() : +{ + final SqlNode e; +} +{ + ( + e = IntervalLiteralOrExpression() + | + e = NonIntervalLiteral() + ) + { return e; } } /** Parses a unsigned numeric literal */ @@ -4416,6 +4449,53 @@ SqlLiteral IntervalLiteral() : } } +/** Parses an interval literal (e.g. {@code INTERVAL '2:3' HOUR TO MINUTE}) + * or an interval expression (e.g. {@code INTERVAL emp.empno MINUTE} + * or {@code INTERVAL 3 MONTHS}). */ +SqlNode IntervalLiteralOrExpression() : +{ + final String p; + final SqlIntervalQualifier intervalQualifier; + int sign = 1; + final Span s; + SqlNode e; +} +{ + { s = span(); } + [ + { sign = -1; } + | + { sign = 1; } + ] + ( + // literal (with quoted string) + { p = token.image; } + intervalQualifier = IntervalQualifier() { + return SqlParserUtil.parseIntervalLiteral(s.end(intervalQualifier), + sign, p, intervalQualifier); + } + | + // To keep parsing simple, any expressions besides numeric literal and + // identifiers must be enclosed in parentheses. + ( + + e = Expression(ExprContext.ACCEPT_SUB_QUERY) + + | + e = UnsignedNumericLiteral() + | + e = CompoundIdentifier() + ) + intervalQualifier = IntervalQualifierStart() { + if (sign == -1) { + e = SqlStdOperatorTable.UNARY_MINUS.createCall(e.getParserPosition(), e); + } + return SqlStdOperatorTable.INTERVAL.createCall(s.end(this), e, + intervalQualifier); + } + ) +} + TimeUnit Year() : { } @@ -4472,6 +4552,7 @@ TimeUnit Second() : SqlIntervalQualifier IntervalQualifier() : { + final Span s; final TimeUnit start; TimeUnit end = null; int startPrec = RelDataType.PRECISION_NOT_SPECIFIED; @@ -4479,27 +4560,28 @@ SqlIntervalQualifier IntervalQualifier() : } { ( - start = Year() [ startPrec = UnsignedIntLiteral() ] + start = Year() { s = span(); } startPrec = PrecisionOpt() [ LOOKAHEAD(2) end = Month() ] | - start = Month() [ startPrec = UnsignedIntLiteral() ] + start = Month() { s = span(); } startPrec = PrecisionOpt() | - start = Day() [ startPrec = UnsignedIntLiteral() ] - [ LOOKAHEAD(2) + start = Day() { s = span(); } startPrec = PrecisionOpt() + [ + LOOKAHEAD(2) ( end = Hour() | end = Minute() | - end = Second() - [ secondFracPrec = UnsignedIntLiteral() ] + end = Second() secondFracPrec = PrecisionOpt() ) ] | - start = Hour() [ startPrec = UnsignedIntLiteral() ] - [ LOOKAHEAD(2) + start = Hour() { s = span(); } startPrec = PrecisionOpt() + [ + LOOKAHEAD(2) ( end = Minute() | @@ -4508,26 +4590,54 @@ SqlIntervalQualifier IntervalQualifier() : ) ] | - start = Minute() [ startPrec = UnsignedIntLiteral() ] - [ LOOKAHEAD(2) - ( - end = Second() - [ secondFracPrec = UnsignedIntLiteral() ] - ) + start = Minute() { s = span(); } startPrec = PrecisionOpt() + [ + LOOKAHEAD(2) end = Second() + [ secondFracPrec = UnsignedIntLiteral() ] ] | - start = Second() + start = Second() { s = span(); } + [ + startPrec = UnsignedIntLiteral() + [ secondFracPrec = UnsignedIntLiteral() ] + + ] + ) + { + return new SqlIntervalQualifier(start, startPrec, end, secondFracPrec, + s.end(this)); + } +} + +/** Interval qualifier without 'TO unit'. */ +SqlIntervalQualifier IntervalQualifierStart() : +{ + final Span s; + final TimeUnit start; + int startPrec = RelDataType.PRECISION_NOT_SPECIFIED; + int secondFracPrec = RelDataType.PRECISION_NOT_SPECIFIED; +} +{ + ( + ( + start = Year() + | start = Month() + | start = Day() + | start = Hour() + | start = Minute() + ) + { s = span(); } + startPrec = PrecisionOpt() + | + start = Second() { s = span(); } [ startPrec = UnsignedIntLiteral() [ secondFracPrec = UnsignedIntLiteral() ] ] ) { - return new SqlIntervalQualifier(start, - startPrec, - end, - secondFracPrec, - getPos()); + return new SqlIntervalQualifier(start, startPrec, null, secondFracPrec, + s.end(this)); } } @@ -5260,7 +5370,6 @@ int PrecisionOpt() : int precision = -1; } { - LOOKAHEAD(2) precision = UnsignedIntLiteral() diff --git a/core/src/main/java/org/apache/calcite/sql/SqlDialect.java b/core/src/main/java/org/apache/calcite/sql/SqlDialect.java index 6c19c1a0e0e1..5dedfc865172 100644 --- a/core/src/main/java/org/apache/calcite/sql/SqlDialect.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlDialect.java @@ -526,7 +526,7 @@ public void unparseSqlIntervalQualifier(SqlWriter writer, public void unparseSqlIntervalLiteral(SqlWriter writer, SqlIntervalLiteral literal, int leftPrec, int rightPrec) { SqlIntervalLiteral.IntervalValue interval = - (SqlIntervalLiteral.IntervalValue) literal.getValue(); + literal.getValueAs(SqlIntervalLiteral.IntervalValue.class); writer.keyword("INTERVAL"); if (interval.getSign() == -1) { writer.print("-"); diff --git a/core/src/main/java/org/apache/calcite/sql/SqlKind.java b/core/src/main/java/org/apache/calcite/sql/SqlKind.java index adb32233f64c..1fee9bfdbdc5 100644 --- a/core/src/main/java/org/apache/calcite/sql/SqlKind.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlKind.java @@ -349,6 +349,9 @@ public enum SqlKind { /** {@code CASE} expression. */ CASE, + /** {@code INTERVAL} expression. */ + INTERVAL, + /** {@code NULLIF} operator. */ NULLIF, @@ -1066,7 +1069,7 @@ public enum SqlKind { FILTER, WITHIN_GROUP, IGNORE_NULLS, RESPECT_NULLS, DESCENDING, CUBE, ROLLUP, GROUPING_SETS, EXTEND, LATERAL, SELECT, JOIN, OTHER_FUNCTION, POSITION, CAST, TRIM, FLOOR, CEIL, - TIMESTAMP_ADD, TIMESTAMP_DIFF, EXTRACT, + TIMESTAMP_ADD, TIMESTAMP_DIFF, EXTRACT, INTERVAL, LITERAL_CHAIN, JDBC_FN, PRECEDING, FOLLOWING, ORDER_BY, NULLS_FIRST, NULLS_LAST, COLLECTION_TABLE, TABLESAMPLE, VALUES, WITH, WITH_ITEM, ITEM, SKIP_TO_FIRST, SKIP_TO_LAST, diff --git a/core/src/main/java/org/apache/calcite/sql/SqlLiteral.java b/core/src/main/java/org/apache/calcite/sql/SqlLiteral.java index bd80488aeeaf..7ce5fa184c34 100644 --- a/core/src/main/java/org/apache/calcite/sql/SqlLiteral.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlLiteral.java @@ -252,6 +252,20 @@ public Object getValue() { return value; } + /** + * Returns the value of this literal as a particular type. + * + *

The type might be the internal type, or other convenient types. + * For example, numeric literals' values are stored internally as + * {@link BigDecimal}, but other numeric types such as {@link Long} and + * {@link Double} are also allowed. + * + * @param clazz Desired value type + * @param Value type + * @return Value of the literal + * + * @throws AssertionError if the value type is not supported + */ public T getValueAs(Class clazz) { if (clazz.isInstance(value)) { return clazz.cast(value); @@ -320,6 +334,8 @@ public T getValueAs(Class clazz) { return clazz.cast(BigDecimal.valueOf(getValueAs(Long.class))); } else if (clazz == TimeUnitRange.class) { return clazz.cast(valMonth.getIntervalQualifier().timeUnitRange); + } else if (clazz == SqlIntervalQualifier.class) { + return clazz.cast(valMonth.getIntervalQualifier()); } break; case INTERVAL_DAY: @@ -341,6 +357,8 @@ public T getValueAs(Class clazz) { return clazz.cast(BigDecimal.valueOf(getValueAs(Long.class))); } else if (clazz == TimeUnitRange.class) { return clazz.cast(valTime.getIntervalQualifier().timeUnitRange); + } else if (clazz == SqlIntervalQualifier.class) { + return clazz.cast(valTime.getIntervalQualifier()); } break; } diff --git a/core/src/main/java/org/apache/calcite/sql/SqlNode.java b/core/src/main/java/org/apache/calcite/sql/SqlNode.java index ec69cae11c32..82bad7167840 100644 --- a/core/src/main/java/org/apache/calcite/sql/SqlNode.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlNode.java @@ -211,6 +211,17 @@ public abstract void unparse( int leftPrec, int rightPrec); + public void unparseWithParentheses(SqlWriter writer, int leftPrec, + int rightPrec, boolean parentheses) { + if (parentheses) { + final SqlWriter.Frame frame = writer.startList("(", ")"); + unparse(writer, 0, 0); + writer.endList(frame); + } else { + unparse(writer, leftPrec, rightPrec); + } + } + public SqlParserPos getParserPosition() { return pos; } diff --git a/core/src/main/java/org/apache/calcite/sql/dialect/BigQuerySqlDialect.java b/core/src/main/java/org/apache/calcite/sql/dialect/BigQuerySqlDialect.java index af2e855789bf..19276b1b9541 100644 --- a/core/src/main/java/org/apache/calcite/sql/dialect/BigQuerySqlDialect.java +++ b/core/src/main/java/org/apache/calcite/sql/dialect/BigQuerySqlDialect.java @@ -164,10 +164,10 @@ public BigQuerySqlDialect(SqlDialect.Context context) { } /** BigQuery interval syntax: INTERVAL int64 time_unit. */ - @Override public void unparseSqlIntervalLiteral( - SqlWriter writer, SqlIntervalLiteral literal, int leftPrec, int rightPrec) { + @Override public void unparseSqlIntervalLiteral(SqlWriter writer, + SqlIntervalLiteral literal, int leftPrec, int rightPrec) { SqlIntervalLiteral.IntervalValue interval = - (SqlIntervalLiteral.IntervalValue) literal.getValue(); + literal.getValueAs(SqlIntervalLiteral.IntervalValue.class); writer.keyword("INTERVAL"); if (interval.getSign() == -1) { writer.print("-"); diff --git a/core/src/main/java/org/apache/calcite/sql/dialect/Db2SqlDialect.java b/core/src/main/java/org/apache/calcite/sql/dialect/Db2SqlDialect.java index 27994af232ea..9dbbda032a2a 100644 --- a/core/src/main/java/org/apache/calcite/sql/dialect/Db2SqlDialect.java +++ b/core/src/main/java/org/apache/calcite/sql/dialect/Db2SqlDialect.java @@ -84,7 +84,7 @@ public Db2SqlDialect(Context context) { // If one operand is a timestamp, the other operand can be any of teh duration. SqlIntervalLiteral.IntervalValue interval = - (SqlIntervalLiteral.IntervalValue) literal.getValue(); + literal.getValueAs(SqlIntervalLiteral.IntervalValue.class); if (interval.getSign() == -1) { writer.print("-"); } diff --git a/core/src/main/java/org/apache/calcite/sql/dialect/MssqlSqlDialect.java b/core/src/main/java/org/apache/calcite/sql/dialect/MssqlSqlDialect.java index 547f9a9dd893..f0051751f53c 100644 --- a/core/src/main/java/org/apache/calcite/sql/dialect/MssqlSqlDialect.java +++ b/core/src/main/java/org/apache/calcite/sql/dialect/MssqlSqlDialect.java @@ -270,7 +270,7 @@ private void unparseFloor(SqlWriter writer, SqlCall call) { private void unparseSqlIntervalLiteralMssql( SqlWriter writer, SqlIntervalLiteral literal, int sign) { final SqlIntervalLiteral.IntervalValue interval = - (SqlIntervalLiteral.IntervalValue) literal.getValue(); + literal.getValueAs(SqlIntervalLiteral.IntervalValue.class); unparseSqlIntervalQualifier(writer, interval.getIntervalQualifier(), RelDataTypeSystem.DEFAULT); writer.sep(",", true); diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlIntervalOperator.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlIntervalOperator.java new file mode 100644 index 000000000000..e959b401d6ad --- /dev/null +++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlIntervalOperator.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.calcite.sql.fun; + +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.sql.SqlCall; +import org.apache.calcite.sql.SqlIdentifier; +import org.apache.calcite.sql.SqlInternalOperator; +import org.apache.calcite.sql.SqlIntervalQualifier; +import org.apache.calcite.sql.SqlKind; +import org.apache.calcite.sql.SqlLiteral; +import org.apache.calcite.sql.SqlNode; +import org.apache.calcite.sql.SqlOperatorBinding; +import org.apache.calcite.sql.SqlWriter; +import org.apache.calcite.sql.type.InferTypes; +import org.apache.calcite.sql.type.OperandTypes; +import org.apache.calcite.sql.type.SqlReturnTypeInference; +import org.apache.calcite.sql.type.SqlTypeTransforms; + +/** Interval expression. + * + *

Syntax: + * + *

INTERVAL numericExpression timeUnit
+ *
+ * timeUnit: YEAR | MONTH | DAY | HOUR | MINUTE | SECOND
+ * + *

Compare with interval literal, whose syntax is + * {@code INTERVAL characterLiteral timeUnit [ TO timeUnit ]}. + */ +public class SqlIntervalOperator extends SqlInternalOperator { + private static final SqlReturnTypeInference RETURN_TYPE = + ((SqlReturnTypeInference) SqlIntervalOperator::returnType) + .andThen(SqlTypeTransforms.TO_NULLABLE); + + SqlIntervalOperator() { + super("INTERVAL", SqlKind.INTERVAL, 0, true, RETURN_TYPE, + InferTypes.ANY_NULLABLE, OperandTypes.NUMERIC_INTERVAL); + } + + private static RelDataType returnType(SqlOperatorBinding opBinding) { + final SqlIntervalQualifier intervalQualifier = + opBinding.getOperandLiteralValue(1, SqlIntervalQualifier.class); + return opBinding.getTypeFactory().createSqlIntervalType(intervalQualifier); + } + + @Override public void unparse(SqlWriter writer, SqlCall call, int leftPrec, + int rightPrec) { + writer.keyword("INTERVAL"); + final SqlNode expression = call.operand(0); + final SqlIntervalQualifier intervalQualifier = call.operand(1); + expression.unparseWithParentheses(writer, leftPrec, rightPrec, + !(expression instanceof SqlLiteral + || expression instanceof SqlIdentifier + || expression.getKind() == SqlKind.MINUS_PREFIX + || writer.isAlwaysUseParentheses())); + assert intervalQualifier.timeUnitRange.endUnit == null; + intervalQualifier.unparse(writer, 0, 0); + } + + @Override public String getSignatureTemplate(int operandsCount) { + switch (operandsCount) { + case 2: + return "{0} {1} {2}"; // e.g. "INTERVAL " + default: + throw new AssertionError(); + } + } +} diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java index 46f2208b7489..77cff5013040 100644 --- a/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java +++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java @@ -547,6 +547,12 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable { public static final SqlSpecialOperator DATETIME_PLUS = new SqlDatetimePlusOperator(); + /** + * Interval expression, 'INTERVAL n timeUnit'. + */ + public static final SqlSpecialOperator INTERVAL = + new SqlIntervalOperator(); + /** * Multiset {@code MEMBER OF}, which returns whether a element belongs to a * multiset. diff --git a/core/src/main/java/org/apache/calcite/sql/type/SqlOperandTypeChecker.java b/core/src/main/java/org/apache/calcite/sql/type/SqlOperandTypeChecker.java index 6e3452cb9591..7f15371789d9 100644 --- a/core/src/main/java/org/apache/calcite/sql/type/SqlOperandTypeChecker.java +++ b/core/src/main/java/org/apache/calcite/sql/type/SqlOperandTypeChecker.java @@ -25,6 +25,8 @@ * *

This interface is an example of the * {@link org.apache.calcite.util.Glossary#STRATEGY_PATTERN strategy pattern}. + * + * @see OperandTypes */ public interface SqlOperandTypeChecker { //~ Methods ---------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/sql/type/SqlOperandTypeInference.java b/core/src/main/java/org/apache/calcite/sql/type/SqlOperandTypeInference.java index 7091ebbd9f61..e27524c07f6f 100644 --- a/core/src/main/java/org/apache/calcite/sql/type/SqlOperandTypeInference.java +++ b/core/src/main/java/org/apache/calcite/sql/type/SqlOperandTypeInference.java @@ -21,6 +21,8 @@ /** * Strategy to infer unknown types of the operands of an operator call. + * + * @see InferTypes */ public interface SqlOperandTypeInference { //~ Methods ---------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/sql/type/SqlReturnTypeInference.java b/core/src/main/java/org/apache/calcite/sql/type/SqlReturnTypeInference.java index c9e31d26787c..16a80ea37091 100644 --- a/core/src/main/java/org/apache/calcite/sql/type/SqlReturnTypeInference.java +++ b/core/src/main/java/org/apache/calcite/sql/type/SqlReturnTypeInference.java @@ -28,7 +28,9 @@ * {@link org.apache.calcite.util.Glossary#STRATEGY_PATTERN strategy pattern}. * This makes * sense because many operators have similar, straightforward strategies, such - * as to take the type of the first operand.

+ * as to take the type of the first operand. + * + * @see ReturnTypes */ @FunctionalInterface public interface SqlReturnTypeInference { diff --git a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeTransform.java b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeTransform.java index 6bf80b277151..ffdd96812df3 100644 --- a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeTransform.java +++ b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeTransform.java @@ -27,6 +27,8 @@ * *

This class is an example of the * {@link org.apache.calcite.util.Glossary#STRATEGY_PATTERN strategy pattern}. + * + * @see SqlTypeTransforms */ public interface SqlTypeTransform { //~ Methods ---------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java index bf59a4fdd589..a81f3591ee7b 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java @@ -3088,8 +3088,7 @@ public void validateLiteral(SqlLiteral literal) { case INTERVAL_SECOND: if (literal instanceof SqlIntervalLiteral) { SqlIntervalLiteral.IntervalValue interval = - (SqlIntervalLiteral.IntervalValue) - literal.getValue(); + literal.getValueAs(SqlIntervalLiteral.IntervalValue.class); SqlIntervalQualifier intervalQualifier = interval.getIntervalQualifier(); diff --git a/core/src/main/java/org/apache/calcite/sql2rel/ReflectiveConvertletTable.java b/core/src/main/java/org/apache/calcite/sql2rel/ReflectiveConvertletTable.java index d8243c8d12d5..ffa05f006cc7 100644 --- a/core/src/main/java/org/apache/calcite/sql2rel/ReflectiveConvertletTable.java +++ b/core/src/main/java/org/apache/calcite/sql2rel/ReflectiveConvertletTable.java @@ -134,7 +134,7 @@ public SqlRexConvertlet get(SqlCall call) { final SqlOperator op = call.getOperator(); // Is there a convertlet for this operator - // (e.g. SqlStdOperatorTable.plusOperator)? + // (e.g. SqlStdOperatorTable.PLUS)? convertlet = (SqlRexConvertlet) map.get(op); if (convertlet != null) { return convertlet; diff --git a/core/src/main/java/org/apache/calcite/sql2rel/SqlNodeToRexConverterImpl.java b/core/src/main/java/org/apache/calcite/sql2rel/SqlNodeToRexConverterImpl.java index a6b124a57483..c6104c46bf2a 100644 --- a/core/src/main/java/org/apache/calcite/sql2rel/SqlNodeToRexConverterImpl.java +++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlNodeToRexConverterImpl.java @@ -23,7 +23,6 @@ import org.apache.calcite.rex.RexLiteral; import org.apache.calcite.rex.RexNode; import org.apache.calcite.sql.SqlCall; -import org.apache.calcite.sql.SqlIntervalLiteral; import org.apache.calcite.sql.SqlIntervalQualifier; import org.apache.calcite.sql.SqlLiteral; import org.apache.calcite.sql.SqlTimeLiteral; @@ -95,10 +94,7 @@ public RexNode convertLiteral( return rexBuilder.makeNullLiteral(type); } - BitString bitString; - SqlIntervalLiteral.IntervalValue intervalValue; - long l; - + final BitString bitString; switch (literal.getTypeName()) { case DECIMAL: // exact number @@ -152,8 +148,7 @@ public RexNode convertLiteral( case INTERVAL_MINUTE_SECOND: case INTERVAL_SECOND: SqlIntervalQualifier sqlIntervalQualifier = - literal.getValueAs(SqlIntervalLiteral.IntervalValue.class) - .getIntervalQualifier(); + literal.getValueAs(SqlIntervalQualifier.class); return rexBuilder.makeIntervalLiteral( literal.getValueAs(BigDecimal.class), sqlIntervalQualifier); 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 aaae32e08217..d1c9bd9cd2d3 100644 --- a/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java +++ b/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java @@ -259,6 +259,9 @@ private StandardConvertletTable() { registerOp(SqlStdOperatorTable.TIMESTAMP_DIFF, new TimestampDiffConvertlet()); + registerOp(SqlStdOperatorTable.INTERVAL, + StandardConvertletTable::convertInterval); + // Convert "element()" to "$element_slice()", if the // expression is a multiset of scalars. if (false) { @@ -294,6 +297,21 @@ private StandardConvertletTable() { } } + /** Converts an interval expression to a numeric multiplied by an interval + * literal. */ + private static RexNode convertInterval(SqlRexContext cx, SqlCall call) { + // "INTERVAL n HOUR" becomes "n * INTERVAL '1' HOUR" + final SqlNode n = call.operand(0); + final SqlIntervalQualifier intervalQualifier = call.operand(1); + final SqlIntervalLiteral literal = + SqlLiteral.createInterval(1, "1", intervalQualifier, + call.getParserPosition()); + final SqlCall multiply = + SqlStdOperatorTable.MULTIPLY.createCall(call.getParserPosition(), n, + literal); + return cx.convertExpression(multiply); + } + //~ Methods ---------------------------------------------------------------- private RexNode or(RexBuilder rexBuilder, RexNode a0, RexNode a1) { @@ -546,7 +564,7 @@ protected RexNode convertFloorCeil(SqlRexContext cx, SqlCall call) { && call.operand(0) instanceof SqlIntervalLiteral) { final SqlIntervalLiteral literal = call.operand(0); SqlIntervalLiteral.IntervalValue interval = - (SqlIntervalLiteral.IntervalValue) literal.getValue(); + literal.getValueAs(SqlIntervalLiteral.IntervalValue.class); BigDecimal val = interval.getIntervalQualifier().getStartUnit().multiplier; RexNode rexInterval = cx.convertExpression(literal); diff --git a/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java b/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java index 4fa345117eb7..3c4bf76290ba 100644 --- a/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java +++ b/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java @@ -7096,6 +7096,26 @@ public void subTestIntervalSecondFailsValidation() { .ok("INTERVAL '1:x:2' HOUR TO SECOND"); } + @Test void testIntervalExpression() { + expr("interval 0 day").ok("INTERVAL 0 DAY"); + expr("interval 0 days").ok("INTERVAL 0 DAY"); + expr("interval -10 days").ok("INTERVAL (- 10) DAY"); + expr("interval -10 days").ok("INTERVAL (- 10) DAY"); + // parser requires parentheses for expressions other than numeric + // literal or identifier + expr("interval 1 ^+^ x.y days") + .fails("(?s)Encountered \"\\+\" at .*"); + expr("interval (1 + x.y) days") + .ok("INTERVAL (1 + `X`.`Y`) DAY"); + expr("interval -x second(3)") + .ok("INTERVAL (- `X`) SECOND(3)"); + expr("interval -x.y second(3)") + .ok("INTERVAL (- `X`.`Y`) SECOND(3)"); + expr("interval 1 day ^to^ hour") + .fails("(?s)Encountered \"to\" at .*"); + expr("interval '1 1' day to hour").ok("INTERVAL '1 1' DAY TO HOUR"); + } + @Test void testIntervalOperators() { expr("-interval '1' day") .ok("(- INTERVAL '1' DAY)"); diff --git a/core/src/test/java/org/apache/calcite/sql/test/AbstractSqlTester.java b/core/src/test/java/org/apache/calcite/sql/test/AbstractSqlTester.java index a858170dadf7..4a6bbf1f2449 100644 --- a/core/src/test/java/org/apache/calcite/sql/test/AbstractSqlTester.java +++ b/core/src/test/java/org/apache/calcite/sql/test/AbstractSqlTester.java @@ -234,7 +234,7 @@ public void checkIntervalConv(String sql, String expected) { assertNotNull(node); SqlIntervalLiteral intervalLiteral = (SqlIntervalLiteral) node; SqlIntervalLiteral.IntervalValue interval = - (SqlIntervalLiteral.IntervalValue) intervalLiteral.getValue(); + intervalLiteral.getValueAs(SqlIntervalLiteral.IntervalValue.class); long l = interval.getIntervalQualifier().isYearMonth() ? SqlParserUtil.intervalToMonths(interval) 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 e05ef0797a41..4bb6ed06f7ba 100644 --- a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java +++ b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java @@ -119,6 +119,10 @@ public final Sql sql(String sql) { sql(sql).ok(); } + @Test void testIntervalExpression() { + sql("select interval mgr hour as h from emp").ok(); + } + @Test void testAliasList() { final String sql = "select a + b from (\n" + " select deptno, 1 as uno, name from dept\n" 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 48c2958749da..12cdca6c0128 100644 --- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java +++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java @@ -92,7 +92,7 @@ * {@link org.apache.calcite.sql.test.SqlTester}. */ @LocaleEnUs -class SqlValidatorTest extends SqlValidatorTestCase { +public class SqlValidatorTest extends SqlValidatorTestCase { //~ Static fields/initializers --------------------------------------------- // CHECKSTYLE: IGNORE 1 @@ -1550,7 +1550,7 @@ public void _testLikeAndSimilarFails() { .fails("(?s).*Function '.fn HAHAHA.' is not defined.*"); } - @Test void testQuotedFunction() { + @Test public void testQuotedFunction() { if (false) { // REVIEW jvs 2-Feb-2005: I am disabling this test because I // removed the corresponding support from the parser. Where in the @@ -1822,7 +1822,7 @@ public void _testLikeAndSimilarFails() { * Similarly, any changes to tests here should be echoed appropriately to * each of the other 12 subTestIntervalXXXPositive() tests. */ - public void subTestIntervalYearPositive() { + void subTestIntervalYearPositive() { // default precision expr("INTERVAL '1' YEAR") .columnType("INTERVAL YEAR NOT NULL"); @@ -1873,7 +1873,7 @@ public void subTestIntervalYearPositive() { * Similarly, any changes to tests here should be echoed appropriately to * each of the other 12 subTestIntervalXXXPositive() tests. */ - public void subTestIntervalYearToMonthPositive() { + void subTestIntervalYearToMonthPositive() { // default precision expr("INTERVAL '1-2' YEAR TO MONTH") .columnType("INTERVAL YEAR TO MONTH NOT NULL"); @@ -1928,7 +1928,7 @@ public void subTestIntervalYearToMonthPositive() { * Similarly, any changes to tests here should be echoed appropriately to * each of the other 12 subTestIntervalXXXPositive() tests. */ - public void subTestIntervalMonthPositive() { + void subTestIntervalMonthPositive() { // default precision expr("INTERVAL '1' MONTH") .columnType("INTERVAL MONTH NOT NULL"); @@ -1979,7 +1979,7 @@ public void subTestIntervalMonthPositive() { * Similarly, any changes to tests here should be echoed appropriately to * each of the other 12 subTestIntervalXXXPositive() tests. */ - public void subTestIntervalDayPositive() { + void subTestIntervalDayPositive() { // default precision expr("INTERVAL '1' DAY") .columnType("INTERVAL DAY NOT NULL"); @@ -2023,7 +2023,7 @@ public void subTestIntervalDayPositive() { .columnType("INTERVAL DAY NOT NULL"); } - public void subTestIntervalDayToHourPositive() { + void subTestIntervalDayToHourPositive() { // default precision expr("INTERVAL '1 2' DAY TO HOUR") .columnType("INTERVAL DAY TO HOUR NOT NULL"); @@ -2078,7 +2078,7 @@ public void subTestIntervalDayToHourPositive() { * Similarly, any changes to tests here should be echoed appropriately to * each of the other 12 subTestIntervalXXXPositive() tests. */ - public void subTestIntervalDayToMinutePositive() { + void subTestIntervalDayToMinutePositive() { // default precision expr("INTERVAL '1 2:3' DAY TO MINUTE") .columnType("INTERVAL DAY TO MINUTE NOT NULL"); @@ -2133,7 +2133,7 @@ public void subTestIntervalDayToMinutePositive() { * Similarly, any changes to tests here should be echoed appropriately to * each of the other 12 subTestIntervalXXXPositive() tests. */ - public void subTestIntervalDayToSecondPositive() { + void subTestIntervalDayToSecondPositive() { // default precision expr("INTERVAL '1 2:3:4' DAY TO SECOND") .columnType("INTERVAL DAY TO SECOND NOT NULL"); @@ -2202,7 +2202,7 @@ public void subTestIntervalDayToSecondPositive() { * Similarly, any changes to tests here should be echoed appropriately to * each of the other 12 subTestIntervalXXXPositive() tests. */ - public void subTestIntervalHourPositive() { + void subTestIntervalHourPositive() { // default precision expr("INTERVAL '1' HOUR") .columnType("INTERVAL HOUR NOT NULL"); @@ -2253,7 +2253,7 @@ public void subTestIntervalHourPositive() { * Similarly, any changes to tests here should be echoed appropriately to * each of the other 12 subTestIntervalXXXPositive() tests. */ - public void subTestIntervalHourToMinutePositive() { + void subTestIntervalHourToMinutePositive() { // default precision expr("INTERVAL '2:3' HOUR TO MINUTE") .columnType("INTERVAL HOUR TO MINUTE NOT NULL"); @@ -2308,7 +2308,7 @@ public void subTestIntervalHourToMinutePositive() { * Similarly, any changes to tests here should be echoed appropriately to * each of the other 12 subTestIntervalXXXPositive() tests. */ - public void subTestIntervalHourToSecondPositive() { + void subTestIntervalHourToSecondPositive() { // default precision expr("INTERVAL '2:3:4' HOUR TO SECOND") .columnType("INTERVAL HOUR TO SECOND NOT NULL"); @@ -2377,7 +2377,7 @@ public void subTestIntervalHourToSecondPositive() { * Similarly, any changes to tests here should be echoed appropriately to * each of the other 12 subTestIntervalXXXPositive() tests. */ - public void subTestIntervalMinutePositive() { + void subTestIntervalMinutePositive() { // default precision expr("INTERVAL '1' MINUTE") .columnType("INTERVAL MINUTE NOT NULL"); @@ -2428,7 +2428,7 @@ public void subTestIntervalMinutePositive() { * Similarly, any changes to tests here should be echoed appropriately to * each of the other 12 subTestIntervalXXXPositive() tests. */ - public void subTestIntervalMinuteToSecondPositive() { + void subTestIntervalMinuteToSecondPositive() { // default precision expr("INTERVAL '2:4' MINUTE TO SECOND") .columnType("INTERVAL MINUTE TO SECOND NOT NULL"); @@ -2497,7 +2497,7 @@ public void subTestIntervalMinuteToSecondPositive() { * Similarly, any changes to tests here should be echoed appropriately to * each of the other 12 subTestIntervalXXXPositive() tests. */ - public void subTestIntervalSecondPositive() { + void subTestIntervalSecondPositive() { // default precision expr("INTERVAL '1' SECOND") .columnType("INTERVAL SECOND NOT NULL"); @@ -2558,7 +2558,7 @@ public void subTestIntervalSecondPositive() { * Similarly, any changes to tests here should be echoed appropriately to * each of the other 12 subTestIntervalXXXNegative() tests. */ - public void subTestIntervalYearNegative() { + void subTestIntervalYearNegative() { // Qualifier - field mismatches wholeExpr("INTERVAL '-' YEAR") .fails("Illegal interval literal format '-' for INTERVAL YEAR.*"); @@ -2595,14 +2595,14 @@ public void subTestIntervalYearNegative() { + "YEAR\\(10\\) field"); // precision > maximum - expr("INTERVAL '1' YEAR(11^)^") + expr("INTERVAL '1' ^YEAR(11)^") .fails("Interval leading field precision '11' out of range for " + "INTERVAL YEAR\\(11\\)"); // precision < minimum allowed) // note: parser will catch negative values, here we // just need to check for 0 - expr("INTERVAL '0' YEAR(0^)^") + expr("INTERVAL '0' ^YEAR(0)^") .fails("Interval leading field precision '0' out of range for " + "INTERVAL YEAR\\(0\\)"); } @@ -2614,7 +2614,7 @@ public void subTestIntervalYearNegative() { * Similarly, any changes to tests here should be echoed appropriately to * each of the other 12 subTestIntervalXXXNegative() tests. */ - public void subTestIntervalYearToMonthNegative() { + void subTestIntervalYearToMonthNegative() { // Qualifier - field mismatches wholeExpr("INTERVAL '-' YEAR TO MONTH") .fails("Illegal interval literal format '-' for INTERVAL YEAR TO MONTH"); @@ -2660,14 +2660,14 @@ public void subTestIntervalYearToMonthNegative() { .fails("Illegal interval literal format '1-12' for INTERVAL YEAR TO MONTH.*"); // precision > maximum - expr("INTERVAL '1-1' YEAR(11) TO ^MONTH^") + expr("INTERVAL '1-1' ^YEAR(11) TO MONTH^") .fails("Interval leading field precision '11' out of range for " + "INTERVAL YEAR\\(11\\) TO MONTH"); // precision < minimum allowed) // note: parser will catch negative values, here we // just need to check for 0 - expr("INTERVAL '0-0' YEAR(0) TO ^MONTH^") + expr("INTERVAL '0-0' ^YEAR(0) TO MONTH^") .fails("Interval leading field precision '0' out of range for " + "INTERVAL YEAR\\(0\\) TO MONTH"); } @@ -2679,7 +2679,7 @@ public void subTestIntervalYearToMonthNegative() { * Similarly, any changes to tests here should be echoed appropriately to * each of the other 12 subTestIntervalXXXNegative() tests. */ - public void subTestIntervalMonthNegative() { + void subTestIntervalMonthNegative() { // Qualifier - field mismatches wholeExpr("INTERVAL '-' MONTH") .fails("Illegal interval literal format '-' for INTERVAL MONTH.*"); @@ -2714,14 +2714,14 @@ public void subTestIntervalMonthNegative() { .fails("Interval field value -2,147,483,648 exceeds precision of MONTH\\(10\\) field.*"); // precision > maximum - expr("INTERVAL '1' MONTH(11^)^") + expr("INTERVAL '1' ^MONTH(11)^") .fails("Interval leading field precision '11' out of range for " + "INTERVAL MONTH\\(11\\)"); // precision < minimum allowed) // note: parser will catch negative values, here we // just need to check for 0 - expr("INTERVAL '0' MONTH(0^)^") + expr("INTERVAL '0' ^MONTH(0)^") .fails("Interval leading field precision '0' out of range for " + "INTERVAL MONTH\\(0\\)"); } @@ -2733,7 +2733,7 @@ public void subTestIntervalMonthNegative() { * Similarly, any changes to tests here should be echoed appropriately to * each of the other 12 subTestIntervalXXXNegative() tests. */ - public void subTestIntervalDayNegative() { + void subTestIntervalDayNegative() { // Qualifier - field mismatches wholeExpr("INTERVAL '-' DAY") .fails("Illegal interval literal format '-' for INTERVAL DAY.*"); @@ -2772,14 +2772,14 @@ public void subTestIntervalDayNegative() { + "DAY\\(10\\) field.*"); // precision > maximum - expr("INTERVAL '1' DAY(11^)^") + expr("INTERVAL '1' ^DAY(11)^") .fails("Interval leading field precision '11' out of range for " + "INTERVAL DAY\\(11\\)"); // precision < minimum allowed) // note: parser will catch negative values, here we // just need to check for 0 - expr("INTERVAL '0' DAY(0^)^") + expr("INTERVAL '0' ^DAY(0)^") .fails("Interval leading field precision '0' out of range for " + "INTERVAL DAY\\(0\\)"); } @@ -2791,7 +2791,7 @@ public void subTestIntervalDayNegative() { * Similarly, any changes to tests here should be echoed appropriately to * each of the other 12 subTestIntervalXXXNegative() tests. */ - public void subTestIntervalDayToHourNegative() { + void subTestIntervalDayToHourNegative() { // Qualifier - field mismatches wholeExpr("INTERVAL '-' DAY TO HOUR") .fails("Illegal interval literal format '-' for INTERVAL DAY TO HOUR"); @@ -2838,14 +2838,14 @@ public void subTestIntervalDayToHourNegative() { .fails("Illegal interval literal format '1 24' for INTERVAL DAY TO HOUR.*"); // precision > maximum - expr("INTERVAL '1 1' DAY(11) TO ^HOUR^") + expr("INTERVAL '1 1' ^DAY(11) TO HOUR^") .fails("Interval leading field precision '11' out of range for " + "INTERVAL DAY\\(11\\) TO HOUR"); // precision < minimum allowed) // note: parser will catch negative values, here we // just need to check for 0 - expr("INTERVAL '0 0' DAY(0) TO ^HOUR^") + expr("INTERVAL '0 0' ^DAY(0) TO HOUR^") .fails("Interval leading field precision '0' out of range for INTERVAL DAY\\(0\\) TO HOUR"); } @@ -2856,7 +2856,7 @@ public void subTestIntervalDayToHourNegative() { * Similarly, any changes to tests here should be echoed appropriately to * each of the other 12 subTestIntervalXXXNegative() tests. */ - public void subTestIntervalDayToMinuteNegative() { + void subTestIntervalDayToMinuteNegative() { // Qualifier - field mismatches wholeExpr("INTERVAL ' :' DAY TO MINUTE") .fails("Illegal interval literal format ' :' for INTERVAL DAY TO MINUTE"); @@ -2920,14 +2920,14 @@ public void subTestIntervalDayToMinuteNegative() { .fails("Illegal interval literal format '1 1:60' for INTERVAL DAY TO MINUTE.*"); // precision > maximum - expr("INTERVAL '1 1:1' DAY(11) TO ^MINUTE^") + expr("INTERVAL '1 1:1' ^DAY(11) TO MINUTE^") .fails("Interval leading field precision '11' out of range for " + "INTERVAL DAY\\(11\\) TO MINUTE"); // precision < minimum allowed) // note: parser will catch negative values, here we // just need to check for 0 - expr("INTERVAL '0 0' DAY(0) TO ^MINUTE^") + expr("INTERVAL '0 0' ^DAY(0) TO MINUTE^") .fails("Interval leading field precision '0' out of range for " + "INTERVAL DAY\\(0\\) TO MINUTE"); } @@ -2939,7 +2939,7 @@ public void subTestIntervalDayToMinuteNegative() { * Similarly, any changes to tests here should be echoed appropriately to * each of the other 12 subTestIntervalXXXNegative() tests. */ - public void subTestIntervalDayToSecondNegative() { + void subTestIntervalDayToSecondNegative() { // Qualifier - field mismatches wholeExpr("INTERVAL ' ::' DAY TO SECOND") .fails("Illegal interval literal format ' ::' for INTERVAL DAY TO SECOND"); @@ -3041,20 +3041,20 @@ public void subTestIntervalDayToSecondNegative() { + "INTERVAL DAY TO SECOND\\(3\\).*"); // precision > maximum - expr("INTERVAL '1 1' DAY(11) TO ^SECOND^") + expr("INTERVAL '1 1' ^DAY(11) TO SECOND^") .fails("Interval leading field precision '11' out of range for " + "INTERVAL DAY\\(11\\) TO SECOND"); - expr("INTERVAL '1 1' DAY TO SECOND(10^)^") + expr("INTERVAL '1 1' ^DAY TO SECOND(10)^") .fails("Interval fractional second precision '10' out of range for " + "INTERVAL DAY TO SECOND\\(10\\)"); // precision < minimum allowed) // note: parser will catch negative values, here we // just need to check for 0 - expr("INTERVAL '0 0:0:0' DAY(0) TO ^SECOND^") + expr("INTERVAL '0 0:0:0' ^DAY(0) TO SECOND^") .fails("Interval leading field precision '0' out of range for " + "INTERVAL DAY\\(0\\) TO SECOND"); - expr("INTERVAL '0 0:0:0' DAY TO SECOND(0^)^") + expr("INTERVAL '0 0:0:0' ^DAY TO SECOND(0)^") .fails("Interval fractional second precision '0' out of range for " + "INTERVAL DAY TO SECOND\\(0\\)"); } @@ -3066,7 +3066,7 @@ public void subTestIntervalDayToSecondNegative() { * Similarly, any changes to tests here should be echoed appropriately to * each of the other 12 subTestIntervalXXXNegative() tests. */ - public void subTestIntervalHourNegative() { + void subTestIntervalHourNegative() { // Qualifier - field mismatches wholeExpr("INTERVAL '-' HOUR") .fails("Illegal interval literal format '-' for INTERVAL HOUR.*"); @@ -3110,14 +3110,14 @@ public void subTestIntervalHourNegative() { + "HOUR\\(10\\) field.*"); // precision > maximum - expr("INTERVAL '1' HOUR(11^)^") + expr("INTERVAL '1' ^HOUR(11)^") .fails("Interval leading field precision '11' out of range for " + "INTERVAL HOUR\\(11\\)"); // precision < minimum allowed) // note: parser will catch negative values, here we // just need to check for 0 - expr("INTERVAL '0' HOUR(0^)^") + expr("INTERVAL '0' ^HOUR(0)^") .fails("Interval leading field precision '0' out of range for " + "INTERVAL HOUR\\(0\\)"); } @@ -3129,7 +3129,7 @@ public void subTestIntervalHourNegative() { * Similarly, any changes to tests here should be echoed appropriately to * each of the other 12 subTestIntervalXXXNegative() tests. */ - public void subTestIntervalHourToMinuteNegative() { + void subTestIntervalHourToMinuteNegative() { // Qualifier - field mismatches wholeExpr("INTERVAL ':' HOUR TO MINUTE") .fails("Illegal interval literal format ':' for INTERVAL HOUR TO MINUTE"); @@ -3175,14 +3175,14 @@ public void subTestIntervalHourToMinuteNegative() { .fails("Illegal interval literal format '1:60' for INTERVAL HOUR TO MINUTE.*"); // precision > maximum - expr("INTERVAL '1:1' HOUR(11) TO ^MINUTE^") + expr("INTERVAL '1:1' ^HOUR(11) TO MINUTE^") .fails("Interval leading field precision '11' out of range for " + "INTERVAL HOUR\\(11\\) TO MINUTE"); // precision < minimum allowed) // note: parser will catch negative values, here we // just need to check for 0 - expr("INTERVAL '0:0' HOUR(0) TO ^MINUTE^") + expr("INTERVAL '0:0' ^HOUR(0) TO MINUTE^") .fails("Interval leading field precision '0' out of range for " + "INTERVAL HOUR\\(0\\) TO MINUTE"); } @@ -3194,7 +3194,7 @@ public void subTestIntervalHourToMinuteNegative() { * Similarly, any changes to tests here should be echoed appropriately to * each of the other 12 subTestIntervalXXXNegative() tests. */ - public void subTestIntervalHourToSecondNegative() { + void subTestIntervalHourToSecondNegative() { // Qualifier - field mismatches wholeExpr("INTERVAL '::' HOUR TO SECOND") .fails("Illegal interval literal format '::' for INTERVAL HOUR TO SECOND"); @@ -3270,20 +3270,20 @@ public void subTestIntervalHourToSecondNegative() { + "INTERVAL HOUR TO SECOND\\(3\\).*"); // precision > maximum - expr("INTERVAL '1:1:1' HOUR(11) TO ^SECOND^") + expr("INTERVAL '1:1:1' ^HOUR(11) TO SECOND^") .fails("Interval leading field precision '11' out of range for " + "INTERVAL HOUR\\(11\\) TO SECOND"); - expr("INTERVAL '1:1:1' HOUR TO SECOND(10^)^") + expr("INTERVAL '1:1:1' ^HOUR TO SECOND(10)^") .fails("Interval fractional second precision '10' out of range for " + "INTERVAL HOUR TO SECOND\\(10\\)"); // precision < minimum allowed) // note: parser will catch negative values, here we // just need to check for 0 - expr("INTERVAL '0:0:0' HOUR(0) TO ^SECOND^") + expr("INTERVAL '0:0:0' ^HOUR(0) TO SECOND^") .fails("Interval leading field precision '0' out of range for " + "INTERVAL HOUR\\(0\\) TO SECOND"); - expr("INTERVAL '0:0:0' HOUR TO SECOND(0^)^") + expr("INTERVAL '0:0:0' ^HOUR TO SECOND(0)^") .fails("Interval fractional second precision '0' out of range for " + "INTERVAL HOUR TO SECOND\\(0\\)"); } @@ -3295,7 +3295,7 @@ public void subTestIntervalHourToSecondNegative() { * Similarly, any changes to tests here should be echoed appropriately to * each of the other 12 subTestIntervalXXXNegative() tests. */ - public void subTestIntervalMinuteNegative() { + void subTestIntervalMinuteNegative() { // Qualifier - field mismatches wholeExpr("INTERVAL '-' MINUTE") .fails("Illegal interval literal format '-' for INTERVAL MINUTE.*"); @@ -3332,14 +3332,14 @@ public void subTestIntervalMinuteNegative() { .fails("Interval field value -2,147,483,648 exceeds precision of MINUTE\\(10\\) field.*"); // precision > maximum - expr("INTERVAL '1' MINUTE(11^)^") + expr("INTERVAL '1' ^MINUTE(11)^") .fails("Interval leading field precision '11' out of range for " + "INTERVAL MINUTE\\(11\\)"); // precision < minimum allowed) // note: parser will catch negative values, here we // just need to check for 0 - expr("INTERVAL '0' MINUTE(0^)^") + expr("INTERVAL '0' ^MINUTE(0)^") .fails("Interval leading field precision '0' out of range for " + "INTERVAL MINUTE\\(0\\)"); } @@ -3351,7 +3351,7 @@ public void subTestIntervalMinuteNegative() { * Similarly, any changes to tests here should be echoed appropriately to * each of the other 12 subTestIntervalXXXNegative() tests. */ - public void subTestIntervalMinuteToSecondNegative() { + void subTestIntervalMinuteToSecondNegative() { // Qualifier - field mismatches wholeExpr("INTERVAL ':' MINUTE TO SECOND") .fails("Illegal interval literal format ':' for INTERVAL MINUTE TO SECOND"); @@ -3414,20 +3414,20 @@ public void subTestIntervalMinuteToSecondNegative() { + " INTERVAL MINUTE TO SECOND\\(3\\).*"); // precision > maximum - expr("INTERVAL '1:1' MINUTE(11) TO ^SECOND^") + expr("INTERVAL '1:1' ^MINUTE(11) TO SECOND^") .fails("Interval leading field precision '11' out of range for" + " INTERVAL MINUTE\\(11\\) TO SECOND"); - expr("INTERVAL '1:1' MINUTE TO SECOND(10^)^") + expr("INTERVAL '1:1' ^MINUTE TO SECOND(10)^") .fails("Interval fractional second precision '10' out of range for" + " INTERVAL MINUTE TO SECOND\\(10\\)"); // precision < minimum allowed) // note: parser will catch negative values, here we // just need to check for 0 - expr("INTERVAL '0:0' MINUTE(0) TO ^SECOND^") + expr("INTERVAL '0:0' ^MINUTE(0) TO SECOND^") .fails("Interval leading field precision '0' out of range for" + " INTERVAL MINUTE\\(0\\) TO SECOND"); - expr("INTERVAL '0:0' MINUTE TO SECOND(0^)^") + expr("INTERVAL '0:0' ^MINUTE TO SECOND(0)^") .fails("Interval fractional second precision '0' out of range for" + " INTERVAL MINUTE TO SECOND\\(0\\)"); } @@ -3439,7 +3439,7 @@ public void subTestIntervalMinuteToSecondNegative() { * Similarly, any changes to tests here should be echoed appropriately to * each of the other 12 subTestIntervalXXXNegative() tests. */ - public void subTestIntervalSecondNegative() { + void subTestIntervalSecondNegative() { // Qualifier - field mismatches wholeExpr("INTERVAL ':' SECOND") .fails("Illegal interval literal format ':' for INTERVAL SECOND.*"); @@ -3491,20 +3491,20 @@ public void subTestIntervalSecondNegative() { + " INTERVAL SECOND\\(2, 9\\).*"); // precision > maximum - expr("INTERVAL '1' SECOND(11^)^") + expr("INTERVAL '1' ^SECOND(11)^") .fails("Interval leading field precision '11' out of range for" + " INTERVAL SECOND\\(11\\)"); - expr("INTERVAL '1.1' SECOND(1, 10^)^") + expr("INTERVAL '1.1' ^SECOND(1, 10)^") .fails("Interval fractional second precision '10' out of range for" + " INTERVAL SECOND\\(1, 10\\)"); // precision < minimum allowed) // note: parser will catch negative values, here we // just need to check for 0 - expr("INTERVAL '0' SECOND(0^)^") + expr("INTERVAL '0' ^SECOND(0)^") .fails("Interval leading field precision '0' out of range for" + " INTERVAL SECOND\\(0\\)"); - expr("INTERVAL '0' SECOND(1, 0^)^") + expr("INTERVAL '0' ^SECOND(1, 0)^") .fails("Interval fractional second precision '0' out of range for" + " INTERVAL SECOND\\(1, 0\\)"); } @@ -3583,6 +3583,31 @@ public void subTestIntervalSecondNegative() { .columnType("INTERVAL MONTH(3) NOT NULL"); } + @Test void testIntervalExpression() { + expr("interval 1 hour").columnType("INTERVAL HOUR NOT NULL"); + expr("interval (2 + 3) month").columnType("INTERVAL MONTH NOT NULL"); + expr("interval (cast(null as integer)) year").columnType("INTERVAL YEAR"); + expr("interval (cast(null as integer)) year(2)") + .columnType("INTERVAL YEAR(2)"); + expr("interval (date '1970-01-01') hour").withWhole(true) + .fails("Cannot apply 'INTERVAL' to arguments of type " + + "'INTERVAL '\\. Supported form\\(s\\): " + + "'INTERVAL '"); + expr("interval (nullif(true, true)) hour").withWhole(true) + .fails("Cannot apply 'INTERVAL' to arguments of type " + + "'INTERVAL '\\. Supported form\\(s\\): " + + "'INTERVAL '"); + expr("interval (interval '1' day) hour").withWhole(true) + .fails("Cannot apply 'INTERVAL' to arguments of type " + + "'INTERVAL '\\. " + + "Supported form\\(s\\): " + + "'INTERVAL '"); + sql("select interval empno hour as h from emp") + .columnType("INTERVAL HOUR NOT NULL"); + sql("select interval emp.mgr hour as h from emp") + .columnType("INTERVAL HOUR"); + } + @Test void testIntervalOperators() { expr("interval '1' hour + TIME '8:8:8'") .columnType("TIME(0) NOT NULL"); 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 53ef390bd672..178e2e6bb71b 100644 --- a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml +++ b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml @@ -2336,6 +2336,17 @@ LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$ LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], $f9=[+($7, 0)]) LogicalTableScan(table=[[CATALOG, SALES, EMP]]) LogicalTableScan(table=[[CATALOG, SALES, DEPT]]) +]]> + + + + + + + + @@ -3743,7 +3754,6 @@ LogicalProject(DEPTNO=[$7]) ]]> -