This is an automated email from the ASF dual-hosted git repository.

mbudiu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git


The following commit(s) were added to refs/heads/main by this push:
     new f68af8cd9f [CALCITE-6445] Add REVERSE function (enabled in Spark 
library)
f68af8cd9f is described below

commit f68af8cd9fd2bcab302d9150f7f246b545b8a85f
Author: YiwenWu <[email protected]>
AuthorDate: Mon Jul 8 13:11:03 2024 +0800

    [CALCITE-6445] Add REVERSE function (enabled in Spark library)
---
 babel/src/test/resources/sql/spark.iq              | 93 +++++++++++++++++++++
 .../calcite/adapter/enumerable/RexImpTable.java    |  3 +
 .../main/java/org/apache/calcite/sql/SqlKind.java  |  3 +
 .../calcite/sql/fun/SqlLibraryOperators.java       |  9 ++
 .../org/apache/calcite/sql/type/ReturnTypes.java   | 14 ++++
 site/_docs/reference.md                            |  1 +
 .../org/apache/calcite/test/SqlOperatorTest.java   | 95 +++++++++++++++-------
 7 files changed, 188 insertions(+), 30 deletions(-)

diff --git a/babel/src/test/resources/sql/spark.iq 
b/babel/src/test/resources/sql/spark.iq
index 97ba83664f..668d15409e 100644
--- a/babel/src/test/resources/sql/spark.iq
+++ b/babel/src/test/resources/sql/spark.iq
@@ -355,4 +355,97 @@ EXPR$0
 !ok
 
 
