From 9c0d690439920fff5120250e5f1899f857a5b1ec Mon Sep 17 00:00:00 2001 From: James Duong Date: Thu, 7 Mar 2024 17:11:37 -0800 Subject: [PATCH] [CALCITE-6314] Add RANDOM function (enabled in Postgres library) --- .../adapter/enumerable/RexImpTable.java | 2 + .../apache/calcite/sql/SqlBasicFunction.java | 51 ++++++++++++------ .../calcite/sql/fun/SqlLibraryOperators.java | 6 +++ .../calcite/sql/fun/SqlRandFunction.java | 52 ------------------- .../calcite/sql/fun/SqlStdOperatorTable.java | 5 +- site/_docs/reference.md | 1 + .../apache/calcite/test/SqlOperatorTest.java | 16 ++++++ 7 files changed, 65 insertions(+), 68 deletions(-) delete mode 100644 core/src/main/java/org/apache/calcite/sql/fun/SqlRandFunction.java 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 1bc88f18c43f..9cabfcb1068f 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 @@ -232,6 +232,7 @@ import static org.apache.calcite.sql.fun.SqlLibraryOperators.PARSE_TIMESTAMP; import static org.apache.calcite.sql.fun.SqlLibraryOperators.PARSE_URL; import static org.apache.calcite.sql.fun.SqlLibraryOperators.POW; +import static org.apache.calcite.sql.fun.SqlLibraryOperators.RANDOM; import static org.apache.calcite.sql.fun.SqlLibraryOperators.REGEXP; import static org.apache.calcite.sql.fun.SqlLibraryOperators.REGEXP_CONTAINS; import static org.apache.calcite.sql.fun.SqlLibraryOperators.REGEXP_EXTRACT; @@ -652,6 +653,7 @@ Builder populate() { BuiltInMethod.RAND_SEED.method); defineReflective(RAND_INTEGER, BuiltInMethod.RAND_INTEGER.method, BuiltInMethod.RAND_INTEGER_SEED.method); + defineReflective(RANDOM, BuiltInMethod.RAND.method); defineMethod(ACOS, BuiltInMethod.ACOS.method, NullPolicy.STRICT); defineMethod(ACOSH, BuiltInMethod.ACOSH.method, NullPolicy.STRICT); diff --git a/core/src/main/java/org/apache/calcite/sql/SqlBasicFunction.java b/core/src/main/java/org/apache/calcite/sql/SqlBasicFunction.java index 7e3da75791e6..f2f54a685039 100644 --- a/core/src/main/java/org/apache/calcite/sql/SqlBasicFunction.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlBasicFunction.java @@ -48,6 +48,7 @@ public class SqlBasicFunction extends SqlFunction { private final SqlOperandHandler operandHandler; private final int callValidator; private final Function monotonicityInference; + private final boolean dynamic; //~ Constructors ----------------------------------------------------------- @@ -73,7 +74,8 @@ private SqlBasicFunction(String name, SqlKind kind, SqlSyntax syntax, SqlOperandTypeChecker operandTypeChecker, Integer callValidator, SqlFunctionCategory category, - Function monotonicityInference) { + Function monotonicityInference, + boolean dynamic) { super(name, kind, requireNonNull(returnTypeInference, "returnTypeInference"), operandTypeInference, @@ -84,6 +86,7 @@ private SqlBasicFunction(String name, SqlKind kind, SqlSyntax syntax, this.callValidator = requireNonNull(callValidator, "callValidator"); this.monotonicityInference = requireNonNull(monotonicityInference, "monotonicityInference"); + this.dynamic = dynamic; } /** Creates a {@code SqlBasicFunction} whose name is the same as its kind @@ -94,7 +97,7 @@ public static SqlBasicFunction create(SqlKind kind, return new SqlBasicFunction(kind.name(), kind, SqlSyntax.FUNCTION, true, returnTypeInference, null, OperandHandlers.DEFAULT, operandTypeChecker, 0, - SqlFunctionCategory.SYSTEM, call -> SqlMonotonicity.NOT_MONOTONIC); + SqlFunctionCategory.SYSTEM, call -> SqlMonotonicity.NOT_MONOTONIC, false); } /** Creates a {@code SqlBasicFunction} @@ -106,7 +109,7 @@ public static SqlBasicFunction create(String name, return new SqlBasicFunction(name, SqlKind.OTHER_FUNCTION, SqlSyntax.FUNCTION, true, returnTypeInference, null, OperandHandlers.DEFAULT, operandTypeChecker, 0, - SqlFunctionCategory.NUMERIC, call -> SqlMonotonicity.NOT_MONOTONIC); + SqlFunctionCategory.NUMERIC, call -> SqlMonotonicity.NOT_MONOTONIC, false); } /** Creates a {@code SqlBasicFunction} @@ -117,7 +120,7 @@ public static SqlBasicFunction create(String name, return new SqlBasicFunction(name, SqlKind.OTHER_FUNCTION, SqlSyntax.FUNCTION, true, returnTypeInference, null, OperandHandlers.DEFAULT, operandTypeChecker, 0, - category, call -> SqlMonotonicity.NOT_MONOTONIC); + category, call -> SqlMonotonicity.NOT_MONOTONIC, false); } //~ Methods ---------------------------------------------------------------- @@ -151,12 +154,16 @@ public static SqlBasicFunction create(String name, super.validateCall(call, validator, scope, operandScope); } + @Override public boolean isDynamicFunction() { + return dynamic; + } + /** Returns a copy of this function with a given name. */ public SqlBasicFunction withName(String name) { return new SqlBasicFunction(name, kind, syntax, deterministic, getReturnTypeInference(), getOperandTypeInference(), operandHandler, getOperandTypeChecker(), callValidator, - getFunctionType(), monotonicityInference); + getFunctionType(), monotonicityInference, dynamic); } /** Returns a copy of this function with a given kind. */ @@ -164,14 +171,14 @@ public SqlBasicFunction withKind(SqlKind kind) { return new SqlBasicFunction(getName(), kind, syntax, deterministic, getReturnTypeInference(), getOperandTypeInference(), operandHandler, getOperandTypeChecker(), callValidator, - getFunctionType(), monotonicityInference); + getFunctionType(), monotonicityInference, dynamic); } /** Returns a copy of this function with a given category. */ public SqlBasicFunction withFunctionType(SqlFunctionCategory category) { return new SqlBasicFunction(getName(), kind, syntax, deterministic, getReturnTypeInference(), getOperandTypeInference(), operandHandler, - getOperandTypeChecker(), callValidator, category, monotonicityInference); + getOperandTypeChecker(), callValidator, category, monotonicityInference, dynamic); } /** Returns a copy of this function with a given syntax. */ @@ -179,7 +186,7 @@ public SqlBasicFunction withSyntax(SqlSyntax syntax) { return new SqlBasicFunction(getName(), kind, syntax, deterministic, getReturnTypeInference(), getOperandTypeInference(), operandHandler, getOperandTypeChecker(), callValidator, - getFunctionType(), monotonicityInference); + getFunctionType(), monotonicityInference, dynamic); } /** Returns a copy of this function with a given strategy for inferring @@ -189,7 +196,7 @@ public SqlBasicFunction withReturnTypeInference( return new SqlBasicFunction(getName(), kind, syntax, deterministic, returnTypeInference, getOperandTypeInference(), operandHandler, getOperandTypeChecker(), callValidator, - getFunctionType(), monotonicityInference); + getFunctionType(), monotonicityInference, dynamic); } /** Returns a copy of this function with a given strategy for inferring @@ -199,7 +206,7 @@ public SqlBasicFunction withOperandTypeInference( return new SqlBasicFunction(getName(), kind, syntax, deterministic, getReturnTypeInference(), operandTypeInference, operandHandler, getOperandTypeChecker(), callValidator, - getFunctionType(), monotonicityInference); + getFunctionType(), monotonicityInference, dynamic); } /** Returns a copy of this function with a given strategy for handling @@ -208,14 +215,14 @@ public SqlBasicFunction withOperandHandler(SqlOperandHandler operandHandler) { return new SqlBasicFunction(getName(), kind, syntax, deterministic, getReturnTypeInference(), getOperandTypeInference(), operandHandler, getOperandTypeChecker(), callValidator, - getFunctionType(), monotonicityInference); + getFunctionType(), monotonicityInference, dynamic); } /** Returns a copy of this function with a given determinism. */ public SqlBasicFunction withDeterministic(boolean deterministic) { return new SqlBasicFunction(getName(), kind, syntax, deterministic, getReturnTypeInference(), getOperandTypeInference(), operandHandler, getOperandTypeChecker(), callValidator, - getFunctionType(), monotonicityInference); + getFunctionType(), monotonicityInference, dynamic); } /** Returns a copy of this function with a given strategy for inferring @@ -225,13 +232,27 @@ public SqlBasicFunction withMonotonicityInference( return new SqlBasicFunction(getName(), kind, syntax, deterministic, getReturnTypeInference(), getOperandTypeInference(), operandHandler, getOperandTypeChecker(), callValidator, - getFunctionType(), monotonicityInference); + getFunctionType(), monotonicityInference, dynamic); + } + + public SqlBasicFunction withValidation(int callValidator) { + return new SqlBasicFunction(getName(), kind, syntax, deterministic, + getReturnTypeInference(), getOperandTypeInference(), operandHandler, + getOperandTypeChecker(), callValidator, + getFunctionType(), monotonicityInference, dynamic); } - public SqlFunction withValidation(int callValidator) { + public SqlBasicFunction withDynamic(boolean dynamic) { return new SqlBasicFunction(getName(), kind, syntax, deterministic, getReturnTypeInference(), getOperandTypeInference(), operandHandler, getOperandTypeChecker(), callValidator, - getFunctionType(), monotonicityInference); + getFunctionType(), monotonicityInference, dynamic); + } + + public SqlBasicFunction withOperandTypeChecker(SqlOperandTypeChecker operandTypeChecker) { + return new SqlBasicFunction(getName(), kind, syntax, deterministic, + getReturnTypeInference(), getOperandTypeInference(), operandHandler, + operandTypeChecker, callValidator, + getFunctionType(), monotonicityInference, dynamic); } } 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 0c700d9de322..b32658f86638 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 @@ -2311,4 +2311,10 @@ private static RelDataType deriveTypeMapFromEntries(SqlOperatorBinding opBinding @LibraryOperator(libraries = {SPARK}) public static final SqlFunction GETBIT = BIT_GET.withName("GETBIT"); + + /** The RANDOM() function. Equivalent to RAND(). */ + @LibraryOperator(libraries = {POSTGRESQL}) + public static final SqlFunction RANDOM = SqlStdOperatorTable.RAND + .withName("RANDOM") + .withOperandTypeChecker(OperandTypes.NILADIC); } diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlRandFunction.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlRandFunction.java deleted file mode 100644 index 096d6f97378b..000000000000 --- a/core/src/main/java/org/apache/calcite/sql/fun/SqlRandFunction.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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.sql.SqlFunction; -import org.apache.calcite.sql.SqlFunctionCategory; -import org.apache.calcite.sql.SqlKind; -import org.apache.calcite.sql.type.OperandTypes; -import org.apache.calcite.sql.type.ReturnTypes; - -/** - * The RAND function. There are two overloads: - * - *
    - *
  • RAND() returns a random double between 0 and 1 - *
  • RAND(seed) returns a random double between 0 and 1, initializing the - * random number generator with seed on first call - *
- */ -public class SqlRandFunction extends SqlFunction { - //~ Constructors ----------------------------------------------------------- - - public SqlRandFunction() { - super("RAND", - SqlKind.OTHER_FUNCTION, - ReturnTypes.DOUBLE, - null, - OperandTypes.NILADIC.or(OperandTypes.NUMERIC), - SqlFunctionCategory.NUMERIC); - } - - //~ Methods ---------------------------------------------------------------- - - // Plans referencing context variables should never be cached - @Override public boolean isDynamicFunction() { - return true; - } -} 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 bf40fb20bc52..93f510518bc5 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 @@ -317,7 +317,10 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable { /** The {@code RAND([seed])} function, which yields a random double, * optionally with seed. */ - public static final SqlRandFunction RAND = new SqlRandFunction(); + public static final SqlBasicFunction RAND = SqlBasicFunction + .create("RAND", ReturnTypes.DOUBLE, + OperandTypes.NILADIC.or(OperandTypes.NUMERIC), SqlFunctionCategory.NUMERIC) + .withDynamic(true); /** * Internal integer arithmetic division operator, '/INT'. This diff --git a/site/_docs/reference.md b/site/_docs/reference.md index d8712e52ab65..542b1f6af025 100644 --- a/site/_docs/reference.md +++ b/site/_docs/reference.md @@ -2815,6 +2815,7 @@ In the following: | b | PARSE_TIMESTAMP(format, string[, timeZone]) | Uses format specified by *format* to convert *string* representation of timestamp to a TIMESTAMP WITH LOCAL TIME ZONE value in *timeZone* | h s | PARSE_URL(urlString, partToExtract [, keyToExtract] ) | Returns the specified *partToExtract* from the *urlString*. Valid values for *partToExtract* include HOST, PATH, QUERY, REF, PROTOCOL, AUTHORITY, FILE, and USERINFO. *keyToExtract* specifies which query to extract | b s | POW(numeric1, numeric2) | Returns *numeric1* raised to the power *numeric2* +| p | RANDOM() | Generates a random double between 0 and 1 inclusive | s | REGEXP(string, regexp) | Equivalent to `string1 RLIKE string2` | b | REGEXP_CONTAINS(string, regexp) | Returns whether *string* is a partial match for the *regexp* | b | REGEXP_EXTRACT(string, regexp [, position [, occurrence]]) | Returns the substring in *string* that matches the *regexp*, starting search at *position* (default 1), and until locating the nth *occurrence* (default 1). Returns NULL if there is no match 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 01f308adfe0d..5a772c86a235 100644 --- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java +++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java @@ -6359,6 +6359,22 @@ void checkRegexpExtract(SqlOperatorFixture f0, FunctionAlias functionAlias) { } } + /** Test case for + * [CALCITE-6314] + * Add RANDOM function (enabled in Postgres library). */ + @Test void testRandomFunc() { + final SqlOperatorFixture f = fixture(); + f.setFor(SqlLibraryOperators.RANDOM, VmName.EXPAND); + f.checkFails("^random^", "Column 'RANDOM' not found in any table", false); + Consumer consumer = fixture -> { + for (int i = 0; i < 100; i++) { + // Result must always be between 0 and 1, inclusive. + fixture.checkScalarApprox("random()", "DOUBLE NOT NULL", isWithin(0.5, 0.5)); + } + }; + f.forEachLibrary(list(SqlLibrary.POSTGRESQL), consumer); + } + @Test void testRandIntegerSeedFunc() { final SqlOperatorFixture f = fixture(); f.setFor(SqlStdOperatorTable.RAND_INTEGER, VmName.EXPAND);