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

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

commit 1cc1eb3a477ce7687436024b4760cf1f04a80cbc
Author: xiaogang <[email protected]>
AuthorDate: Tue Sep 19 16:27:15 2023 +0800

    [CALCITE-5995] JSON_VALUE, JSON_EXISTS, JSON_QUERY functions should cache 
generated objects between calls
    
    Close apache/calcite#3432
---
 .../calcite/adapter/enumerable/RexImpTable.java    |  18 +-
 .../org/apache/calcite/runtime/JsonFunctions.java  | 399 +++++++++++----------
 .../org/apache/calcite/runtime/SqlFunctions.java   |   5 +-
 .../org/apache/calcite/util/BuiltInMethod.java     |  16 +-
 .../apache/calcite/test/SqlJsonFunctionsTest.java  | 121 ++++---
 5 files changed, 299 insertions(+), 260 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 de84ea7d71..8cd68d859e 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
@@ -905,10 +905,11 @@ public class RexImpTable {
           BuiltInMethod.JSON_VALUE_EXPRESSION.method, NullPolicy.STRICT);
       defineMethod(JSON_TYPE_OPERATOR,
           BuiltInMethod.JSON_VALUE_EXPRESSION.method, NullPolicy.STRICT);
-      defineMethod(JSON_EXISTS, BuiltInMethod.JSON_EXISTS.method, 
NullPolicy.ARG0);
+      defineReflective(JSON_EXISTS, BuiltInMethod.JSON_EXISTS2.method,
+          BuiltInMethod.JSON_EXISTS3.method);
       map.put(JSON_VALUE,
           new JsonValueImplementor(BuiltInMethod.JSON_VALUE.method));
-      defineMethod(JSON_QUERY, BuiltInMethod.JSON_QUERY.method, 
NullPolicy.ARG0);
+      defineReflective(JSON_QUERY, BuiltInMethod.JSON_QUERY.method);
       defineMethod(JSON_TYPE, BuiltInMethod.JSON_TYPE.method, NullPolicy.ARG0);
       defineMethod(JSON_DEPTH, BuiltInMethod.JSON_DEPTH.method, 
NullPolicy.ARG0);
       defineMethod(JSON_INSERT, BuiltInMethod.JSON_INSERT.method, 
NullPolicy.ARG0);
@@ -2778,7 +2779,6 @@ public class RexImpTable {
 
     @Override Expression implementSafe(RexToLixTranslator translator,
         RexCall call, List<Expression> argValueList) {
-      final Expression expression;
       final List<Expression> newOperands = new ArrayList<>();
       newOperands.add(argValueList.get(0));
       newOperands.add(argValueList.get(1));
@@ -2821,13 +2821,11 @@ public class RexImpTable {
       newOperands.add(defaultValueOnEmpty);
       newOperands.add(errorBehavior);
       newOperands.add(defaultValueOnError);
-      @SuppressWarnings("rawtypes")
-      final Class clazz = method.getDeclaringClass();
-      expression = EnumUtils.call(null, clazz, method.getName(), newOperands);
-
-      final Type returnType =
-          translator.typeFactory.getJavaClass(call.getType());
-      return EnumUtils.convert(expression, returnType);
+      List<Expression> argValueList0 =
+          EnumUtils.fromInternal(method.getParameterTypes(), newOperands);
+      final Expression target =
+          Expressions.new_(method.getDeclaringClass());
+      return Expressions.call(target, method, argValueList0);
     }
   }
 
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 c65f70c1f6..6cd9258633 100644
--- a/core/src/main/java/org/apache/calcite/runtime/JsonFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/JsonFunctions.java
@@ -16,6 +16,7 @@
  */
 package org.apache.calcite.runtime;
 
+import org.apache.calcite.linq4j.function.Deterministic;
 import org.apache.calcite.sql.SqlJsonConstructorNullClause;
 import org.apache.calcite.sql.SqlJsonExistsErrorBehavior;
 import org.apache.calcite.sql.SqlJsonQueryEmptyOrErrorBehavior;
@@ -27,6 +28,9 @@ import com.fasterxml.jackson.annotation.JsonValue;
 import com.fasterxml.jackson.core.PrettyPrinter;
 import com.fasterxml.jackson.core.util.DefaultIndenter;
 import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
 import com.jayway.jsonpath.Configuration;
 import com.jayway.jsonpath.DocumentContext;
 import com.jayway.jsonpath.InvalidPathException;
@@ -55,6 +59,7 @@ import java.util.Queue;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import static 
org.apache.calcite.config.CalciteSystemProperty.FUNCTION_LEVEL_CACHE_MAX_SIZE;
 import static org.apache.calcite.linq4j.Nullness.castNonNull;
 import static org.apache.calcite.util.Static.RESOURCE;
 
@@ -170,212 +175,232 @@ public class JsonFunctions {
     }
   }
 
-  public static @Nullable Boolean jsonExists(String input, String pathSpec) {
-    return jsonExists(jsonApiCommonSyntax(input, pathSpec));
-  }
 