+#####################################################################
+# REVERSE
+#
+# REVERSE(str | array)
+# Returns a reversed string or an array with reverse order of elements.
+#
+# Returns STRING or ARRAY
+
+SELECT REVERSE('Spark SQL');
+EXPR$0
+LQS krapS
+!ok
+
+SELECT REVERSE('#c@b!a');
+EXPR$0
+a!b@c#
+!ok
+
+SELECT REVERSE(5);
+EXPR$0
+5
+!ok
+
+SELECT REVERSE(array(2, 1, 4, 3));
+EXPR$0
+[3, 4, 1, 2]
+!ok
+
+SELECT REVERSE(NULL);
+EXPR$0
+null
+!ok
+
+SELECT REVERSE(cast(NULL as tinyint));
+EXPR$0
+null
+!ok
+
+SELECT REVERSE(array(NULL));
+EXPR$0
+[null]
+!ok
+
+SELECT REVERSE(cast(NULL as integer array));
+EXPR$0
+null
+!ok
+
+SELECT REVERSE(array(cast(NULL as tinyint)));
+EXPR$0
+[null]
+!ok
+
+SELECT REVERSE('');
+EXPR$0
+
+!ok
+
+SELECT REVERSE(array(cast(1 as tinyint), 2));
+EXPR$0
+[2, 1]
+!ok
+
+SELECT REVERSE(array(2.1, 2.2));
+EXPR$0
+[2.2, 2.1]
+!ok
+
+SELECT REVERSE(array(2.1, NULL));
+EXPR$0
+[null, 2.1]
+!ok
+
+SELECT REVERSE(array(2, CAST(2.2 AS double)));
+EXPR$0
+[2.2, 2.0]
+!ok
+
+SELECT REVERSE(array(2, CAST(NULL AS double)));
+EXPR$0
+[null, 2.0]
+!ok
+
+SELECT REVERSE(array(CAST(2.1 as decimal(17)), 2.1111111111111119));
+EXPR$0
+[2.1111111111111119, 2.1]
+!ok
+
+SELECT REVERSE(array(CAST(2.1 as double), 2.1111111111111119));
+EXPR$0
+[2.111111111111112, 2.1]
+!ok
+
 # End spark.iq
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 3e6088d626..da7ef8eaf6 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
@@ -249,6 +249,7 @@ import static 
org.apache.calcite.sql.fun.SqlLibraryOperators.REGEXP_LIKE;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.REGEXP_REPLACE;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.REPEAT;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.REVERSE;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.REVERSE_SPARK;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.RIGHT;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.RLIKE;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.RPAD;
@@ -611,6 +612,8 @@ public class RexImpTable {
       defineMethod(SOUNDEX_SPARK, BuiltInMethod.SOUNDEX_SPARK.method, 
NullPolicy.STRICT);
       defineMethod(DIFFERENCE, BuiltInMethod.DIFFERENCE.method, 
NullPolicy.STRICT);
       defineMethod(REVERSE, BuiltInMethod.REVERSE.method, NullPolicy.STRICT);
+      defineReflective(REVERSE_SPARK, BuiltInMethod.REVERSE.method,
+          BuiltInMethod.ARRAY_REVERSE.method);
       defineMethod(LEVENSHTEIN, BuiltInMethod.LEVENSHTEIN.method, 
NullPolicy.STRICT);
       defineMethod(SPLIT, BuiltInMethod.SPLIT.method, NullPolicy.STRICT);
       defineReflective(PARSE_URL, BuiltInMethod.PARSE_URL2.method,
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 9f64e13c5a..808c85c368 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlKind.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlKind.java
@@ -810,6 +810,9 @@ public enum SqlKind {
   /** {@code REVERSE} function (SQL Server, MySQL). */
   REVERSE,
 
+  /** {@code REVERSE} function (Spark semantics). */
+  REVERSE_SPARK,
+
   /** {@code SOUNDEX} function (Spark semantics). */
   SOUNDEX_SPARK,
 
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 5c876e0614..061a1025ae 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
@@ -1639,6 +1639,15 @@ public abstract class SqlLibraryOperators {
           OperandTypes.CHARACTER)
           .withFunctionType(SqlFunctionCategory.STRING);
 
+  /** The "REVERSE(string|array)" function. */
+  @LibraryOperator(libraries = {SPARK})
+  public static final SqlFunction REVERSE_SPARK =
+      SqlBasicFunction.create(SqlKind.REVERSE,
+              ReturnTypes.ARG0_ARRAY_NULLABLE_VARYING,
+              OperandTypes.CHARACTER.or(OperandTypes.ARRAY))
+          .withFunctionType(SqlFunctionCategory.STRING)
+          .withKind(SqlKind.REVERSE_SPARK);
+
   /** The "LEVENSHTEIN(string1, string2)" function. */
   @LibraryOperator(libraries = {HIVE, SPARK})
   public static final SqlFunction LEVENSHTEIN =
diff --git a/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java 
b/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java
index 40b5353514..f8aaff072c 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java
@@ -178,6 +178,20 @@ public abstract class ReturnTypes {
   public static final SqlReturnTypeInference ARG0_NULLABLE =
       ARG0.andThen(SqlTypeTransforms.TO_NULLABLE);
 
+  /**
+   * Type-inference strategy that determines the return type based on the 
first argument.
+   * If the first argument is an array, the return type is consistent with 
{@link #ARG0_NULLABLE}.
+   * If the first argument is not an array,
+   * the return type is consistent with {@link #ARG0_NULLABLE_VARYING}.
+   */
+  public static final SqlReturnTypeInference ARG0_ARRAY_NULLABLE_VARYING = 
opBinding -> {
+    SqlTypeName op = opBinding.getOperandType(0).getSqlTypeName();
+    if (op == SqlTypeName.ARRAY) {
+      return ARG0_NULLABLE.inferReturnType(opBinding);
+    }
+    return ARG0_NULLABLE_VARYING.inferReturnType(opBinding);
+  };
+
   /**
    * Type-inference strategy whereby the result type of a call is the type of
    * the operand #0 (0-based). If the operand #0 (0-based) is nullable, the
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index dc80e67ed9..3e0a51eb45 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -2851,6 +2851,7 @@ In the following:
 | b | REGEXP_SUBSTR(string, regexp [, position [, occurrence]]) | Synonym for 
REGEXP_EXTRACT
 | b m p r s | REPEAT(string, integer)                | Returns a string 
consisting of *string* repeated of *integer* times; returns an empty string if 
*integer* is less than 1
 | b m | REVERSE(string)                              | Returns *string* with 
the order of the characters reversed
+| s | REVERSE(string | array)                        | Returns *string* with 
the characters in reverse order or array with elements in reverse order
 | b m p r s | RIGHT(string, length)                  | Returns the rightmost 
*length* characters from the *string*
 | h m s | string1 RLIKE string2                      | Whether *string1* 
matches regex pattern *string2* (similar to `LIKE`, but uses Java regex)
 | h m s | string1 NOT RLIKE string2                  | Whether *string1* does 
not match regex pattern *string2* (similar to `NOT LIKE`, but uses Java regex)
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 3bea58f537..65e1a1cb96 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -5570,22 +5570,30 @@ public class SqlOperatorTest {
   }
 
   @Test void testReverseFunc() {
-    final SqlOperatorFixture f0 = 
fixture().setFor(SqlLibraryOperators.REVERSE);
+    final SqlFunction func = SqlLibraryOperators.REVERSE;
+    final SqlOperatorFixture f0 = fixture().setFor(func);
     f0.checkFails("^reverse('abc')^",
         "No match found for function signature REVERSE\\(<CHARACTER>\\)",
         false);
+    checReverseFunc(f0, func, list(SqlLibrary.BIG_QUERY, SqlLibrary.MYSQL));
+  }
+
+  void checReverseFunc(SqlOperatorFixture f0, SqlFunction function,
+      Iterable<? extends SqlLibrary> libraries) {
+    final String fn = function.getName();
     final Consumer<SqlOperatorFixture> consumer = f -> {
-      f.checkString("reverse('')", "", "VARCHAR(0) NOT NULL");
-      f.checkString("reverse('123')", "321", "VARCHAR(3) NOT NULL");
-      f.checkString("reverse('abc')", "cba", "VARCHAR(3) NOT NULL");
-      f.checkString("reverse('ABC')", "CBA", "VARCHAR(3) NOT NULL");
-      f.checkString("reverse('Hello World')", "dlroW olleH",
+      f.checkString(fn + "('')", "", "VARCHAR(0) NOT NULL");
+      f.checkString(fn + "('123')", "321", "VARCHAR(3) NOT NULL");
+      f.checkString(fn + "('abc')", "cba", "VARCHAR(3) NOT NULL");
+      f.checkString(fn + "('ABC')", "CBA", "VARCHAR(3) NOT NULL");
+      f.checkString(fn + "('Hello World')", "dlroW olleH",
           "VARCHAR(11) NOT NULL");
-      f.checkString("reverse(_UTF8'\u4F60\u597D')", "\u597D\u4F60",
+      f.checkString(fn + "(_UTF8'\u4F60\u597D')", "\u597D\u4F60",
           "VARCHAR(2) NOT NULL");
-      f.checkNull("reverse(cast(null as varchar(1)))");
+      f.checkString(fn + "('a!b@c#')", "#c@b!a", "VARCHAR(6) NOT NULL");
+      f.checkNull(fn + "(cast(null as varchar(1)))");
     };
-    f0.forEachLibrary(list(SqlLibrary.BIG_QUERY, SqlLibrary.MYSQL), consumer);
+    f0.forEachLibrary(libraries, consumer);
   }
 
   @Test void testLevenshtein() {
@@ -7642,30 +7650,57 @@ public class SqlOperatorTest {
 
   /** Tests {@code ARRAY_REVERSE} function from BigQuery. */
   @Test void testArrayReverseFunc() {
+    final SqlFunction func = SqlLibraryOperators.ARRAY_REVERSE;
     final SqlOperatorFixture f0 = fixture();
-    f0.setFor(SqlLibraryOperators.ARRAY_REVERSE);
+    f0.setFor(func);
     f0.checkFails("^array_reverse(array[1])^",
         "No match found for function signature ARRAY_REVERSE\\(<INTEGER 
ARRAY>\\)", false);
-    final SqlOperatorFixture f = f0.withLibrary(SqlLibrary.BIG_QUERY);
-    f.checkScalar("array_reverse(array[1])", "[1]",
-        "INTEGER NOT NULL ARRAY NOT NULL");
-    f.checkScalar("array_reverse(array[1, 2])", "[2, 1]",
-        "INTEGER NOT NULL ARRAY NOT NULL");
-    f.checkScalar("array_reverse(array[null, 1])", "[1, null]",
-        "INTEGER ARRAY NOT NULL");
-    // elements cast
-    f.checkScalar("array_reverse(array[cast(1 as tinyint), 2])", "[2, 1]",
-        "INTEGER NOT NULL ARRAY NOT NULL");
-    f.checkScalar("array_reverse(array[null, 1, cast(2 as tinyint)])", "[2, 1, 
null]",
-        "INTEGER ARRAY NOT NULL");
-    f.checkScalar("array_reverse(array[cast(1 as bigint), 2])", "[2, 1]",
-        "BIGINT NOT NULL ARRAY NOT NULL");
-    f.checkScalar("array_reverse(array[null, 1, cast(2 as bigint)])", "[2, 1, 
null]",
-        "BIGINT ARRAY NOT NULL");
-    f.checkScalar("array_reverse(array[cast(1 as decimal), 2])", "[2, 1]",
-        "DECIMAL(19, 0) NOT NULL ARRAY NOT NULL");
-    f.checkScalar("array_reverse(array[null, 1, cast(2 as decimal)])", "[2, 1, 
null]",
-        "DECIMAL(19, 0) ARRAY NOT NULL");
+    checkArrayReverseFunc(f0, func, list(SqlLibrary.BIG_QUERY));
+  }
+
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-6445";>[CALCITE-6445]
+   * Add REVERSE function (enabled in Spark library)</a>. */
+  @Test void testReverseSparkFunc() {
+    final SqlFunction func = SqlLibraryOperators.REVERSE_SPARK;
+    final SqlOperatorFixture f0 = fixture();
+    f0.setFor(func);
+    Iterable<SqlLibrary> libraries = list(SqlLibrary.SPARK);
+    checkArrayReverseFunc(f0, func, libraries);
+    checReverseFunc(f0, func, libraries);
+  }
+
+  void checkArrayReverseFunc(SqlOperatorFixture f0, SqlFunction function,
+      Iterable<? extends SqlLibrary> libraries) {
+    final String fn = function.getName();
+    final Consumer<SqlOperatorFixture> consumer = f -> {
+      f.checkScalar(fn + "(array[1])", "[1]",
+          "INTEGER NOT NULL ARRAY NOT NULL");
+      f.checkScalar(fn + "(array[1, 2])", "[2, 1]",
+          "INTEGER NOT NULL ARRAY NOT NULL");
+      f.checkScalar(fn + "(array[null, 1])", "[1, null]",
+          "INTEGER ARRAY NOT NULL");
+      // elements cast
+      f.checkScalar(fn + "(array[cast(1 as tinyint), 2])", "[2, 1]",
+          "INTEGER NOT NULL ARRAY NOT NULL");
+      f.checkScalar(fn + "(array[null, 1, cast(2 as tinyint)])", "[2, 1, 
null]",
+          "INTEGER ARRAY NOT NULL");
+      f.checkScalar(fn + "(array[cast(1 as bigint), 2])", "[2, 1]",
+          "BIGINT NOT NULL ARRAY NOT NULL");
+      f.checkScalar(fn + "(array[null, 1, cast(2 as bigint)])", "[2, 1, null]",
+          "BIGINT ARRAY NOT NULL");
+      f.checkScalar(fn + "(array[cast(1 as decimal), 2])", "[2, 1]",
+          "DECIMAL(19, 0) NOT NULL ARRAY NOT NULL");
+      f.checkScalar(fn + "(array[null, 1, cast(2 as decimal)])", "[2, 1, 
null]",
+          "DECIMAL(19, 0) ARRAY NOT NULL");
+      f.checkScalar(fn + "(array[CAST(2.1 as decimal(17)), 
2.1111111111111119])",
+          "[2.1111111111111119, 2.1]", "DECIMAL(19, 2) NOT NULL ARRAY NOT 
NULL");
+      f.checkScalar(fn + "(array[CAST(2.1 as double), 2.1111111111111119])",
+          "[2.111111111111112, 2.1]", "DOUBLE NOT NULL ARRAY NOT NULL");
+      f.checkScalar(fn + "(array[null])", "[null]", "NULL ARRAY NOT NULL");
+      f.checkNull(fn + "(cast(null as integer array))");
+    };
+    f0.forEachLibrary(libraries, consumer);
   }
 
   /** Tests {@code ARRAY_SIZE} function from Spark. */

Reply via email to