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 eb92f96a74 [CALCITE-6397] Add NVL2 function (enabled in Oracle, Spark 
library)
eb92f96a74 is described below

commit eb92f96a747b3ba1301c7ee8499392260e65faa0
Author: caicancai <[email protected]>
AuthorDate: Fri May 3 21:16:13 2024 +0800

    [CALCITE-6397] Add NVL2 function (enabled in Oracle, Spark library)
---
 .../main/java/org/apache/calcite/sql/SqlKind.java  |  7 ++--
 .../calcite/sql/fun/SqlLibraryOperators.java       |  8 +++++
 .../org/apache/calcite/sql/type/OperandTypes.java  | 13 +++++++
 .../org/apache/calcite/sql/type/ReturnTypes.java   | 10 ++++++
 .../calcite/sql2rel/StandardConvertletTable.java   | 24 +++++++++++++
 site/_docs/reference.md                            |  1 +
 .../org/apache/calcite/test/SqlOperatorTest.java   | 41 ++++++++++++++++++++++
 7 files changed, 102 insertions(+), 2 deletions(-)

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 ae44f495a9..30fc858e28 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlKind.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlKind.java
@@ -421,10 +421,13 @@ public enum SqlKind {
   /** {@code DECODE} function (Oracle). */
   DECODE,
 
-  /** {@code NVL} function (Oracle). */
+  /** {@code NVL} function (Oracle, Spark). */
   NVL,
 
-  /** {@code GREATEST} function (Oracle). */
+  /** {@code NVL2} function (Oracle, Spark). */
+  NVL2,
+
+  /** {@code GREATEST} function (Oracle, Spark). */
   GREATEST,
 
   /** The two-argument {@code CONCAT} function (Oracle). */
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 bf75070e48..efff32c25d 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
@@ -271,6 +271,14 @@ public abstract class SqlLibraryOperators {
               .andThen(SqlTypeTransforms.TO_NULLABLE_ALL),
           OperandTypes.SAME_SAME);
 
+  /** The "NVL2(value, value, value)" function. */
+  @LibraryOperator(libraries = {ORACLE, SPARK})
+  public static final SqlBasicFunction NVL2 =
+      SqlBasicFunction.create(SqlKind.NVL2,
+          ReturnTypes.NVL2_RESTRICTIVE
+              .andThen(SqlTypeTransforms.TO_NULLABLE_ALL),
+          OperandTypes.SECOND_THIRD_SAME);
+
   /** The "IFNULL(value, value)" function. */
   @LibraryOperator(libraries = {BIG_QUERY, SPARK})
   public static final SqlFunction IFNULL = NVL.withName("IFNULL");
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 c7a85cd66e..08b6301154 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
@@ -986,6 +986,19 @@ public abstract class OperandTypes {
 
   public static final SqlSingleOperandTypeChecker ANY_ANY =
       family(SqlTypeFamily.ANY, SqlTypeFamily.ANY);
+
+  /**
+   * Operand type-checking strategy where the second and third operands must 
be comparable.
+   * This is used when the operator has three operands and only the
+   * second and third operands need to be comparable.
+   */
+  public static final SqlSingleOperandTypeChecker SECOND_THIRD_SAME =
+      new SameOperandTypeChecker(3) {
+        @Override protected List<Integer> getOperandList(int operandCount) {
+          // Only check the second and third operands
+          return ImmutableList.of(1, 2);
+        }
+      };
   public static final SqlSingleOperandTypeChecker ANY_IGNORE =
       family(SqlTypeFamily.ANY, SqlTypeFamily.IGNORE);
   public static final SqlSingleOperandTypeChecker IGNORE_ANY =
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 eb7f9e4471..511fe2eff7 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
@@ -35,6 +35,7 @@ import org.apache.calcite.util.Glossary;
 import org.apache.calcite.util.Util;
 
 import java.util.AbstractList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.function.UnaryOperator;
 
@@ -551,6 +552,15 @@ public abstract class ReturnTypes {
       opBinding -> opBinding.getTypeFactory().leastRestrictive(
           opBinding.collectOperandTypes());
 
+  /**
+   * Type-inference strategy for NVL2 function. It returns the least 
restrictive type
+   * between the second and third operands.
+   */
+  public static final SqlReturnTypeInference NVL2_RESTRICTIVE = opBinding -> {
+    return opBinding.getTypeFactory().leastRestrictive(
+        Arrays.asList(opBinding.getOperandType(1), 
opBinding.getOperandType(2)));
+  };
+
   /**
    * Type-inference strategy that returns the type of the first operand, 
unless it
    * is an integer type, in which case the return type is DOUBLE.
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 f3360d2e31..82e1ceeaea 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
@@ -232,6 +232,7 @@ public class StandardConvertletTable extends 
ReflectiveConvertletTable {
         registerOp(operator, 
StandardConvertletTable::convertQuantifyOperator));
 
     registerOp(SqlLibraryOperators.NVL, StandardConvertletTable::convertNvl);
+    registerOp(SqlLibraryOperators.NVL2, StandardConvertletTable::convertNvl2);
     registerOp(SqlLibraryOperators.DECODE,
         StandardConvertletTable::convertDecode);
     registerOp(SqlLibraryOperators.IF, StandardConvertletTable::convertIf);
@@ -421,6 +422,29 @@ public class StandardConvertletTable extends 
ReflectiveConvertletTable {
                 operand1)));
   }
 
+  /** Converts a call to the {@code NVL2} function. */
+  private static RexNode convertNvl2(SqlRexContext cx, SqlCall call) {
+    final RexBuilder rexBuilder = cx.getRexBuilder();
+    final List<RexNode> operands =
+        convertOperands(cx, call, call.getOperandList(), 
SqlOperandTypeChecker.Consistency.NONE);
+    final RelDataType type = cx.getValidator().getValidatedNodeType(call);
+
+    // Create a CASE expression equivalent to the NVL2 function
+    // NVL2(x, y, z) is equivalent to CASE WHEN x IS NOT NULL THEN y ELSE z END
+    return rexBuilder.makeCall(type, SqlStdOperatorTable.CASE,
+        ImmutableList.of(
+            rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_NULL,
+                operands.get(0)),
+            rexBuilder.makeCast(
+                cx.getTypeFactory()
+                    .createTypeWithNullability(type, 
operands.get(1).getType().isNullable()),
+                operands.get(1)),
+            rexBuilder.makeCast(
+                cx.getTypeFactory()
+                    .createTypeWithNullability(type, 
operands.get(2).getType().isNullable()),
+                operands.get(2))));
+  }
+
   /** Converts a call to the INSTR function.
    * INSTR(string, substring, position, occurrence) is equivalent to
    * POSITION(substring, string, position, occurrence) */
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index 8a23f9338c..4faca2f7e6 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -2812,6 +2812,7 @@ In the following:
 | b m p s | MD5(string)                              | Calculates an MD5 
128-bit checksum of *string* and returns it as a hex string
 | m | MONTHNAME(date)                                | Returns the name, in 
the connection's locale, of the month in *datetime*; for example, it returns 
'二月' for both DATE '2020-02-10' and TIMESTAMP '2020-02-10 10:10:10'
 | o s | NVL(value1, value2)                          | Returns *value1* if 
*value1* is not null, otherwise *value2*
+| o s | NVL2(value1, value2, value3)                 | Returns *value2* if 
*value1* is not null, otherwise *value3*
 | b | OFFSET(index)                                  | When indexing an array, 
wrapping *index* in `OFFSET` returns the value at the 0-based *index*; throws 
error if *index* is out of bounds
 | b | ORDINAL(index)                                 | Similar to `OFFSET` 
except *index* begins at 1
 | b | PARSE_DATE(format, string)                     | Uses format specified 
by *format* to convert *string* representation of date to a DATE value
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 2ac1c50744..daf139a75c 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -10705,6 +10705,47 @@ public class SqlOperatorTest {
     checkNvl(f, FunctionAlias.of(SqlLibraryOperators.NVL));
   }
 
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-6397";>[CALCITE-6397]
+   * Add NVL2 function (enabled in Oracle, Spark library) </a>.
+   */
+  @Test void testNvl2Func() {
+    final SqlOperatorFixture f = fixture();
+    f.setFor(SqlLibraryOperators.NVL2, VmName.EXPAND);
+    f.checkFails("^nvl2(NULL, 2, 1)^",
+        "No match found for function signature "
+            + "NVL2\\(<NULL>, <NUMERIC>, <NUMERIC>\\)", false);
+
+    final Consumer<SqlOperatorFixture> consumer = f12 -> {
+      f12.checkScalar("nvl2(NULL, 2, 1)", "1", "INTEGER NOT NULL");
+      f12.checkScalar("nvl2(true, true, false)", true, "BOOLEAN NOT NULL");
+      f12.checkScalar("nvl2(false, true, false)", true, "BOOLEAN NOT NULL");
+      f12.checkScalar("nvl2(NULL, true, false)", false, "BOOLEAN NOT NULL");
+      f12.checkScalar("nvl2(3, 2, 1)", "2", "INTEGER NOT NULL");
+      f12.checkScalar("nvl2(3, 'a', 'b')", "a", "CHAR(1) NOT NULL");
+      f12.checkScalar("nvl2(NULL, 'a', 'b')", "b", "CHAR(1) NOT NULL");
+      f12.checkScalar("nvl2(NULL, 'ab', 'de')", "de", "CHAR(2) NOT NULL");
+      f12.checkScalar("nvl2('ab', 'abc', 'def')", "abc", "CHAR(3) NOT NULL");
+      f12.checkScalar("nvl2('a', 3, 2)", "3", "INTEGER NOT NULL");
+      f12.checkScalar("NVL2(NULL, 3.0, 4.0)", "4.0", "DECIMAL(2, 1) NOT NULL");
+      f12.checkScalar("NVL2('abc', 3.0, 4.0)", "3.0", "DECIMAL(2, 1) NOT 
NULL");
+      f12.checkScalar("NVL2(1, 3.0, 2.111)", "3.0", "DECIMAL(4, 3) NOT NULL");
+      f12.checkScalar("NVL2(NULL, 3.0, 2.111)", "2.111", "DECIMAL(4, 3) NOT 
NULL");
+      f12.checkScalar("NVL2(3.111, 3.1415926, 2.111)", "3.1415926", 
"DECIMAL(8, 7) NOT NULL");
+
+      f12.checkNull("nvl2('ab', CAST(NULL AS VARCHAR(6)), 'def')");
+      f12.checkNull("nvl2(NULL, 'abc', NULL)");
+      f12.checkNull("nvl2(NULL, NULL, NULL)");
+
+      f12.checkFails("^NVL2(2.0, 2.0, true)^", "Parameters must be of the same 
type", false);
+      f12.checkFails("^NVL2(NULL, 2.0, true)^", "Parameters must be of the 
same type", false);
+      f12.checkFails("^NVL2(2.0, 1, true)^", "Parameters must be of the same 
type", false);
+      f12.checkFails("^NVL2(NULL, 1, true)^", "Parameters must be of the same 
type", false);
+    };
+    f.forEachLibrary(list(SqlLibrary.ORACLE, SqlLibrary.SPARK), consumer);
+
+  }
+
   /** Tests the {@code NVL} and {@code IFNULL} operators. */
   void checkNvl(SqlOperatorFixture f0, FunctionAlias functionAlias) {
     final SqlFunction function = functionAlias.function;

Reply via email to