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

xiong 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 3ef0999d39 [CALCITE-6478] JSON functions should return NULL when input 
is NULL
3ef0999d39 is described below

commit 3ef0999d392cef9484d17d761cd2dec56d7ff122
Author: Xiong Duan <[email protected]>
AuthorDate: Tue Jul 30 18:38:00 2024 +0800

    [CALCITE-6478] JSON functions should return NULL when input is NULL
---
 .../calcite/adapter/enumerable/RexImpTable.java    | 42 +++++++++++++++++++---
 .../org/apache/calcite/runtime/JsonFunctions.java  | 20 ++++++++---
 .../org/apache/calcite/runtime/SqlFunctions.java   |  2 +-
 .../calcite/sql/fun/SqlStdOperatorTable.java       | 16 ++++-----
 .../apache/calcite/test/SqlJsonFunctionsTest.java  |  4 +++
 site/_docs/reference.md                            |  4 +++
 .../org/apache/calcite/test/SqlOperatorTest.java   |  8 +++++
 7 files changed, 79 insertions(+), 17 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 55ef175c67..d3f49d4349 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
@@ -1042,19 +1042,19 @@ public class RexImpTable {
           new MethodImplementor(BuiltInMethod.IS_JSON_SCALAR.method,
               NullPolicy.NONE, false));
       map.put(IS_NOT_JSON_VALUE,
-          NotImplementor.of(
+          NotJsonImplementor.of(
               new MethodImplementor(BuiltInMethod.IS_JSON_VALUE.method,
                   NullPolicy.NONE, false)));
       map.put(IS_NOT_JSON_OBJECT,
-          NotImplementor.of(
+          NotJsonImplementor.of(
               new MethodImplementor(BuiltInMethod.IS_JSON_OBJECT.method,
                   NullPolicy.NONE, false)));
       map.put(IS_NOT_JSON_ARRAY,
-          NotImplementor.of(
+          NotJsonImplementor.of(
               new MethodImplementor(BuiltInMethod.IS_JSON_ARRAY.method,
                   NullPolicy.NONE, false)));
       map.put(IS_NOT_JSON_SCALAR,
-          NotImplementor.of(
+          NotJsonImplementor.of(
               new MethodImplementor(BuiltInMethod.IS_JSON_SCALAR.method,
                   NullPolicy.NONE, false)));
 
@@ -3674,6 +3674,40 @@ public class RexImpTable {
     }
   }
 
+  /** Implementor for the {@code NOT JSON} operator. */
+  private static class NotJsonImplementor extends AbstractRexCallImplementor {
+    private final AbstractRexCallImplementor implementor;
+
+    private NotJsonImplementor(AbstractRexCallImplementor implementor) {
+      super("not_json", implementor.nullPolicy, false);
+      this.implementor = implementor;
+    }
+
+    static AbstractRexCallImplementor of(AbstractRexCallImplementor 
implementor) {
+      return new NotJsonImplementor(implementor);
+    }
+
+    @Override Expression implementSafe(final RexToLixTranslator translator,
+        final RexCall call, final List<Expression> argValueList) {
+      // E.g., "final Boolean resultValue = (callValue == null) ? null : 
!callValue"
+      final Expression expression =
+          implementor.implementSafe(translator, call, argValueList);
+      final ParameterExpression callValue =
+          Expressions.parameter(expression.getType());
+      translator.getBlockBuilder().add(
+          Expressions.declare(Modifier.FINAL, callValue, expression));
+      final Expression valueExpression =
+          Expressions.condition(
+              Expressions.equal(callValue, NULL_EXPR),
+              NULL_EXPR,
+              Expressions.not(callValue));
+      final ParameterExpression resultValue = 
Expressions.parameter(expression.getType());
+      translator.getBlockBuilder().add(
+          Expressions.declare(Modifier.FINAL, resultValue, valueExpression));
+      return resultValue;
+    }
+  }
+
   /** Implementor for various datetime arithmetic. */
   private static class DatetimeArithmeticImplementor
       extends AbstractRexCallImplementor {
diff --git a/core/src/main/java/org/apache/calcite/runtime/JsonFunctions.java 
b/core/src/main/java/org/apache/calcite/runtime/JsonFunctions.java
index b77633924f..b613ad1d98 100644
--- a/core/src/main/java/org/apache/calcite/runtime/JsonFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/JsonFunctions.java
@@ -833,7 +833,10 @@ public class JsonFunctions {
     }
   }
 
-  public static boolean isJsonValue(String input) {
+  public static @Nullable Boolean isJsonValue(@Nullable String input) {
+    if (input == null) {
+      return null;
+    }
     try {
       dejsonize(input);
       return true;
@@ -842,7 +845,10 @@ public class JsonFunctions {
     }
   }
 
-  public static boolean isJsonObject(String input) {
+  public static @Nullable Boolean isJsonObject(@Nullable String input) {
+    if (input == null) {
+      return null;
+    }
     try {
       Object o = dejsonize(input);
       return o instanceof Map;
@@ -851,7 +857,10 @@ public class JsonFunctions {
     }
   }
 
-  public static boolean isJsonArray(String input) {
+  public static @Nullable Boolean isJsonArray(@Nullable String input) {
+    if (input == null) {
+      return null;
+    }
     try {
       Object o = dejsonize(input);
       return o instanceof Collection;
@@ -860,7 +869,10 @@ public class JsonFunctions {
     }
   }
 
-  public static boolean isJsonScalar(String input) {
+  public static @Nullable Boolean isJsonScalar(@Nullable String input) {
+    if (input == null) {
+      return null;
+    }
     try {
       Object o = dejsonize(input);
       return !(o instanceof Map) && !(o instanceof Collection);
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 ff51fc6b66..456977d1ad 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
@@ -996,7 +996,7 @@ public class SqlFunctions {
   public static boolean containsSubstr(Object expr, String substr) {
     expr = normalize(expr.toString());
     substr = normalize(substr);
-    if (JsonFunctions.isJsonObject(expr.toString())) {
+    if (Boolean.TRUE.equals(JsonFunctions.isJsonObject(expr.toString()))) {
       return containsSubstr(expr.toString(), substr, "JSON_VALUES");
     }
     return ((String) expr).contains(substr);
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 dc2489664f..0254e2a219 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
@@ -810,7 +810,7 @@ public class SqlStdOperatorTable extends 
ReflectiveSqlOperatorTable {
           "IS JSON VALUE",
           SqlKind.OTHER,
           28,
-          ReturnTypes.BOOLEAN,
+          ReturnTypes.BOOLEAN_NULLABLE,
           null,
           OperandTypes.CHARACTER);
 
@@ -819,7 +819,7 @@ public class SqlStdOperatorTable extends 
ReflectiveSqlOperatorTable {
           "IS NOT JSON VALUE",
           SqlKind.OTHER,
           28,
-          ReturnTypes.BOOLEAN,
+          ReturnTypes.BOOLEAN_NULLABLE,
           null,
           OperandTypes.CHARACTER);
 
@@ -828,7 +828,7 @@ public class SqlStdOperatorTable extends 
ReflectiveSqlOperatorTable {
           "IS JSON OBJECT",
           SqlKind.OTHER,
           28,
-          ReturnTypes.BOOLEAN,
+          ReturnTypes.BOOLEAN_NULLABLE,
           null,
           OperandTypes.CHARACTER);
 
@@ -837,7 +837,7 @@ public class SqlStdOperatorTable extends 
ReflectiveSqlOperatorTable {
           "IS NOT JSON OBJECT",
           SqlKind.OTHER,
           28,
-          ReturnTypes.BOOLEAN,
+          ReturnTypes.BOOLEAN_NULLABLE,
           null,
           OperandTypes.CHARACTER);
 
@@ -846,7 +846,7 @@ public class SqlStdOperatorTable extends 
ReflectiveSqlOperatorTable {
           "IS JSON ARRAY",
           SqlKind.OTHER,
           28,
-          ReturnTypes.BOOLEAN,
+          ReturnTypes.BOOLEAN_NULLABLE,
           null,
           OperandTypes.CHARACTER);
 
@@ -855,7 +855,7 @@ public class SqlStdOperatorTable extends 
ReflectiveSqlOperatorTable {
           "IS NOT JSON ARRAY",
           SqlKind.OTHER,
           28,
-          ReturnTypes.BOOLEAN,
+          ReturnTypes.BOOLEAN_NULLABLE,
           null,
           OperandTypes.CHARACTER);
 
@@ -864,7 +864,7 @@ public class SqlStdOperatorTable extends 
ReflectiveSqlOperatorTable {
           "IS JSON SCALAR",
           SqlKind.OTHER,
           28,
-          ReturnTypes.BOOLEAN,
+          ReturnTypes.BOOLEAN_NULLABLE,
           null,
           OperandTypes.CHARACTER);
 
@@ -873,7 +873,7 @@ public class SqlStdOperatorTable extends 
ReflectiveSqlOperatorTable {
           "IS NOT JSON SCALAR",
           SqlKind.OTHER,
           28,
-          ReturnTypes.BOOLEAN,
+          ReturnTypes.BOOLEAN_NULLABLE,
           null,
           OperandTypes.CHARACTER);
 
diff --git 
a/core/src/test/java/org/apache/calcite/test/SqlJsonFunctionsTest.java 
b/core/src/test/java/org/apache/calcite/test/SqlJsonFunctionsTest.java
index 0879bdee0a..2786eabcdd 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlJsonFunctionsTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlJsonFunctionsTest.java
@@ -601,18 +601,22 @@ class SqlJsonFunctionsTest {
     assertIsJsonValue("{}", is(true));
     assertIsJsonValue("100", is(true));
     assertIsJsonValue("{]", is(false));
+    assertIsJsonValue(null, nullValue());
     assertIsJsonObject("[]", is(false));
     assertIsJsonObject("{}", is(true));
     assertIsJsonObject("100", is(false));
     assertIsJsonObject("{]", is(false));
+    assertIsJsonObject(null, nullValue());
     assertIsJsonArray("[]", is(true));
     assertIsJsonArray("{}", is(false));
     assertIsJsonArray("100", is(false));
     assertIsJsonArray("{]", is(false));
+    assertIsJsonArray(null, nullValue());
     assertIsJsonScalar("[]", is(false));
     assertIsJsonScalar("{}", is(false));
     assertIsJsonScalar("100", is(true));
     assertIsJsonScalar("{]", is(false));
+    assertIsJsonScalar(null, nullValue());
   }
 
   @Test public void testJsonInsert() {
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index b3565bc1fa..fe8ff90365 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -2646,6 +2646,10 @@ Note:
 | jsonValue IS JSON ARRAY           | Whether *jsonValue* is a JSON array
 | jsonValue IS NOT JSON ARRAY       | Whether *jsonValue* is not a JSON array
 
+Note:
+
+* If the *jsonValue* is `NULL`, the function will return `NULL`.
+
 ### Dialect-specific Operators
 
 The following operators are not in the SQL standard, and are not enabled in
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 12d3961289..d3679c0ab1 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -6732,6 +6732,14 @@ public class SqlOperatorTest {
     f.checkBoolean("'[]' is not json array", false);
     f.checkBoolean("'100' is not json scalar", false);
     f.checkBoolean("'[]' is not json scalar", true);
+    f.checkNull("null is json value");
+    f.checkNull("null is json object");
+    f.checkNull("null is json array");
+    f.checkNull("null is json scalar");
+    f.checkNull("null is not json value");
+    f.checkNull("null is not json object");
+    f.checkNull("null is not json array");
+    f.checkNull("null is not json scalar");
   }
 
   @Test void testCompress() {

Reply via email to