From 4823cb7760913f236e7f0f2cb149325b55a3f124 Mon Sep 17 00:00:00 2001 From: Mihai Budiu Date: Thu, 21 Mar 2024 10:04:45 -0700 Subject: [PATCH] [CALCITE-6015] AssertionError during optimization of EXTRACT expression Signed-off-by: Mihai Budiu --- .../adapter/enumerable/RexImpTable.java | 11 +- .../java/org/apache/calcite/sql/SqlCall.java | 2 +- .../calcite/sql/fun/SqlExtractFunction.java | 119 ++++++++++++- .../apache/calcite/sql/type/OperandTypes.java | 3 +- .../sql/validate/SqlValidatorImpl.java | 2 +- .../apache/calcite/test/SqlValidatorTest.java | 3 +- site/_docs/history.md | 4 + .../calcite/sql/test/SqlOperatorFixture.java | 8 - .../apache/calcite/test/SqlOperatorTest.java | 168 ++++++++---------- 9 files changed, 210 insertions(+), 110 deletions(-) diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java index 9cabfcb1068f..6bf8f18bee2a 100644 --- a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java +++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java @@ -3128,7 +3128,8 @@ private static class ExtractImplementor extends AbstractRexCallImplementor { case MILLISECOND: case MICROSECOND: case NANOSECOND: - if (sqlTypeName == SqlTypeName.DATE) { + if (sqlTypeName == SqlTypeName.DATE + || SqlTypeName.YEAR_INTERVAL_TYPES.contains(sqlTypeName)) { return Expressions.constant(0L); } operand = mod(operand, TimeUnit.MINUTE.multiplier.longValue(), !isIntervalType); @@ -3155,9 +3156,6 @@ private static class ExtractImplementor extends AbstractRexCallImplementor { translator.getRoot())); return Expressions.divide(operand, Expressions.constant(TimeUnit.SECOND.multiplier.longValue())); - case INTERVAL_YEAR: - case INTERVAL_YEAR_MONTH: - case INTERVAL_MONTH: case INTERVAL_DAY: case INTERVAL_DAY_HOUR: case INTERVAL_DAY_MINUTE: @@ -3168,6 +3166,11 @@ private static class ExtractImplementor extends AbstractRexCallImplementor { case INTERVAL_MINUTE: case INTERVAL_MINUTE_SECOND: case INTERVAL_SECOND: + return Expressions.divide(operand, + Expressions.constant(TimeUnit.SECOND.multiplier.longValue())); + case INTERVAL_YEAR: + case INTERVAL_YEAR_MONTH: + case INTERVAL_MONTH: // no convertlet conversion, pass it as extract throw new AssertionError("unexpected " + sqlTypeName); default: diff --git a/core/src/main/java/org/apache/calcite/sql/SqlCall.java b/core/src/main/java/org/apache/calcite/sql/SqlCall.java index 9eba738bd482..617fa469ebdf 100755 --- a/core/src/main/java/org/apache/calcite/sql/SqlCall.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlCall.java @@ -191,7 +191,7 @@ public int operandCount() { * Returns a string describing the actual argument types of a call, e.g. * "SUBSTR(VARCHAR(12), NUMBER(3,2), INTEGER)". */ - protected String getCallSignature( + public String getCallSignature( SqlValidator validator, @Nullable SqlValidatorScope scope) { List signatureList = new ArrayList<>(); diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlExtractFunction.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlExtractFunction.java index 07789aa7cca6..1c8a20b0ad76 100644 --- a/core/src/main/java/org/apache/calcite/sql/fun/SqlExtractFunction.java +++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlExtractFunction.java @@ -17,6 +17,7 @@ package org.apache.calcite.sql.fun; import org.apache.calcite.avatica.util.TimeUnitRange; +import org.apache.calcite.rel.type.RelDataType; import org.apache.calcite.sql.SqlCall; import org.apache.calcite.sql.SqlFunction; import org.apache.calcite.sql.SqlFunctionCategory; @@ -26,12 +27,16 @@ import org.apache.calcite.sql.SqlWriter; import org.apache.calcite.sql.type.OperandTypes; import org.apache.calcite.sql.type.ReturnTypes; +import org.apache.calcite.sql.type.SqlTypeName; import org.apache.calcite.sql.validate.SqlMonotonicity; import org.apache.calcite.sql.validate.SqlValidator; import org.apache.calcite.sql.validate.SqlValidatorScope; import org.apache.calcite.util.Util; +import com.google.common.collect.ImmutableSet; + import static org.apache.calcite.sql.validate.SqlNonNullableAccessors.getOperandLiteralValueOrThrow; +import static org.apache.calcite.util.Static.RESOURCE; /** * The SQL EXTRACT operator. Extracts a specified field value from @@ -70,6 +75,59 @@ public SqlExtractFunction(String name) { writer.endFunCall(frame); } + // List of types that support EXTRACT(X, ...) where X is MONTH or larger + private static final ImmutableSet MONTH_AND_ABOVE_TYPES = + new ImmutableSet.Builder() + .add(SqlTypeName.DATE) + .add(SqlTypeName.TIMESTAMP) + .add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE) + .addAll(SqlTypeName.YEAR_INTERVAL_TYPES) + .build(); + + // List of types that support EXTRACT(X, ...) where X is between DAY and WEEK + private static final ImmutableSet DAY_TO_WEEK_TYPES = + new ImmutableSet.Builder() + .add(SqlTypeName.DATE) + .add(SqlTypeName.TIMESTAMP) + .add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE) + .build(); + + // List of types that support EXTRACT(EPOCH, ...) + private static final ImmutableSet EPOCH_TYPES = + new ImmutableSet.Builder() + .add(SqlTypeName.DATE) + .add(SqlTypeName.TIMESTAMP) + .add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE) + .addAll(SqlTypeName.YEAR_INTERVAL_TYPES) + .addAll(SqlTypeName.DAY_INTERVAL_TYPES) + .build(); + + // List of types that support EXTRACT(DAY, ...) + private static final ImmutableSet DAY_TYPES = + new ImmutableSet.Builder() + .add(SqlTypeName.DATE) + .add(SqlTypeName.TIMESTAMP) + .add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE) + .add(SqlTypeName.INTERVAL_DAY) + .add(SqlTypeName.INTERVAL_DAY_HOUR) + .add(SqlTypeName.INTERVAL_DAY_MINUTE) + .add(SqlTypeName.INTERVAL_DAY_SECOND) + .addAll(SqlTypeName.YEAR_INTERVAL_TYPES) + .build(); + + // List of types that support EXTRACT(X, ...) where X is + // between HOUR and NANOSECOND + private static final ImmutableSet HOUR_TO_NANOSECOND_TYPES = + new ImmutableSet.Builder() + .add(SqlTypeName.DATE) + .add(SqlTypeName.TIMESTAMP) + .add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE) + .add(SqlTypeName.TIME) + .add(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE) + .addAll(SqlTypeName.YEAR_INTERVAL_TYPES) + .addAll(SqlTypeName.DAY_INTERVAL_TYPES) + .build(); + @Override public void validateCall(SqlCall call, SqlValidator validator, SqlValidatorScope scope, SqlValidatorScope operandScope) { super.validateCall(call, validator, scope, operandScope); @@ -83,8 +141,65 @@ public SqlExtractFunction(String name) { // startUnit = EPOCH and timeFrameName = 'MINUTE15'. // // If the latter, check that timeFrameName is valid. - validator.validateTimeFrame( - (SqlIntervalQualifier) call.getOperandList().get(0)); + SqlIntervalQualifier qualifier = call.operand(0); + validator.validateTimeFrame(qualifier); + TimeUnitRange range = qualifier.timeUnitRange; + + RelDataType type = validator.getValidatedNodeTypeIfKnown(call.operand(1)); + if (type == null) { + return; + } + + SqlTypeName typeName = type.getSqlTypeName(); + boolean legal; + switch (range) { + case YEAR: + case MONTH: + case ISOYEAR: + case QUARTER: + case DECADE: + case CENTURY: + case MILLENNIUM: + legal = MONTH_AND_ABOVE_TYPES.contains(typeName); + break; + case WEEK: + case DOW: + case ISODOW: + case DOY: + legal = DAY_TO_WEEK_TYPES.contains(typeName); + break; + case EPOCH: + legal = EPOCH_TYPES.contains(typeName); + break; + case DAY: + legal = DAY_TYPES.contains(typeName); + break; + case HOUR: + case MINUTE: + case SECOND: + case MILLISECOND: + case MICROSECOND: + case NANOSECOND: + legal = HOUR_TO_NANOSECOND_TYPES.contains(typeName); + break; + case YEAR_TO_MONTH: + case DAY_TO_HOUR: + case DAY_TO_MINUTE: + case DAY_TO_SECOND: + case HOUR_TO_MINUTE: + case HOUR_TO_SECOND: + case MINUTE_TO_SECOND: + default: + legal = false; + break; + } + + if (!legal) { + throw validator.newValidationError(call, + RESOURCE.canNotApplyOp2Type(call.getOperator().getName(), + call.getCallSignature(validator, scope), + call.getOperator().getAllowedSignatures())); + } } @Override public SqlMonotonicity getMonotonicity(SqlOperatorBinding call) { diff --git a/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java b/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java index 67367ba3f415..c7a85cd66e61 100644 --- a/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java +++ b/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java @@ -1100,7 +1100,8 @@ public static SqlSingleOperandTypeChecker same(int operandCount, family(SqlTypeFamily.DATETIME_INTERVAL, SqlTypeFamily.DATETIME); public static final SqlSingleOperandTypeChecker INTERVALINTERVAL_INTERVALDATETIME = - INTERVAL_SAME_SAME.or(INTERVAL_DATETIME); + INTERVAL_SAME_SAME.or(INTERVAL_DATETIME) + .or(family(SqlTypeFamily.INTERVAL_DAY_TIME, SqlTypeFamily.INTERVAL_YEAR_MONTH)); // TODO: datetime+interval checking missing // TODO: interval+datetime checking missing 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 8612da78a958..9572cf4ebd91 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 @@ -1869,7 +1869,7 @@ protected SqlSelect createSourceSelectForDelete(SqlDelete call) { } final SqlNode original = originalExprs.get(node); if (original != null && original != node) { - return getValidatedNodeType(original); + return getValidatedNodeTypeIfKnown(original); } if (node instanceof SqlIdentifier) { return getCatalogReader().getNamedType((SqlIdentifier) node); 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 21e2e91c63c8..11c2edf7a7a6 100644 --- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java +++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java @@ -7067,9 +7067,8 @@ void testGroupExpressionEquivalenceParams() { .columnType("BIGINT NOT NULL"); expr("extract(minute from interval '1.1' second)").ok(); expr("extract(year from DATE '2008-2-2')").ok(); + expr("extract(minute from interval '11' month)").ok(); - wholeExpr("extract(minute from interval '11' month)") - .fails("(?s).*Cannot apply.*"); wholeExpr("extract(year from interval '11' second)") .fails("(?s).*Cannot apply.*"); } diff --git a/site/_docs/history.md b/site/_docs/history.md index 553ca1c5852f..f3140e7d268e 100644 --- a/site/_docs/history.md +++ b/site/_docs/history.md @@ -43,6 +43,10 @@ z. #### Breaking Changes {: #breaking-1-37-0} +* In the context of [CALCITE-6015] the visibility of the method +`SqlCall.getCallSignature` has been converted from `protected` to `public`. + Any subclass overriding it will need to be adjusted accordingly. + Compatibility: This release is tested on Linux, macOS, Microsoft Windows; using JDK/OpenJDK versions 8 to 19; Guava versions 21.0 to 32.1.3-jre; diff --git a/testkit/src/main/java/org/apache/calcite/sql/test/SqlOperatorFixture.java b/testkit/src/main/java/org/apache/calcite/sql/test/SqlOperatorFixture.java index f87208c02a4f..af4264e70af0 100644 --- a/testkit/src/main/java/org/apache/calcite/sql/test/SqlOperatorFixture.java +++ b/testkit/src/main/java/org/apache/calcite/sql/test/SqlOperatorFixture.java @@ -84,14 +84,6 @@ public interface SqlOperatorFixture extends AutoCloseable { // TODO: Change message String BAD_DATETIME_MESSAGE = "(?s).*"; - // Error messages when an invalid time unit is given as - // input to extract for a particular input type. - String INVALID_EXTRACT_UNIT_CONVERTLET_ERROR = - "Was not expecting value '.*' for enumeration.*"; - - String INVALID_EXTRACT_UNIT_VALIDATION_ERROR = - "Cannot apply 'EXTRACT' to arguments of type .*'\n.*"; - String LITERAL_OUT_OF_RANGE_MESSAGE = "(?s).*Numeric literal.*out of range.*"; diff --git a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java index 5a772c86a235..023de7c972d1 100644 --- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java +++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java @@ -129,8 +129,6 @@ import static org.apache.calcite.sql.test.SqlOperatorFixture.DIVISION_BY_ZERO_MESSAGE; import static org.apache.calcite.sql.test.SqlOperatorFixture.INVALID_ARGUMENTS_NUMBER; import static org.apache.calcite.sql.test.SqlOperatorFixture.INVALID_CHAR_MESSAGE; -import static org.apache.calcite.sql.test.SqlOperatorFixture.INVALID_EXTRACT_UNIT_CONVERTLET_ERROR; -import static org.apache.calcite.sql.test.SqlOperatorFixture.INVALID_EXTRACT_UNIT_VALIDATION_ERROR; import static org.apache.calcite.sql.test.SqlOperatorFixture.LITERAL_OUT_OF_RANGE_MESSAGE; import static org.apache.calcite.sql.test.SqlOperatorFixture.OUT_OF_RANGE_MESSAGE; import static org.apache.calcite.sql.test.SqlOperatorFixture.WRONG_FORMAT_MESSAGE; @@ -10626,64 +10624,45 @@ private static void checkArrayConcatAggFuncFails(SqlOperatorFixture t) { f.setFor(SqlStdOperatorTable.EXTRACT, VM_FENNEL, VM_JAVA); if (TODO) { - // Not supported, fails in type validation because the extract - // unit is not YearMonth interval type. - f.checkScalar("extract(epoch from interval '4-2' year to month)", // number of seconds elapsed since timestamp // '1970-01-01 00:00:00' + input interval "131328000", "BIGINT NOT NULL"); - - f.checkScalar("extract(second from interval '4-2' year to month)", - "0", "BIGINT NOT NULL"); - - f.checkScalar("extract(millisecond from " - + "interval '4-2' year to month)", "0", "BIGINT NOT NULL"); - - f.checkScalar("extract(microsecond " - + "from interval '4-2' year to month)", "0", "BIGINT NOT NULL"); - - f.checkScalar("extract(nanosecond from " - + "interval '4-2' year to month)", "0", "BIGINT NOT NULL"); - - f.checkScalar("extract(minute from interval '4-2' year to month)", - "0", "BIGINT NOT NULL"); - - f.checkScalar("extract(hour from interval '4-2' year to month)", - "0", "BIGINT NOT NULL"); - - f.checkScalar("extract(day from interval '4-2' year to month)", - "0", "BIGINT NOT NULL"); } + f.checkScalar("extract(second from interval '4-2' year to month)", + "0", "BIGINT NOT NULL"); + f.checkScalar("extract(millisecond from " + + "interval '4-2' year to month)", "0", "BIGINT NOT NULL"); + f.checkScalar("extract(microsecond " + + "from interval '4-2' year to month)", "0", "BIGINT NOT NULL"); + f.checkScalar("extract(nanosecond from " + + "interval '4-2' year to month)", "0", "BIGINT NOT NULL"); + f.checkScalar("extract(minute from interval '4-2' year to month)", + "0", "BIGINT NOT NULL"); + f.checkScalar("extract(hour from interval '4-2' year to month)", + "0", "BIGINT NOT NULL"); + f.checkScalar("extract(day from interval '4-2' year to month)", + "0", "BIGINT NOT NULL"); - // Postgres doesn't support DOW, ISODOW, DOY and WEEK on INTERVAL YEAR MONTH type. - // SQL standard doesn't have extract units for DOW, ISODOW, DOY and WEEK. - if (Bug.CALCITE_2539_FIXED) { - f.checkFails("extract(doy from interval '4-2' year to month)", - INVALID_EXTRACT_UNIT_VALIDATION_ERROR, false); - f.checkFails("^extract(dow from interval '4-2' year to month)^", - INVALID_EXTRACT_UNIT_VALIDATION_ERROR, false); - f.checkFails("^extract(week from interval '4-2' year to month)^", - INVALID_EXTRACT_UNIT_VALIDATION_ERROR, false); - f.checkFails("^extract(isodow from interval '4-2' year to month)^", - INVALID_EXTRACT_UNIT_VALIDATION_ERROR, false); - } + final String fail = "Cannot apply 'EXTRACT' to arguments of type 'EXTRACT\\(<.*> " + + "FROM \\)'\\. Supported form\\(s\\): " + + ".*\\n.*\\n.*"; + + f.checkFails("^extract(doy from interval '4-2' year to month)^", fail, false); + f.checkFails("^extract(dow from interval '4-2' year to month)^", fail, false); + f.checkFails("^extract(isodow from interval '4-2' year to month)^", fail, false); + f.checkFails("^extract(week from interval '4-2' year to month)^", fail, false); f.checkScalar("extract(month from interval '4-2' year to month)", "2", "BIGINT NOT NULL"); - f.checkScalar("extract(quarter from interval '4-2' year to month)", "1", "BIGINT NOT NULL"); - f.checkScalar("extract(year from interval '4-2' year to month)", "4", "BIGINT NOT NULL"); - f.checkScalar("extract(decade from " + "interval '426-3' year(3) to month)", "42", "BIGINT NOT NULL"); - f.checkScalar("extract(century from " + "interval '426-3' year(3) to month)", "4", "BIGINT NOT NULL"); - f.checkScalar("extract(millennium from " + "interval '2005-3' year(4) to month)", "2", "BIGINT NOT NULL"); } @@ -10692,15 +10671,11 @@ private static void checkArrayConcatAggFuncFails(SqlOperatorFixture t) { final SqlOperatorFixture f = fixture(); f.setFor(SqlStdOperatorTable.EXTRACT, VM_FENNEL, VM_JAVA); - if (TODO) { - // Not implemented in operator test - f.checkScalar("extract(epoch from " - + "interval '2 3:4:5.678' day to second)", - // number of seconds elapsed since timestamp - // '1970-01-01 00:00:00' + input interval - "183845.678", - "BIGINT NOT NULL"); - } + f.checkScalar("extract(epoch from interval '2 3:4:5.678' day to second)", + // number of seconds elapsed since timestamp + // '1970-01-01 00:00:00' + input interval + "183845", + "BIGINT NOT NULL"); f.checkScalar("extract(millisecond from " + "interval '2 3:4:5.678' day to second)", @@ -10737,46 +10712,19 @@ private static void checkArrayConcatAggFuncFails(SqlOperatorFixture t) { "2", "BIGINT NOT NULL"); - // Postgres doesn't support DOW, ISODOW, DOY and WEEK on INTERVAL DAY TIME type. - // SQL standard doesn't have extract units for DOW, ISODOW, DOY and WEEK. - f.checkFails("extract(doy from interval '2 3:4:5.678' day to second)", - INVALID_EXTRACT_UNIT_CONVERTLET_ERROR, true); - f.checkFails("extract(dow from interval '2 3:4:5.678' day to second)", - INVALID_EXTRACT_UNIT_CONVERTLET_ERROR, true); - f.checkFails("extract(week from interval '2 3:4:5.678' day to second)", - INVALID_EXTRACT_UNIT_CONVERTLET_ERROR, true); - f.checkFails("extract(isodow from interval '2 3:4:5.678' day to second)", - INVALID_EXTRACT_UNIT_CONVERTLET_ERROR, true); - - f.checkFails("^extract(month from interval '2 3:4:5.678' day to second)^", - "(?s)Cannot apply 'EXTRACT' to arguments of type 'EXTRACT\\( FROM \\)'\\. Supported " - + "form\\(s\\):.*", - false); + final String fail = "Cannot apply 'EXTRACT' to arguments of type 'EXTRACT\\(<.*> " + + "FROM \\)'\\. Supported form\\(s\\): .*\\n.*\\n.*"; - f.checkFails("^extract(quarter from interval '2 3:4:5.678' day to second)^", - "(?s)Cannot apply 'EXTRACT' to arguments of type 'EXTRACT\\( FROM \\)'\\. Supported " - + "form\\(s\\):.*", - false); - - f.checkFails("^extract(year from interval '2 3:4:5.678' day to second)^", - "(?s)Cannot apply 'EXTRACT' to arguments of type 'EXTRACT\\( FROM \\)'\\. Supported " - + "form\\(s\\):.*", - false); - - f.checkFails("^extract(isoyear from interval '2 3:4:5.678' day to second)^", - "(?s)Cannot apply 'EXTRACT' to arguments of type 'EXTRACT\\( FROM \\)'\\. Supported " - + "form\\(s\\):.*", - false); - - f.checkFails("^extract(century from interval '2 3:4:5.678' day to second)^", - "(?s)Cannot apply 'EXTRACT' to arguments of type 'EXTRACT\\( FROM \\)'\\. Supported " - + "form\\(s\\):.*", - false); + f.checkFails("^extract(doy from interval '2 3:4:5.678' day to second)^", fail, false); + f.checkFails("^extract(dow from interval '2 3:4:5.678' day to second)^", fail, false); + f.checkFails("^extract(week from interval '2 3:4:5.678' day to second)^", fail, false); + f.checkFails("^extract(isodow from interval '2 3:4:5.678' day to second)^", fail, false); + f.checkFails("^extract(month from interval '2 3:4:5.678' day to second)^", fail, false); + f.checkFails("^extract(quarter from interval '2 3:4:5.678' day to second)^", fail, false); + f.checkFails("^extract(year from interval '2 3:4:5.678' day to second)^", fail, false); + f.checkFails("^extract(isoyear from interval '2 3:4:5.678' day to second)^", fail, false); + f.checkFails("^extract(century from interval '2 3:4:5.678' day to second)^", fail, false); + f.checkFails("^extract(millennium from interval '2 3:4:5.678' day to second)^", fail, false); } @Test void testExtractDate() { @@ -10862,6 +10810,44 @@ private static void checkArrayConcatAggFuncFails(SqlOperatorFixture t) { "3", "BIGINT NOT NULL"); } + @Test void testExtractTime() { + final SqlOperatorFixture f = fixture(); + f.setFor(SqlStdOperatorTable.EXTRACT, VM_FENNEL, VM_JAVA); + + final String fail = "Cannot apply 'EXTRACT' to arguments of type 'EXTRACT\\(<.*> " + + "FROM \\)'\\. " + + "Supported form\\(s\\): .*\\n.*\\n.*"; + + f.checkFails("extract(^a^ from time '12:34:56')", + "'A' is not a valid time frame", false); + f.checkFails("^extract(epoch from time '12:34:56')^", + fail, false); + + f.checkScalar("extract(second from time '12:34:56')", + "56", "BIGINT NOT NULL"); + f.checkScalar("extract(millisecond from time '12:34:56')", + "56000", "BIGINT NOT NULL"); + f.checkScalar("extract(microsecond from time '12:34:56')", + "56000000", "BIGINT NOT NULL"); + f.checkScalar("extract(nanosecond from time '12:34:56')", + "56000000000", "BIGINT NOT NULL"); + f.checkScalar("extract(minute from time '12:34:56')", + "34", "BIGINT NOT NULL"); + f.checkScalar("extract(hour from time '12:34:56')", + "12", "BIGINT NOT NULL"); + f.checkFails("^extract(day from time '12:34:56')^", fail, false); + f.checkFails("^extract(month from time '12:34:56')^", fail, false); + f.checkFails("^extract(quarter from time '12:34:56')^", fail, false); + f.checkFails("^extract(year from time '12:34:56')^", fail, false); + f.checkFails("^extract(isoyear from time '12:34:56')^", fail, false); + f.checkFails("^extract(doy from time '12:34:56')^", fail, false); + f.checkFails("^extract(dow from time '12:34:56')^", fail, false); + f.checkFails("^extract(week from time '12:34:56')^", fail, false); + f.checkFails("^extract(decade from time '12:34:56')^", fail, false); + f.checkFails("^extract(century from time '12:34:56')^", fail, false); + f.checkFails("^extract(millennium from time '12:34:56')^", fail, false); + } + @Test void testExtractTimestamp() { final SqlOperatorFixture f = fixture(); f.setFor(SqlStdOperatorTable.EXTRACT, VM_FENNEL, VM_JAVA);