-  public static @Nullable Boolean jsonExists(String input, String pathSpec,
-      SqlJsonExistsErrorBehavior errorBehavior) {
-    return jsonExists(jsonApiCommonSyntax(input, pathSpec), errorBehavior);
-  }
+  /** State for {@code JSON_EXISTS}, {@code JSON_VALUE}, {@code JSON_QUERY}.
+   *
+   * <p>Marked deterministic so that the code generator instantiates one once
+   * per query, not once per row. */
+  @Deterministic
+  public static class StatefulFunction {
+    private final LoadingCache<String, JsonValueContext> cache =
+        CacheBuilder.newBuilder()
+            .maximumSize(FUNCTION_LEVEL_CACHE_MAX_SIZE.value())
+            .build(CacheLoader.from(JsonFunctions::jsonValueExpression));
 
-  public static @Nullable Boolean jsonExists(JsonValueContext input, String 
pathSpec) {
-    return jsonExists(jsonApiCommonSyntax(input, pathSpec));
-  }
+    public JsonPathContext jsonApiCommonSyntaxWithCache(String input,
+        String pathSpec) {
+      return jsonApiCommonSyntax(cache.getUnchecked(input), pathSpec);
+    }
 
-  public static @Nullable Boolean jsonExists(JsonValueContext input, String 
pathSpec,
-      SqlJsonExistsErrorBehavior errorBehavior) {
-    return jsonExists(jsonApiCommonSyntax(input, pathSpec), errorBehavior);
-  }
+    public @Nullable Boolean jsonExists(String input, String pathSpec) {
+      return jsonExists(jsonApiCommonSyntaxWithCache(input, pathSpec));
+    }
 
-  public static @Nullable Boolean jsonExists(JsonPathContext context) {
-    return jsonExists(context, SqlJsonExistsErrorBehavior.FALSE);
-  }
+    public @Nullable Boolean jsonExists(String input, String pathSpec,
+        SqlJsonExistsErrorBehavior errorBehavior) {
+      return jsonExists(jsonApiCommonSyntaxWithCache(input, pathSpec),
+          errorBehavior);
+    }
 
-  public static @Nullable Boolean jsonExists(JsonPathContext context,
-      SqlJsonExistsErrorBehavior errorBehavior) {
-    if (context.hasException()) {
-      switch (errorBehavior) {
-      case TRUE:
-        return Boolean.TRUE;
-      case FALSE:
-        return Boolean.FALSE;
-      case ERROR:
-        throw toUnchecked(context.exc);
-      case UNKNOWN:
-        return null;
-      default:
-        throw RESOURCE.illegalErrorBehaviorInJsonExistsFunc(
-            errorBehavior.toString()).ex();
-      }
-    } else {
-      return context.obj != null;
-    }
-  }
-
-  public static @Nullable Object jsonValue(String input,
-      String pathSpec,
-      SqlJsonValueEmptyOrErrorBehavior emptyBehavior,
-      Object defaultValueOnEmpty,
-      SqlJsonValueEmptyOrErrorBehavior errorBehavior,
-      Object defaultValueOnError) {
-    return jsonValue(
-        jsonApiCommonSyntax(input, pathSpec),
-        emptyBehavior,
-        defaultValueOnEmpty,
-        errorBehavior,
-        defaultValueOnError);
-  }
-
-  public static @Nullable Object jsonValue(JsonValueContext input,
-      String pathSpec,
-      SqlJsonValueEmptyOrErrorBehavior emptyBehavior,
-      Object defaultValueOnEmpty,
-      SqlJsonValueEmptyOrErrorBehavior errorBehavior,
-      Object defaultValueOnError) {
-    return jsonValue(
-        jsonApiCommonSyntax(input, pathSpec),
-        emptyBehavior,
-        defaultValueOnEmpty,
-        errorBehavior,
-        defaultValueOnError);
-  }
-
-  public static @Nullable Object jsonValue(JsonPathContext context,
-      SqlJsonValueEmptyOrErrorBehavior emptyBehavior,
-      Object defaultValueOnEmpty,
-      SqlJsonValueEmptyOrErrorBehavior errorBehavior,
-      Object defaultValueOnError) {
-    final Exception exc;
-    if (context.hasException()) {
-      exc = context.exc;
-    } else {
-      Object value = context.obj;
-      if (value == null || context.mode == PathMode.LAX
-          && !isScalarObject(value)) {
-        switch (emptyBehavior) {
+    public @Nullable Boolean jsonExists(JsonValueContext input,
+        String pathSpec) {
+      return jsonExists(jsonApiCommonSyntax(input, pathSpec));
+    }
+
+    public @Nullable Boolean jsonExists(JsonValueContext input, String 
pathSpec,
+        SqlJsonExistsErrorBehavior errorBehavior) {
+      return jsonExists(jsonApiCommonSyntax(input, pathSpec), errorBehavior);
+    }
+
+    public @Nullable Boolean jsonExists(JsonPathContext context) {
+      return jsonExists(context, SqlJsonExistsErrorBehavior.FALSE);
+    }
+
+    public @Nullable Boolean jsonExists(JsonPathContext context,
+        SqlJsonExistsErrorBehavior errorBehavior) {
+      if (context.hasException()) {
+        switch (errorBehavior) {
+        case TRUE:
+          return Boolean.TRUE;
+        case FALSE:
+          return Boolean.FALSE;
         case ERROR:
-          throw RESOURCE.emptyResultOfJsonValueFuncNotAllowed().ex();
-        case NULL:
+          throw toUnchecked(context.exc);
+        case UNKNOWN:
           return null;
-        case DEFAULT:
-          return defaultValueOnEmpty;
         default:
-          throw RESOURCE.illegalEmptyBehaviorInJsonValueFunc(
-              emptyBehavior.toString()).ex();
+          throw RESOURCE.illegalErrorBehaviorInJsonExistsFunc(
+              errorBehavior.toString()).ex();
         }
-      } else if (context.mode == PathMode.STRICT
-          && !isScalarObject(value)) {
-        exc =
-            RESOURCE.scalarValueRequiredInStrictModeOfJsonValueFunc(
-                value.toString()).ex();
       } else {
-        return value;
+        return context.obj != null;
       }
     }
-    switch (errorBehavior) {
-    case ERROR:
-      throw toUnchecked(exc);
-    case NULL:
-      return null;
-    case DEFAULT:
-      return defaultValueOnError;
-    default:
-      throw RESOURCE.illegalErrorBehaviorInJsonValueFunc(
-          errorBehavior.toString()).ex();
-    }
-  }
-
-  public static @Nullable String jsonQuery(String input,
-      String pathSpec,
-      SqlJsonQueryWrapperBehavior wrapperBehavior,
-      SqlJsonQueryEmptyOrErrorBehavior emptyBehavior,
-      SqlJsonQueryEmptyOrErrorBehavior errorBehavior) {
-    return jsonQuery(
-        jsonApiCommonSyntax(input, pathSpec),
-        wrapperBehavior, emptyBehavior, errorBehavior);
-  }
-
-  public static @Nullable String jsonQuery(JsonValueContext input,
-      String pathSpec,
-      SqlJsonQueryWrapperBehavior wrapperBehavior,
-      SqlJsonQueryEmptyOrErrorBehavior emptyBehavior,
-      SqlJsonQueryEmptyOrErrorBehavior errorBehavior) {
-    return jsonQuery(
-        jsonApiCommonSyntax(input, pathSpec),
-        wrapperBehavior, emptyBehavior, errorBehavior);
-  }
-
-  public static @Nullable String jsonQuery(JsonPathContext context,
-      SqlJsonQueryWrapperBehavior wrapperBehavior,
-      SqlJsonQueryEmptyOrErrorBehavior emptyBehavior,
-      SqlJsonQueryEmptyOrErrorBehavior errorBehavior) {
-    final Exception exc;
-    if (context.hasException()) {
-      exc = context.exc;
-    } else {
-      Object value;
-      if (context.obj == null) {
-        value = null;
+
+    public @Nullable Object jsonValue(String input,
+        String pathSpec,
+        SqlJsonValueEmptyOrErrorBehavior emptyBehavior,
+        Object defaultValueOnEmpty,
+        SqlJsonValueEmptyOrErrorBehavior errorBehavior,
+        Object defaultValueOnError) {
+      return jsonValue(
+          jsonApiCommonSyntaxWithCache(input, pathSpec),
+          emptyBehavior,
+          defaultValueOnEmpty,
+          errorBehavior,
+          defaultValueOnError);
+    }
+
+    public @Nullable Object jsonValue(JsonValueContext input,
+        String pathSpec,
+        SqlJsonValueEmptyOrErrorBehavior emptyBehavior,
+        Object defaultValueOnEmpty,
+        SqlJsonValueEmptyOrErrorBehavior errorBehavior,
+        Object defaultValueOnError) {
+      return jsonValue(
+          jsonApiCommonSyntax(input, pathSpec),
+          emptyBehavior,
+          defaultValueOnEmpty,
+          errorBehavior,
+          defaultValueOnError);
+    }
+
+    public @Nullable Object jsonValue(JsonPathContext context,
+        SqlJsonValueEmptyOrErrorBehavior emptyBehavior,
+        Object defaultValueOnEmpty,
+        SqlJsonValueEmptyOrErrorBehavior errorBehavior,
+        Object defaultValueOnError) {
+      final Exception exc;
+      if (context.hasException()) {
+        exc = context.exc;
       } else {
-        switch (wrapperBehavior) {
-        case WITHOUT_ARRAY:
-          value = context.obj;
-          break;
-        case WITH_UNCONDITIONAL_ARRAY:
-          value = Collections.singletonList(context.obj);
-          break;
-        case WITH_CONDITIONAL_ARRAY:
-          if (context.obj instanceof Collection) {
-            value = context.obj;
-          } else {
-            value = Collections.singletonList(context.obj);
+        Object value = context.obj;
+        if (value == null || context.mode == PathMode.LAX
+            && !isScalarObject(value)) {
+          switch (emptyBehavior) {
+          case ERROR:
+            throw RESOURCE.emptyResultOfJsonValueFuncNotAllowed().ex();
+          case NULL:
+            return null;
+          case DEFAULT:
+            return defaultValueOnEmpty;
+          default:
+            throw RESOURCE.illegalEmptyBehaviorInJsonValueFunc(
+                emptyBehavior.toString()).ex();
           }
-          break;
-        default:
-          throw RESOURCE.illegalWrapperBehaviorInJsonQueryFunc(
-              wrapperBehavior.toString()).ex();
+        } else if (context.mode == PathMode.STRICT
+            && !isScalarObject(value)) {
+          exc =
+              RESOURCE.scalarValueRequiredInStrictModeOfJsonValueFunc(
+                  value.toString()).ex();
+        } else {
+          return value;
         }
       }
-      if (value == null || context.mode == PathMode.LAX
-          && isScalarObject(value)) {
-        switch (emptyBehavior) {
-        case ERROR:
-          throw RESOURCE.emptyResultOfJsonQueryFuncNotAllowed().ex();
-        case NULL:
-          return null;
-        case EMPTY_ARRAY:
-          return "[]";
-        case EMPTY_OBJECT:
-          return "{}";
-        default:
-          throw RESOURCE.illegalEmptyBehaviorInJsonQueryFunc(
-              emptyBehavior.toString()).ex();
-        }
-      } else if (context.mode == PathMode.STRICT && isScalarObject(value)) {
-        exc =
-            RESOURCE.arrayOrObjectValueRequiredInStrictModeOfJsonQueryFunc(
-                value.toString()).ex();
+      switch (errorBehavior) {
+      case ERROR:
+        throw toUnchecked(exc);
+      case NULL:
+        return null;
+      case DEFAULT:
+        return defaultValueOnError;
+      default:
+        throw RESOURCE.illegalErrorBehaviorInJsonValueFunc(
+            errorBehavior.toString()).ex();
+      }
+    }
+
+    public @Nullable String jsonQuery(String input,
+        String pathSpec,
+        SqlJsonQueryWrapperBehavior wrapperBehavior,
+        SqlJsonQueryEmptyOrErrorBehavior emptyBehavior,
+        SqlJsonQueryEmptyOrErrorBehavior errorBehavior) {
+      return jsonQuery(
+          jsonApiCommonSyntaxWithCache(input, pathSpec),
+          wrapperBehavior, emptyBehavior, errorBehavior);
+    }
+
+    public @Nullable String jsonQuery(JsonValueContext input,
+        String pathSpec,
+        SqlJsonQueryWrapperBehavior wrapperBehavior,
+        SqlJsonQueryEmptyOrErrorBehavior emptyBehavior,
+        SqlJsonQueryEmptyOrErrorBehavior errorBehavior) {
+      return jsonQuery(
+          jsonApiCommonSyntax(input, pathSpec),
+          wrapperBehavior, emptyBehavior, errorBehavior);
+    }
+
+    public @Nullable String jsonQuery(JsonPathContext context,
+        SqlJsonQueryWrapperBehavior wrapperBehavior,
+        SqlJsonQueryEmptyOrErrorBehavior emptyBehavior,
+        SqlJsonQueryEmptyOrErrorBehavior errorBehavior) {
+      final Exception exc;
+      if (context.hasException()) {
+        exc = context.exc;
       } else {
-        try {
-          return jsonize(value);
-        } catch (Exception e) {
-          exc = e;
+        Object value;
+        if (context.obj == null) {
+          value = null;
+        } else {
+          switch (wrapperBehavior) {
+          case WITHOUT_ARRAY:
+            value = context.obj;
+            break;
+          case WITH_UNCONDITIONAL_ARRAY:
+            value = Collections.singletonList(context.obj);
+            break;
+          case WITH_CONDITIONAL_ARRAY:
+            if (context.obj instanceof Collection) {
+              value = context.obj;
+            } else {
+              value = Collections.singletonList(context.obj);
+            }
+            break;
+          default:
+            throw RESOURCE.illegalWrapperBehaviorInJsonQueryFunc(
+                wrapperBehavior.toString()).ex();
+          }
+        }
+        if (value == null || context.mode == PathMode.LAX
+            && isScalarObject(value)) {
+          switch (emptyBehavior) {
+          case ERROR:
+            throw RESOURCE.emptyResultOfJsonQueryFuncNotAllowed().ex();
+          case NULL:
+            return null;
+          case EMPTY_ARRAY:
+            return "[]";
+          case EMPTY_OBJECT:
+            return "{}";
+          default:
+            throw RESOURCE.illegalEmptyBehaviorInJsonQueryFunc(
+                emptyBehavior.toString()).ex();
+          }
+        } else if (context.mode == PathMode.STRICT && isScalarObject(value)) {
+          exc =
+              RESOURCE.arrayOrObjectValueRequiredInStrictModeOfJsonQueryFunc(
+                  value.toString()).ex();
+        } else {
+          try {
+            return jsonize(value);
+          } catch (Exception e) {
+            exc = e;
+          }
         }
       }
-    }
-    switch (errorBehavior) {
-    case ERROR:
-      throw toUnchecked(exc);
-    case NULL:
-      return null;
-    case EMPTY_ARRAY:
-      return "[]";
-    case EMPTY_OBJECT:
-      return "{}";
-    default:
-      throw RESOURCE.illegalErrorBehaviorInJsonQueryFunc(
-          errorBehavior.toString()).ex();
+      switch (errorBehavior) {
+      case ERROR:
+        throw toUnchecked(exc);
+      case NULL:
+        return null;
+      case EMPTY_ARRAY:
+        return "[]";
+      case EMPTY_OBJECT:
+        return "{}";
+      default:
+        throw RESOURCE.illegalErrorBehaviorInJsonQueryFunc(
+            errorBehavior.toString()).ex();
+      }
     }
   }
 
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 1b659d6cb3..fc163fc659 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
@@ -406,7 +406,8 @@ public class SqlFunctions {
     }
 
     /** Helper for multiple capturing group regex check in REGEXP_* fns. */
-    private void checkMultipleCapturingGroupsInRegex(Matcher matcher, String 
methodName) {
+    private static void checkMultipleCapturingGroupsInRegex(Matcher matcher,
+        String methodName) {
       if (matcher.groupCount() > 1) {
         throw RESOURCE.multipleCapturingGroupsForRegexpExtract(
             Integer.toString(matcher.groupCount()), methodName).ex();
@@ -416,7 +417,7 @@ public class SqlFunctions {
     /** Helper for checking values of position and occurrence arguments in 
REGEXP_* fns.
      * Regex fns not using occurrencePosition param pass a default value of 0.
      * Throws an exception or returns true in case of failed value checks. */
-    private boolean checkPosOccurrenceParamValues(int position,
+    private static boolean checkPosOccurrenceParamValues(int position,
         int occurrence, int occurrencePosition, String value, String 
methodName) {
       if (position <= 0) {
         throw 
RESOURCE.invalidIntegerInputForRegexpFunctions(Integer.toString(position),
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 4652ca6bab..fc040dac70 100644
--- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
+++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
@@ -110,6 +110,7 @@ import org.apache.calcite.schema.Schemas;
 import org.apache.calcite.schema.Table;
 import org.apache.calcite.sql.SqlExplainLevel;
 import org.apache.calcite.sql.SqlJsonConstructorNullClause;
+import org.apache.calcite.sql.SqlJsonExistsErrorBehavior;
 import org.apache.calcite.sql.SqlJsonQueryEmptyOrErrorBehavior;
 import org.apache.calcite.sql.SqlJsonQueryWrapperBehavior;
 import org.apache.calcite.sql.SqlJsonValueEmptyOrErrorBehavior;
@@ -395,13 +396,18 @@ public enum BuiltInMethod {
       String.class),
   JSON_API_COMMON_SYNTAX(JsonFunctions.class, "jsonApiCommonSyntax",
       String.class, String.class),
-  JSON_EXISTS(JsonFunctions.class, "jsonExists", String.class, String.class),
-  JSON_VALUE(JsonFunctions.class, "jsonValue", String.class, String.class,
+  JSON_API_COMMON_SYNTAX_WITH_CACHE(JsonFunctions.StatefulFunction.class,
+      "jsonApiCommonSyntaxWithCache", String.class, String.class),
+  JSON_EXISTS2(JsonFunctions.StatefulFunction.class, "jsonExists",
+      String.class, String.class),
+  JSON_EXISTS3(JsonFunctions.StatefulFunction.class, "jsonExists",
+      String.class, String.class, SqlJsonExistsErrorBehavior.class),
+  JSON_VALUE(JsonFunctions.StatefulFunction.class, "jsonValue",
+      String.class, String.class,
       SqlJsonValueEmptyOrErrorBehavior.class, Object.class,
       SqlJsonValueEmptyOrErrorBehavior.class, Object.class),
-  JSON_QUERY(JsonFunctions.class, "jsonQuery", String.class,
-      String.class,
-      SqlJsonQueryWrapperBehavior.class,
+  JSON_QUERY(JsonFunctions.StatefulFunction.class, "jsonQuery", String.class,
+      String.class, SqlJsonQueryWrapperBehavior.class,
       SqlJsonQueryEmptyOrErrorBehavior.class,
       SqlJsonQueryEmptyOrErrorBehavior.class),
   JSON_OBJECT(JsonFunctions.class, "jsonObject",
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 605b99320f..b69f30d804 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlJsonFunctionsTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlJsonFunctionsTest.java
@@ -631,35 +631,42 @@ class SqlJsonFunctionsTest {
   private void assertJsonValueExpression(String input,
       Matcher<? super JsonFunctions.JsonValueContext> matcher) {
     assertThat(
-        invocationDesc(BuiltInMethod.JSON_VALUE_EXPRESSION.getMethodName(), 
input),
+        invocationDesc(BuiltInMethod.JSON_VALUE_EXPRESSION, input),
         JsonFunctions.jsonValueExpression(input), matcher);
   }
 
   private void assertJsonApiCommonSyntax(String input, String pathSpec,
       Matcher<? super JsonFunctions.JsonPathContext> matcher) {
     assertThat(
-        invocationDesc(BuiltInMethod.JSON_API_COMMON_SYNTAX.getMethodName(), 
input, pathSpec),
+        invocationDesc(BuiltInMethod.JSON_API_COMMON_SYNTAX, input, pathSpec),
         JsonFunctions.jsonApiCommonSyntax(input, pathSpec), matcher);
   }
 
-  private void assertJsonApiCommonSyntax(JsonFunctions.JsonValueContext input, 
String pathSpec,
-      Matcher<? super JsonFunctions.JsonPathContext> matcher) {
+  private void assertJsonApiCommonSyntax(JsonFunctions.JsonValueContext input,
+      String pathSpec, Matcher<? super JsonFunctions.JsonPathContext> matcher) 
{
     assertThat(
-        invocationDesc(BuiltInMethod.JSON_API_COMMON_SYNTAX.getMethodName(), 
input, pathSpec),
+        invocationDesc(BuiltInMethod.JSON_API_COMMON_SYNTAX, input, pathSpec),
         JsonFunctions.jsonApiCommonSyntax(input, pathSpec), matcher);
   }
 
   private void assertJsonExists(JsonFunctions.JsonPathContext context,
-      SqlJsonExistsErrorBehavior errorBehavior, Matcher<? super Boolean> 
matcher) {
-    assertThat(invocationDesc(BuiltInMethod.JSON_EXISTS.getMethodName(), 
context, errorBehavior),
-        JsonFunctions.jsonExists(context, errorBehavior), matcher);
+      SqlJsonExistsErrorBehavior errorBehavior,
+      Matcher<? super Boolean> matcher) {
+    final JsonFunctions.StatefulFunction f =
+        new JsonFunctions.StatefulFunction();
+    assertThat(
+        invocationDesc(BuiltInMethod.JSON_EXISTS2, context, errorBehavior),
+        f.jsonExists(context, errorBehavior), matcher);
   }
 
   private void assertJsonExistsFailed(JsonFunctions.JsonPathContext context,
       SqlJsonExistsErrorBehavior errorBehavior,
       Matcher<? super Throwable> matcher) {
-    assertFailed(invocationDesc(BuiltInMethod.JSON_EXISTS.getMethodName(), 
context, errorBehavior),
-        () -> JsonFunctions.jsonExists(
+    final JsonFunctions.StatefulFunction f =
+        new JsonFunctions.StatefulFunction();
+    assertFailed(
+        invocationDesc(BuiltInMethod.JSON_EXISTS2, context, errorBehavior),
+        () -> f.jsonExists(
             context, errorBehavior), matcher);
   }
 
@@ -669,10 +676,12 @@ class SqlJsonFunctionsTest {
       SqlJsonValueEmptyOrErrorBehavior errorBehavior,
       Object defaultValueOnError,
       Matcher<Object> matcher) {
+    final JsonFunctions.StatefulFunction f =
+        new JsonFunctions.StatefulFunction();
     assertThat(
-        invocationDesc(BuiltInMethod.JSON_VALUE.getMethodName(), context, 
emptyBehavior,
+        invocationDesc(BuiltInMethod.JSON_VALUE, context, emptyBehavior,
             defaultValueOnEmpty, errorBehavior, defaultValueOnError),
-        JsonFunctions.jsonValue(context, emptyBehavior, defaultValueOnEmpty,
+        f.jsonValue(context, emptyBehavior, defaultValueOnEmpty,
             errorBehavior, defaultValueOnError),
         matcher);
   }
@@ -683,10 +692,12 @@ class SqlJsonFunctionsTest {
       SqlJsonValueEmptyOrErrorBehavior errorBehavior,
       Object defaultValueOnError,
       Matcher<? super Throwable> matcher) {
+    final JsonFunctions.StatefulFunction f =
+        new JsonFunctions.StatefulFunction();
     assertFailed(
-        invocationDesc(BuiltInMethod.JSON_VALUE.getMethodName(), input, 
emptyBehavior,
+        invocationDesc(BuiltInMethod.JSON_VALUE, input, emptyBehavior,
             defaultValueOnEmpty, errorBehavior, defaultValueOnError),
-        () -> JsonFunctions.jsonValue(input, emptyBehavior,
+        () -> f.jsonValue(input, emptyBehavior,
             defaultValueOnEmpty, errorBehavior, defaultValueOnError),
         matcher);
   }
@@ -696,10 +707,12 @@ class SqlJsonFunctionsTest {
       SqlJsonQueryEmptyOrErrorBehavior emptyBehavior,
       SqlJsonQueryEmptyOrErrorBehavior errorBehavior,
       Matcher<? super String> matcher) {
+    final JsonFunctions.StatefulFunction f =
+        new JsonFunctions.StatefulFunction();
     assertThat(
-        invocationDesc(BuiltInMethod.JSON_QUERY.getMethodName(), input, 
wrapperBehavior,
+        invocationDesc(BuiltInMethod.JSON_QUERY, input, wrapperBehavior,
             emptyBehavior, errorBehavior),
-        JsonFunctions.jsonQuery(input, wrapperBehavior, emptyBehavior,
+        f.jsonQuery(input, wrapperBehavior, emptyBehavior,
             errorBehavior),
         matcher);
   }
@@ -709,90 +722,89 @@ class SqlJsonFunctionsTest {
       SqlJsonQueryEmptyOrErrorBehavior emptyBehavior,
       SqlJsonQueryEmptyOrErrorBehavior errorBehavior,
       Matcher<? super Throwable> matcher) {
+    final JsonFunctions.StatefulFunction f =
+        new JsonFunctions.StatefulFunction();
     assertFailed(
-        invocationDesc(BuiltInMethod.JSON_QUERY.getMethodName(), input, 
wrapperBehavior,
+        invocationDesc(BuiltInMethod.JSON_QUERY, input, wrapperBehavior,
             emptyBehavior, errorBehavior),
-        () -> JsonFunctions.jsonQuery(input, wrapperBehavior, emptyBehavior,
+        () -> f.jsonQuery(input, wrapperBehavior, emptyBehavior,
             errorBehavior),
         matcher);
   }
 
   private void assertJsonize(Object input,
       Matcher<? super String> matcher) {
-    assertThat(invocationDesc(BuiltInMethod.JSONIZE.getMethodName(), input),
+    assertThat(invocationDesc(BuiltInMethod.JSONIZE, input),
         JsonFunctions.jsonize(input),
         matcher);
   }
 
   private void assertJsonPretty(JsonFunctions.JsonValueContext input,
       Matcher<? super String> matcher) {
-    assertThat(invocationDesc(BuiltInMethod.JSON_PRETTY.getMethodName(), 
input),
+    assertThat(invocationDesc(BuiltInMethod.JSON_PRETTY, input),
         JsonFunctions.jsonPretty(input),
         matcher);
   }
 
   private void assertJsonPrettyFailed(JsonFunctions.JsonValueContext input,
       Matcher<? super Throwable> matcher) {
-    assertFailed(invocationDesc(BuiltInMethod.JSON_PRETTY.getMethodName(), 
input),
+    assertFailed(invocationDesc(BuiltInMethod.JSON_PRETTY, input),
         () -> JsonFunctions.jsonPretty(input),
         matcher);
   }
 
   private void assertJsonLength(JsonFunctions.JsonPathContext input,
       Matcher<? super Integer> matcher) {
-    assertThat(
-        invocationDesc(BuiltInMethod.JSON_LENGTH.getMethodName(), input),
+    assertThat(invocationDesc(BuiltInMethod.JSON_LENGTH, input),
         JsonFunctions.jsonLength(input),
         matcher);
   }
 
   private void assertJsonLengthFailed(JsonFunctions.JsonValueContext input,
       Matcher<? super Throwable> matcher) {
-    assertFailed(
-        invocationDesc(BuiltInMethod.JSON_LENGTH.getMethodName(), input),
+    assertFailed(invocationDesc(BuiltInMethod.JSON_LENGTH, input),
         () -> JsonFunctions.jsonLength(input),
         matcher);
   }
 
   private void assertJsonKeys(JsonFunctions.JsonPathContext input,
       Matcher<? super String> matcher) {
-    assertThat(
-        invocationDesc(BuiltInMethod.JSON_KEYS.getMethodName(), input),
+    assertThat(invocationDesc(BuiltInMethod.JSON_KEYS, input),
         JsonFunctions.jsonKeys(input),
         matcher);
   }
 
   private void assertJsonKeysFailed(JsonFunctions.JsonValueContext input,
       Matcher<? super Throwable> matcher) {
-    assertFailed(invocationDesc(BuiltInMethod.JSON_KEYS.getMethodName(), 
input),
+    assertFailed(invocationDesc(BuiltInMethod.JSON_KEYS, input),
         () -> JsonFunctions.jsonKeys(input),
         matcher);
   }
 
-  private void assertJsonRemove(JsonFunctions.JsonValueContext input, String[] 
pathSpecs,
-      Matcher<? super String> matcher) {
-    assertThat(invocationDesc(BuiltInMethod.JSON_REMOVE.getMethodName(), 
input, pathSpecs),
+  private void assertJsonRemove(JsonFunctions.JsonValueContext input,
+      String[] pathSpecs, Matcher<? super String> matcher) {
+    assertThat(invocationDesc(BuiltInMethod.JSON_REMOVE, input, pathSpecs),
         JsonFunctions.jsonRemove(input, pathSpecs),
         matcher);
   }
 
   private void assertJsonStorageSize(String input,
       Matcher<? super Integer> matcher) {
-    assertThat(invocationDesc(BuiltInMethod.JSON_STORAGE_SIZE.getMethodName(), 
input),
+    assertThat(invocationDesc(BuiltInMethod.JSON_STORAGE_SIZE, input),
         JsonFunctions.jsonStorageSize(input),
         matcher);
   }
 
   private void assertJsonStorageSize(JsonFunctions.JsonValueContext input,
       Matcher<? super Integer> matcher) {
-    assertThat(invocationDesc(BuiltInMethod.JSON_STORAGE_SIZE.getMethodName(), 
input),
+    assertThat(invocationDesc(BuiltInMethod.JSON_STORAGE_SIZE, input),
         JsonFunctions.jsonStorageSize(input),
         matcher);
   }
 
   private void assertJsonStorageSizeFailed(String input,
       Matcher<? super Throwable> matcher) {
-    
assertFailed(invocationDesc(BuiltInMethod.JSON_STORAGE_SIZE.getMethodName(), 
input),
+    assertFailed(invocationDesc(BuiltInMethod.JSON_STORAGE_SIZE, input),
         () -> JsonFunctions.jsonStorageSize(input),
         matcher);
   }
@@ -800,7 +812,7 @@ class SqlJsonFunctionsTest {
   private void assertJsonInsert(JsonFunctions.JsonValueContext jsonDoc,
       Object[] kvs,
       Matcher<? super String> matcher) {
-    assertThat(invocationDesc(BuiltInMethod.JSON_INSERT.getMethodName(), 
jsonDoc, kvs),
+    assertThat(invocationDesc(BuiltInMethod.JSON_INSERT, jsonDoc, kvs),
         JsonFunctions.jsonInsert(jsonDoc, kvs),
         matcher);
   }
@@ -808,7 +820,7 @@ class SqlJsonFunctionsTest {
   private void assertJsonReplace(JsonFunctions.JsonValueContext jsonDoc,
       Object[] kvs,
       Matcher<? super String> matcher) {
-    assertThat(invocationDesc(BuiltInMethod.JSON_REPLACE.getMethodName(), 
jsonDoc, kvs),
+    assertThat(invocationDesc(BuiltInMethod.JSON_REPLACE, jsonDoc, kvs),
         JsonFunctions.jsonReplace(jsonDoc, kvs),
         matcher);
   }
@@ -816,21 +828,21 @@ class SqlJsonFunctionsTest {
   private void assertJsonSet(JsonFunctions.JsonValueContext jsonDoc,
       Object[] kvs,
       Matcher<? super String> matcher) {
-    assertThat(invocationDesc(BuiltInMethod.JSON_SET.getMethodName(), jsonDoc, 
kvs),
+    assertThat(invocationDesc(BuiltInMethod.JSON_SET, jsonDoc, kvs),
         JsonFunctions.jsonSet(jsonDoc, kvs),
         matcher);
   }
 
   private void assertDejsonize(String input,
       Matcher<Object> matcher) {
-    assertThat(invocationDesc(BuiltInMethod.DEJSONIZE.getMethodName(), input),
+    assertThat(invocationDesc(BuiltInMethod.DEJSONIZE, input),
         JsonFunctions.dejsonize(input),
         matcher);
   }
 
   private void assertDejsonizeFailed(String input,
       Matcher<? super Throwable> matcher) {
-    assertFailed(invocationDesc(BuiltInMethod.DEJSONIZE.getMethodName(), 
input),
+    assertFailed(invocationDesc(BuiltInMethod.DEJSONIZE, input),
         () -> JsonFunctions.dejsonize(input),
         matcher);
   }
@@ -838,23 +850,21 @@ class SqlJsonFunctionsTest {
   private void assertJsonObject(Matcher<? super String> matcher,
       SqlJsonConstructorNullClause nullClause,
       Object... kvs) {
-    assertThat(invocationDesc(BuiltInMethod.JSON_OBJECT.getMethodName(), 
nullClause, kvs),
+    assertThat(invocationDesc(BuiltInMethod.JSON_OBJECT, nullClause, kvs),
         JsonFunctions.jsonObject(nullClause, kvs),
         matcher);
   }
 
   private void assertJsonType(Matcher<? super String> matcher,
       String input) {
-    assertThat(
-        invocationDesc(BuiltInMethod.JSON_TYPE.getMethodName(), input),
+    assertThat(invocationDesc(BuiltInMethod.JSON_TYPE, input),
         JsonFunctions.jsonType(input),
         matcher);
   }
 
   private void assertJsonDepth(Matcher<? super Integer> matcher,
       String input) {
-    assertThat(
-        invocationDesc(BuiltInMethod.JSON_DEPTH.getMethodName(), input),
+    assertThat(invocationDesc(BuiltInMethod.JSON_DEPTH, input),
         JsonFunctions.jsonDepth(input),
         matcher);
   }
@@ -864,13 +874,13 @@ class SqlJsonFunctionsTest {
       Matcher<? super Map> matcher) {
     JsonFunctions.jsonObjectAggAdd(map, k, v, nullClause);
     assertThat(
-        invocationDesc(BuiltInMethod.JSON_ARRAYAGG_ADD.getMethodName(), map, 
k, v, nullClause),
+        invocationDesc(BuiltInMethod.JSON_ARRAYAGG_ADD, map, k, v, nullClause),
         map, matcher);
   }
 
   private void assertJsonArray(Matcher<? super String> matcher,
       SqlJsonConstructorNullClause nullClause, Object... elements) {
-    assertThat(invocationDesc(BuiltInMethod.JSON_ARRAY.getMethodName(), 
nullClause, elements),
+    assertThat(invocationDesc(BuiltInMethod.JSON_ARRAY, nullClause, elements),
         JsonFunctions.jsonArray(nullClause, elements),
         matcher);
   }
@@ -880,44 +890,43 @@ class SqlJsonFunctionsTest {
       Matcher<? super List> matcher) {
     JsonFunctions.jsonArrayAggAdd(list, element, nullClause);
     assertThat(
-        invocationDesc(BuiltInMethod.JSON_ARRAYAGG_ADD.getMethodName(), list, 
element,
+        invocationDesc(BuiltInMethod.JSON_ARRAYAGG_ADD, list, element,
             nullClause),
         list, matcher);
   }
 
   private void assertIsJsonValue(String input,
       Matcher<? super Boolean> matcher) {
-    assertThat(invocationDesc(BuiltInMethod.IS_JSON_VALUE.getMethodName(), 
input),
+    assertThat(invocationDesc(BuiltInMethod.IS_JSON_VALUE, input),
         JsonFunctions.isJsonValue(input),
         matcher);
   }
 
   private void assertIsJsonScalar(String input,
       Matcher<? super Boolean> matcher) {
-    assertThat(invocationDesc(BuiltInMethod.IS_JSON_SCALAR.getMethodName(), 
input),
+    assertThat(invocationDesc(BuiltInMethod.IS_JSON_SCALAR, input),
         JsonFunctions.isJsonScalar(input),
         matcher);
   }
 
   private void assertIsJsonArray(String input,
       Matcher<? super Boolean> matcher) {
-    assertThat(invocationDesc(BuiltInMethod.IS_JSON_ARRAY.getMethodName(), 
input),
+    assertThat(invocationDesc(BuiltInMethod.IS_JSON_ARRAY, input),
         JsonFunctions.isJsonArray(input),
         matcher);
   }
 
   private void assertIsJsonObject(String input,
       Matcher<? super Boolean> matcher) {
-    assertThat(invocationDesc(BuiltInMethod.IS_JSON_OBJECT.getMethodName(), 
input),
+    assertThat(invocationDesc(BuiltInMethod.IS_JSON_OBJECT, input),
         JsonFunctions.isJsonObject(input),
         matcher);
   }
 
-  private String invocationDesc(String methodName, Object... args) {
-    return methodName + "(" + String.join(", ",
-        Arrays.stream(args)
-            .map(Objects::toString)
-            .collect(Collectors.toList())) + ")";
+  private static String invocationDesc(BuiltInMethod method, Object... args) {
+    return Arrays.stream(args)
+        .map(Objects::toString)
+        .collect(Collectors.joining(", ", method.getMethodName() + "(", ")"));
   }
 
   private void assertFailed(String invocationDesc, Supplier<?> supplier,


Reply via email to