From db60219198442f2c60345fda95a117d379d87a61 Mon Sep 17 00:00:00 2001 From: Julian Hyde Date: Fri, 22 Sep 2023 12:48:49 -0700 Subject: [PATCH] [CALCITE-6021] Add CURRENT_DATETIME function (enabled in BigQuery library) Co-authored-by: Julian Hyde Co-authored-by: Tanner Clary --- babel/src/test/resources/sql/big-query.iq | 56 +++++++++++-------- .../adapter/enumerable/RexImpTable.java | 9 +++ .../apache/calcite/runtime/SqlFunctions.java | 17 ++++++ .../org/apache/calcite/sql/SqlSyntax.java | 2 +- .../calcite/sql/fun/SqlLibraryOperators.java | 5 +- .../sql/util/ListSqlOperatorTable.java | 18 +++--- .../apache/calcite/util/BuiltInMethod.java | 3 + .../apache/calcite/plan/RelWriterTest.java | 2 +- .../apache/calcite/test/SqlValidatorTest.java | 3 + .../apache/calcite/test/SqlOperatorTest.java | 38 ++++++++++++- 10 files changed, 119 insertions(+), 34 deletions(-) diff --git a/babel/src/test/resources/sql/big-query.iq b/babel/src/test/resources/sql/big-query.iq index 36c8144a3069..10c8ed6b671e 100755 --- a/babel/src/test/resources/sql/big-query.iq +++ b/babel/src/test/resources/sql/big-query.iq @@ -148,23 +148,25 @@ SELECT current_date() AS the_date, t.current_date FROM t; # # Returns DATETIME -!if (false) { -SELECT CURRENT_DATETIME() as now; -+----------------------------+ -| now | -+----------------------------+ -| 2016-05-19T10:38:47.046465 | -+----------------------------+ +SELECT CURRENT_DATETIME() > DATETIME '2008-12-25 15:30:00' as now; ++------+ +| now | ++------+ +| true | ++------+ +(1 row) + !ok -SELECT CURRENT_DATETIME as now; -+----------------------------+ -| now | -+----------------------------+ -| 2016-05-19T10:38:47.046465 | -+----------------------------+ +SELECT CURRENT_DATETIME > DATETIME '2008-12-25 15:30:00' as now; ++------+ +| now | ++------+ +| true | ++------+ +(1 row) + !ok -!} # When a column named current_datetime is present, the column name and # the function call without parentheses are ambiguous. To ensure the @@ -173,16 +175,26 @@ SELECT CURRENT_DATETIME as now; # select the function in the now column and the table column in the # current_datetime column. -!if (false) { WITH t AS (SELECT 'column value' AS `current_datetime`) -SELECT current_datetime() as now, t.current_datetime FROM t; -+----------------------------+------------------+ -| now | current_datetime | -+----------------------------+------------------+ -| 2016-05-19T10:38:47.046465 | column value | -+----------------------------+------------------+ +SELECT current_datetime() > DATETIME '2008-12-25 15:30:00' as now, t.current_datetime FROM t; ++------+------------------+ +| now | current_datetime | ++------+------------------+ +| true | column value | ++------+------------------+ +(1 row) + +!ok + +SELECT CURRENT_DATETIME('UTC') > DATETIME '2008-12-25 15:30:00'; ++--------+ +| EXPR$0 | ++--------+ +| true | ++--------+ +(1 row) + !ok -!} ##################################################################### # CURRENT_TIME 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 4121dd0d7f26..f061950a2360 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 @@ -163,6 +163,7 @@ import static org.apache.calcite.sql.fun.SqlLibraryOperators.COTH; import static org.apache.calcite.sql.fun.SqlLibraryOperators.CSC; import static org.apache.calcite.sql.fun.SqlLibraryOperators.CSCH; +import static org.apache.calcite.sql.fun.SqlLibraryOperators.CURRENT_DATETIME; import static org.apache.calcite.sql.fun.SqlLibraryOperators.DATE; import static org.apache.calcite.sql.fun.SqlLibraryOperators.DATEADD; import static org.apache.calcite.sql.fun.SqlLibraryOperators.DATETIME; @@ -1002,6 +1003,7 @@ Builder populate3() { map.put(CURRENT_TIME, systemFunctionImplementor); map.put(CURRENT_TIMESTAMP, systemFunctionImplementor); map.put(CURRENT_DATE, systemFunctionImplementor); + map.put(CURRENT_DATETIME, systemFunctionImplementor); map.put(LOCALTIME, systemFunctionImplementor); map.put(LOCALTIMESTAMP, systemFunctionImplementor); @@ -3482,6 +3484,13 @@ private static class SystemFunctionImplementor return Expressions.call(BuiltInMethod.CURRENT_TIME.method, root); } else if (op == CURRENT_DATE) { return Expressions.call(BuiltInMethod.CURRENT_DATE.method, root); + } else if (op == CURRENT_DATETIME) { + if (call.getOperands().isEmpty()) { + return Expressions.call(BuiltInMethod.CURRENT_DATETIME.method, root); + } else { + return Expressions.call(BuiltInMethod.CURRENT_DATETIME2.method, root, + argValueList.get(0)); + } } else if (op == LOCALTIMESTAMP) { return Expressions.call(BuiltInMethod.LOCAL_TIMESTAMP.method, root); } else if (op == LOCALTIME) { diff --git a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java index 0586243131db..ceff33066ab7 100644 --- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java +++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java @@ -4708,6 +4708,23 @@ public static int currentDate(DataContext root) { return date; } + /** SQL {@code CURRENT_DATETIME} function. */ + @NonDeterministic + public static Long currentDatetime(DataContext root) { + final long timestamp = DataContext.Variable.CURRENT_TIMESTAMP.get(root); + return datetime(timestamp); + } + + /** SQL {@code CURRENT_DATETIME} function with a specified timezone. */ + @NonDeterministic + public static @Nullable Long currentDatetime(DataContext root, @Nullable String timezone) { + if (timezone == null) { + return null; + } + final long timestamp = DataContext.Variable.UTC_TIMESTAMP.get(root); + return datetime(timestamp, timezone); + } + /** SQL {@code LOCAL_TIMESTAMP} function. */ @NonDeterministic public static long localTimestamp(DataContext root) { diff --git a/core/src/main/java/org/apache/calcite/sql/SqlSyntax.java b/core/src/main/java/org/apache/calcite/sql/SqlSyntax.java index ec94149df3fe..b1825d5dc22c 100644 --- a/core/src/main/java/org/apache/calcite/sql/SqlSyntax.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlSyntax.java @@ -136,7 +136,7 @@ public enum SqlSyntax { * * @see SqlConformance#allowNiladicParentheses() */ - FUNCTION_ID { + FUNCTION_ID(FUNCTION) { @Override public void unparse( SqlWriter writer, SqlOperator operator, diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java index 11bb6f960d1c..a674efb21c29 100644 --- a/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java +++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java @@ -801,9 +801,10 @@ static RelDataType deriveTypeSplit(SqlOperatorBinding operatorBinding, @LibraryOperator(libraries = {BIG_QUERY}) public static final SqlFunction CURRENT_DATETIME = SqlBasicFunction.create("CURRENT_DATETIME", - ReturnTypes.TIMESTAMP.andThen(SqlTypeTransforms.TO_NULLABLE), + ReturnTypes.TIMESTAMP_NULLABLE, OperandTypes.NILADIC.or(OperandTypes.STRING), - SqlFunctionCategory.TIMEDATE); + SqlFunctionCategory.TIMEDATE) + .withSyntax(SqlSyntax.FUNCTION_ID); /** The "DATE_FROM_UNIX_DATE(integer)" function; returns a DATE value * a given number of seconds after 1970-01-01. */ diff --git a/core/src/main/java/org/apache/calcite/sql/util/ListSqlOperatorTable.java b/core/src/main/java/org/apache/calcite/sql/util/ListSqlOperatorTable.java index c37549f9e4d7..3a6b29023b62 100644 --- a/core/src/main/java/org/apache/calcite/sql/util/ListSqlOperatorTable.java +++ b/core/src/main/java/org/apache/calcite/sql/util/ListSqlOperatorTable.java @@ -79,20 +79,24 @@ public void add(SqlOperator op) { SqlSyntax syntax, List operatorList, SqlNameMatcher nameMatcher) { - for (SqlOperator operator : this.operatorList) { - if (operator.getSyntax().family != syntax) { - continue; - } + for (SqlOperator op : this.operatorList) { if (!opName.isSimple() - || !nameMatcher.matches(operator.getName(), opName.getSimple())) { + || !nameMatcher.matches(op.getName(), opName.getSimple())) { continue; } if (category != null - && category != category(operator) + && category != category(op) && !category.isUserDefinedNotSpecificFunction()) { continue; } - operatorList.add(operator); + if (op.getSyntax() == syntax) { + operatorList.add(op); + } else if (syntax == SqlSyntax.FUNCTION + && op instanceof SqlFunction) { + // this special case is needed for operators like CAST, + // which are treated as functions but have special syntax + operatorList.add(op); + } } } diff --git a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java index de349c086df5..f56493e22aea 100644 --- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java +++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java @@ -684,6 +684,9 @@ public enum BuiltInMethod { CURRENT_TIMESTAMP(SqlFunctions.class, "currentTimestamp", DataContext.class), CURRENT_TIME(SqlFunctions.class, "currentTime", DataContext.class), CURRENT_DATE(SqlFunctions.class, "currentDate", DataContext.class), + CURRENT_DATETIME(SqlFunctions.class, "currentDatetime", DataContext.class), + CURRENT_DATETIME2(SqlFunctions.class, "currentDatetime", DataContext.class, + String.class), LOCAL_TIMESTAMP(SqlFunctions.class, "localTimestamp", DataContext.class), LOCAL_TIME(SqlFunctions.class, "localTime", DataContext.class), TIME_ZONE(SqlFunctions.class, "timeZone", DataContext.class), diff --git a/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java b/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java index 8eccd362f6b0..592b6e93e49a 100644 --- a/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java +++ b/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java @@ -1047,7 +1047,7 @@ void testAggregateWithAlias(SqlExplainFormat format) { String relJson = jsonWriter.asString(); String result = deserializeAndDumpToTextFormat(getSchema(rel), relJson); final String expected = "" - + "LogicalProject(JOB=[$2], $f1=[CURRENT_DATETIME()])\n" + + "LogicalProject(JOB=[$2], $f1=[CURRENT_DATETIME])\n" + " LogicalTableScan(table=[[scott, EMP]])\n"; assertThat(result, isLinux(expected)); } 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 4303a610d6c0..7a2896d6b2e3 100644 --- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java +++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java @@ -1615,6 +1615,9 @@ void testLikeAndSimilarFails() { shouldFail.fails("Column 'CURRENT_DATETIME' not found in any table"); final SqlOperatorTable opTable = operatorTableFor(SqlLibrary.BIG_QUERY); + sql("select current_datetime") + .withConformance(SqlConformanceEnum.BIG_QUERY) + .withOperatorTable(opTable).ok(); sql("select current_datetime()") .withConformance(SqlConformanceEnum.BIG_QUERY) .withOperatorTable(opTable).ok(); 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 27f37e18b0fe..746c3307203e 100644 --- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java +++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java @@ -6986,9 +6986,45 @@ private static void checkIf(SqlOperatorFixture f) { // allow it as a function. f.checkNull("DATE(null)"); f.checkScalar("DATE('1985-12-06')", "1985-12-06", "DATE NOT NULL"); + } + + /** Tests that the {@code CURRENT_DATETIME} function is defined in the + * BigQuery library, is not available in the default library, + * and can be called with and without parentheses. */ + @Test void testCurrentDatetimeFunc() { + SqlOperatorFixture f0 = fixture() + .setFor(SqlLibraryOperators.CURRENT_DATETIME); + + // In default conformance, with BigQuery operator table, + // CURRENT_DATETIME is valid only without parentheses. + final SqlOperatorFixture f1 = + f0.withLibrary(SqlLibrary.BIG_QUERY); + f1.checkType("CURRENT_DATETIME", "TIMESTAMP(0) NOT NULL"); + f1.checkFails("^CURRENT_DATETIME()^", + "No match found for function signature CURRENT_DATETIME\\(\\)", + false); + + // In BigQuery conformance, with BigQuery operator table, + // CURRENT_DATETIME should be valid with and without parentheses. + // We cannot execute it because results are non-deterministic. + SqlOperatorFixture f = + f1.withConformance(SqlConformanceEnum.BIG_QUERY); + f.checkType("CURRENT_DATETIME", "TIMESTAMP(0) NOT NULL"); f.checkType("CURRENT_DATETIME()", "TIMESTAMP(0) NOT NULL"); - f.checkType("CURRENT_DATETIME('America/Los_Angeles')", "TIMESTAMP(0) NOT NULL"); + f.checkType("CURRENT_DATETIME('America/Los_Angeles')", + "TIMESTAMP(0) NOT NULL"); f.checkType("CURRENT_DATETIME(CAST(NULL AS VARCHAR(20)))", "TIMESTAMP(0)"); + f.checkNull("CURRENT_DATETIME(CAST(NULL AS VARCHAR(20)))"); + + // In BigQuery conformance, but with the default operator table, + // CURRENT_DATETIME is not found. + final SqlOperatorFixture f2 = + f0.withConformance(SqlConformanceEnum.BIG_QUERY); + f2.checkFails("^CURRENT_DATETIME^", + "Column 'CURRENT_DATETIME' not found in any table", false); + f2.checkFails("^CURRENT_DATETIME()^", + "No match found for function signature CURRENT_DATETIME\\(\\)", + false); } @Test void testAbsFunc() {