[CALCITE-6365] Support for RETURNING clause of JSON_QUERY
diff --git a/core/src/main/codegen/templates/Parser.jj b/core/src/main/codegen/templates/Parser.jj
index 93fda1d..e8c59ba 100644
--- a/core/src/main/codegen/templates/Parser.jj
+++ b/core/src/main/codegen/templates/Parser.jj
@@ -6706,7 +6706,7 @@
SqlCall JsonQueryFunctionCall() :
{
- final SqlNode[] args = new SqlNode[5];
+ final SqlNode[] args = new SqlNode[6];
SqlNode e;
List<SqlNode> commonSyntax;
final Span span;
@@ -6719,6 +6719,11 @@
args[1] = commonSyntax.get(1);
}
[
+ e = JsonReturningClause() {
+ args[5] = e;
+ }
+ ]
+ [
e = JsonQueryWrapperBehavior() <WRAPPER> {
args[2] = e;
}
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 d2d026c..33e4ba1 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
@@ -945,7 +945,7 @@
BuiltInMethod.JSON_EXISTS3.method);
map.put(JSON_VALUE,
new JsonValueImplementor(BuiltInMethod.JSON_VALUE.method));
- defineReflective(JSON_QUERY, BuiltInMethod.JSON_QUERY.method);
+ map.put(JSON_QUERY, new JsonQueryImplementor(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);
@@ -2903,6 +2903,34 @@
}
}
+ /**
+ * Implementor for JSON_QUERY function. Passes the jsonize flag depending on the output type.
+ */
+ private static class JsonQueryImplementor extends MethodImplementor {
+ JsonQueryImplementor(Method method) {
+ super(method, NullPolicy.ARG0, false);
+ }
+
+ @Override Expression implementSafe(RexToLixTranslator translator,
+ RexCall call, List<Expression> argValueList) {
+ final List<Expression> newOperands = new ArrayList<>(argValueList);
+
+ final Expression jsonize;
+ if (SqlTypeUtil.inCharFamily(call.getType())) {
+ jsonize = TRUE_EXPR;
+ } else {
+ jsonize = FALSE_EXPR;
+ }
+ newOperands.add(jsonize);
+
+ List<Expression> argValueList0 =
+ EnumUtils.fromInternal(method.getParameterTypes(), newOperands);
+ final Expression target =
+ Expressions.new_(method.getDeclaringClass());
+ return Expressions.call(target, method, argValueList0);
+ }
+ }
+
/** Implementor for binary operators. */
private static class BinaryImplementor extends AbstractRexCallImplementor {
/** Types that can be arguments to comparison operators such as
diff --git a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
index 6b531c0..d4787a8 100644
--- a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
+++ b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
@@ -993,6 +993,9 @@
@BaseMessage("Illegal error behavior ''{0}'' specified in JSON_VALUE function")
ExInst<CalciteException> illegalErrorBehaviorInJsonQueryFunc(String errorBehavior);
+ @BaseMessage("EMPTY_OBJECT is illegal for given return type")
+ ExInst<CalciteException> illegalEmptyObjectInJsonQueryFunc();
+
@BaseMessage("Null key of JSON object is not allowed")
ExInst<CalciteException> nullKeyOfJsonObjectNotAllowed();
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 6cd9258..b776339 100644
--- a/core/src/main/java/org/apache/calcite/runtime/JsonFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/JsonFunctions.java
@@ -310,30 +310,35 @@
}
}
- public @Nullable String jsonQuery(String input,
+ public @Nullable Object jsonQuery(
+ String input,
String pathSpec,
SqlJsonQueryWrapperBehavior wrapperBehavior,
SqlJsonQueryEmptyOrErrorBehavior emptyBehavior,
- SqlJsonQueryEmptyOrErrorBehavior errorBehavior) {
+ SqlJsonQueryEmptyOrErrorBehavior errorBehavior,
+ boolean jsonize) {
return jsonQuery(
jsonApiCommonSyntaxWithCache(input, pathSpec),
- wrapperBehavior, emptyBehavior, errorBehavior);
+ wrapperBehavior, emptyBehavior, errorBehavior, jsonize);
}
- public @Nullable String jsonQuery(JsonValueContext input,
+ public @Nullable Object jsonQuery(JsonValueContext input,
String pathSpec,
SqlJsonQueryWrapperBehavior wrapperBehavior,
SqlJsonQueryEmptyOrErrorBehavior emptyBehavior,
- SqlJsonQueryEmptyOrErrorBehavior errorBehavior) {
+ SqlJsonQueryEmptyOrErrorBehavior errorBehavior,
+ boolean jsonize) {
return jsonQuery(
jsonApiCommonSyntax(input, pathSpec),
- wrapperBehavior, emptyBehavior, errorBehavior);
+ wrapperBehavior, emptyBehavior, errorBehavior, jsonize);
}
- public @Nullable String jsonQuery(JsonPathContext context,
+ public @Nullable Object jsonQuery(
+ JsonPathContext context,
SqlJsonQueryWrapperBehavior wrapperBehavior,
SqlJsonQueryEmptyOrErrorBehavior emptyBehavior,
- SqlJsonQueryEmptyOrErrorBehavior errorBehavior) {
+ SqlJsonQueryEmptyOrErrorBehavior errorBehavior,
+ boolean jsonize) {
final Exception exc;
if (context.hasException()) {
exc = context.exc;
@@ -369,9 +374,9 @@
case NULL:
return null;
case EMPTY_ARRAY:
- return "[]";
+ return jsonQueryEmptyArray(jsonize);
case EMPTY_OBJECT:
- return "{}";
+ return jsonQueryEmptyObject(jsonize);
default:
throw RESOURCE.illegalEmptyBehaviorInJsonQueryFunc(
emptyBehavior.toString()).ex();
@@ -381,10 +386,14 @@
RESOURCE.arrayOrObjectValueRequiredInStrictModeOfJsonQueryFunc(
value.toString()).ex();
} else {
- try {
- return jsonize(value);
- } catch (Exception e) {
- exc = e;
+ if (jsonize) {
+ try {
+ return jsonize(value);
+ } catch (Exception e) {
+ exc = e;
+ }
+ } else {
+ return value;
}
}
}
@@ -394,14 +403,26 @@
case NULL:
return null;
case EMPTY_ARRAY:
- return "[]";
+ return jsonQueryEmptyArray(jsonize);
case EMPTY_OBJECT:
- return "{}";
+ return jsonQueryEmptyObject(jsonize);
default:
throw RESOURCE.illegalErrorBehaviorInJsonQueryFunc(
errorBehavior.toString()).ex();
}
}
+
+ private static Object jsonQueryEmptyArray(boolean jsonize) {
+ return jsonize ? "[]" : Collections.emptyList();
+ }
+
+ private static String jsonQueryEmptyObject(boolean jsonize) {
+ if (jsonize) {
+ return "{}";
+ } else {
+ throw RESOURCE.illegalEmptyObjectInJsonQueryFunc().ex();
+ }
+ }
}
public static String jsonObject(SqlJsonConstructorNullClause nullClause,
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlJsonQueryFunction.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlJsonQueryFunction.java
index f44072d..31f2329 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlJsonQueryFunction.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlJsonQueryFunction.java
@@ -16,6 +16,9 @@
*/
package org.apache.calcite.sql.fun;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.sql.SqlBasicCall;
import org.apache.calcite.sql.SqlCall;
import org.apache.calcite.sql.SqlFunction;
import org.apache.calcite.sql.SqlFunctionCategory;
@@ -24,15 +27,25 @@
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlLiteral;
import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlOperatorBinding;
import org.apache.calcite.sql.SqlWriter;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.type.OperandTypes;
import org.apache.calcite.sql.type.ReturnTypes;
import org.apache.calcite.sql.type.SqlTypeFamily;
+import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.type.SqlTypeTransforms;
+import org.apache.calcite.sql.type.SqlTypeUtil;
+
+import com.google.common.collect.ImmutableList;
import org.checkerframework.checker.nullness.qual.Nullable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
import static java.util.Objects.requireNonNull;
/**
@@ -41,15 +54,45 @@
public class SqlJsonQueryFunction extends SqlFunction {
public SqlJsonQueryFunction() {
super("JSON_QUERY", SqlKind.OTHER_FUNCTION,
- ReturnTypes.VARCHAR_2000.andThen(SqlTypeTransforms.FORCE_NULLABLE),
+ ReturnTypes.cascade(
+ opBinding ->
+ explicitTypeSpec(opBinding)
+ .map(t -> deriveExplicitType(opBinding, t))
+ .orElseGet(() -> getDefaultType(opBinding)),
+ SqlTypeTransforms.FORCE_NULLABLE),
null,
- OperandTypes.family(SqlTypeFamily.ANY, SqlTypeFamily.CHARACTER,
- SqlTypeFamily.ANY, SqlTypeFamily.ANY, SqlTypeFamily.ANY),
+ OperandTypes.family(
+ ImmutableList.of(SqlTypeFamily.ANY, SqlTypeFamily.CHARACTER,
+ SqlTypeFamily.ANY, SqlTypeFamily.ANY, SqlTypeFamily.ANY, SqlTypeFamily.ANY),
+ i -> i >= 5),
SqlFunctionCategory.SYSTEM);
}
+ /** Returns VARCHAR(2000) as default. */
+ private static RelDataType getDefaultType(SqlOperatorBinding opBinding) {
+ final RelDataTypeFactory typeFactory = opBinding.getTypeFactory();
+ final RelDataType baseType = typeFactory.createSqlType(SqlTypeName.VARCHAR, 2000);
+ return typeFactory.createTypeWithNullability(baseType, true);
+ }
+
+ private static RelDataType deriveExplicitType(SqlOperatorBinding opBinding, RelDataType type) {
+ if (SqlTypeName.ARRAY == type.getSqlTypeName()) {
+ RelDataType elementType = Objects.requireNonNull(type.getComponentType());
+ RelDataType nullableElementType = deriveExplicitType(opBinding, elementType);
+ return SqlTypeUtil.createArrayType(
+ opBinding.getTypeFactory(),
+ nullableElementType,
+ true);
+ }
+ return opBinding.getTypeFactory().createTypeWithNullability(type, true);
+ }
+
@Override public @Nullable String getSignatureTemplate(int operandsCount) {
- return "{0}({1} {2} {3} WRAPPER {4} ON EMPTY {5} ON ERROR)";
+ if (operandsCount == 6) {
+ return "{0}({1} {2} RETURNING {6} {3} WRAPPER {4} ON EMPTY {5} ON ERROR)";
+ } else {
+ return "{0}({1} {2} {3} WRAPPER {4} ON EMPTY {5} ON ERROR)";
+ }
}
@Override public void unparse(SqlWriter writer, SqlCall call, int leftPrec,
@@ -58,6 +101,10 @@
call.operand(0).unparse(writer, 0, 0);
writer.sep(",", true);
call.operand(1).unparse(writer, 0, 0);
+ if (call.operandCount() == 6) {
+ writer.keyword("RETURNING");
+ call.operand(5).unparse(writer, 0, 0);
+ }
final SqlJsonQueryWrapperBehavior wrapperBehavior =
getEnumValue(call.operand(2));
switch (wrapperBehavior) {
@@ -83,16 +130,32 @@
@Override public SqlCall createCall(@Nullable SqlLiteral functionQualifier,
SqlParserPos pos, @Nullable SqlNode... operands) {
+ final List<SqlNode> args = new ArrayList<>();
+ args.add(Objects.requireNonNull(operands[0]));
+ args.add(Objects.requireNonNull(operands[1]));
+
if (operands[2] == null) {
- operands[2] = SqlLiteral.createSymbol(SqlJsonQueryWrapperBehavior.WITHOUT_ARRAY, pos);
+ args.add(SqlLiteral.createSymbol(SqlJsonQueryWrapperBehavior.WITHOUT_ARRAY, pos));
+ } else {
+ args.add(operands[2]);
}
if (operands[3] == null) {
- operands[3] = SqlLiteral.createSymbol(SqlJsonQueryEmptyOrErrorBehavior.NULL, pos);
+ args.add(SqlLiteral.createSymbol(SqlJsonQueryEmptyOrErrorBehavior.NULL, pos));
+ } else {
+ args.add(operands[3]);
}
if (operands[4] == null) {
- operands[4] = SqlLiteral.createSymbol(SqlJsonQueryEmptyOrErrorBehavior.NULL, pos);
+ args.add(SqlLiteral.createSymbol(SqlJsonQueryEmptyOrErrorBehavior.NULL, pos));
+ } else {
+ args.add(operands[4]);
}
- return super.createCall(functionQualifier, pos, operands);
+
+ if (operands.length >= 6 && operands[5] != null) {
+ args.add(operands[5]);
+ }
+
+ pos = pos.plusAll(operands);
+ return new SqlBasicCall(this, args, pos, functionQualifier);
}
private static void unparseEmptyOrErrorBehavior(SqlWriter writer,
@@ -119,4 +182,20 @@
private static <E extends Enum<E>> E getEnumValue(SqlNode operand) {
return (E) requireNonNull(((SqlLiteral) operand).getValue(), "operand.value");
}
+
+ public static boolean hasExplicitTypeSpec(List<SqlNode> operands) {
+ return operands.size() >= 6;
+ }
+
+ public static List<SqlNode> removeTypeSpecOperands(SqlCall call) {
+ return call.getOperandList().subList(0, 5);
+ }
+
+ /** Returns the optional explicit returning type specification. * */
+ private static Optional<RelDataType> explicitTypeSpec(SqlOperatorBinding opBinding) {
+ if (opBinding.getOperandCount() >= 6) {
+ return Optional.of(opBinding.getOperandType(5));
+ }
+ return Optional.empty();
+ }
}
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 1490229..f3360d2 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
@@ -60,6 +60,7 @@
import org.apache.calcite.sql.fun.SqlDatetimeSubtractionOperator;
import org.apache.calcite.sql.fun.SqlExtractFunction;
import org.apache.calcite.sql.fun.SqlInternalOperators;
+import org.apache.calcite.sql.fun.SqlJsonQueryFunction;
import org.apache.calcite.sql.fun.SqlJsonValueFunction;
import org.apache.calcite.sql.fun.SqlLibrary;
import org.apache.calcite.sql.fun.SqlLibraryOperators;
@@ -92,6 +93,8 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import java.util.function.Function;
+import java.util.function.Predicate;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
@@ -914,19 +917,42 @@
}
public RexNode convertJsonValueFunction(
+ SqlRexContext cx, SqlJsonValueFunction fun, SqlCall call) {
+ return convertJsonReturningFunction(
+ cx,
+ fun,
+ call,
+ SqlJsonValueFunction::hasExplicitTypeSpec,
+ SqlJsonValueFunction::removeTypeSpecOperands);
+ }
+
+ public RexNode convertJsonQueryFunction(
+ SqlRexContext cx, SqlJsonQueryFunction fun, SqlCall call) {
+ return convertJsonReturningFunction(
+ cx,
+ fun,
+ call,
+ SqlJsonQueryFunction::hasExplicitTypeSpec,
+ SqlJsonQueryFunction::removeTypeSpecOperands);
+ }
+
+ public RexNode convertJsonReturningFunction(
SqlRexContext cx,
- SqlJsonValueFunction fun,
- SqlCall call) {
+ SqlFunction fun,
+ SqlCall call,
+ Predicate<List<SqlNode>> hasExplicitTypeSpec,
+ Function<SqlCall, List<SqlNode>> removeTypeSpecOperands) {
// For Expression with explicit return type:
- // i.e. json_value('{"foo":"bar"}', 'lax $.foo', returning varchar(2000))
+ // i.e. json_query('{"foo":"bar"}', 'lax $.foo', returning varchar(2000))
// use the specified type as the return type.
- List<SqlNode> operands =
- SqlJsonValueFunction.removeTypeSpecOperands(call);
+ List<SqlNode> operands = call.getOperandList();
+ boolean hasExplicitReturningType = hasExplicitTypeSpec.test(operands);
+ if (hasExplicitReturningType) {
+ operands = removeTypeSpecOperands.apply(call);
+ }
final List<RexNode> exprs =
- convertOperands(cx, call, operands,
- SqlOperandTypeChecker.Consistency.NONE);
- RelDataType returnType =
- cx.getValidator().getValidatedNodeTypeIfKnown(call);
+ convertOperands(cx, call, operands, SqlOperandTypeChecker.Consistency.NONE);
+ RelDataType returnType = cx.getValidator().getValidatedNodeTypeIfKnown(call);
requireNonNull(returnType, () -> "Unable to get type of " + call);
return cx.getRexBuilder().makeCall(returnType, fun, exprs);
}
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 2e72df1..d13dada 100644
--- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
+++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
@@ -423,7 +423,8 @@
JSON_QUERY(JsonFunctions.StatefulFunction.class, "jsonQuery", String.class,
String.class, SqlJsonQueryWrapperBehavior.class,
SqlJsonQueryEmptyOrErrorBehavior.class,
- SqlJsonQueryEmptyOrErrorBehavior.class),
+ SqlJsonQueryEmptyOrErrorBehavior.class,
+ boolean.class),
JSON_OBJECT(JsonFunctions.class, "jsonObject",
SqlJsonConstructorNullClause.class),
JSON_TYPE(JsonFunctions.class, "jsonType", String.class),
diff --git a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
index ab88f0a..9aaf1de 100644
--- a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
+++ b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
@@ -324,6 +324,7 @@
IllegalEmptyBehaviorInJsonQueryFunc=Illegal empty behavior ''{0}'' specified in JSON_VALUE function
ArrayOrObjectValueRequiredInStrictModeOfJsonQueryFunc=Strict jsonpath mode requires array or object value, and the actual value is: ''{0}''
IllegalErrorBehaviorInJsonQueryFunc=Illegal error behavior ''{0}'' specified in JSON_VALUE function
+IllegalEmptyObjectInJsonQueryFunc=EMPTY_OBJECT is illegal for given return type
NullKeyOfJsonObjectNotAllowed=Null key of JSON object is not allowed
QueryExecutionTimeoutReached=Timeout of ''{0}'' ms for query execution is reached. Query execution started at ''{1}''
AmbiguousSortOrderInJsonArrayAggFunc=Including both WITHIN GROUP(...) and inside ORDER BY in a single JSON_ARRAYAGG call is not allowed
diff --git a/core/src/test/java/org/apache/calcite/test/JdbcTest.java b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
index 980594a..352491b 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
@@ -8047,6 +8047,71 @@
.returns("C1=OBJECT; C2=ARRAY; C3=INTEGER; C4=BOOLEAN\n");
}
+ @Test void testJsonQuery() {
+ CalciteAssert.that()
+ .query("SELECT JSON_QUERY(v, '$.a') AS c1\n"
+ + ",JSON_QUERY(v, '$.a' RETURNING INTEGER ARRAY) AS c2\n"
+ + ",JSON_QUERY(v, '$.b' RETURNING INTEGER ARRAY EMPTY ARRAY ON ERROR) AS c3\n"
+ + ",JSON_QUERY(v, '$.b' RETURNING VARCHAR ARRAY WITH ARRAY WRAPPER) AS c4\n"
+ + "FROM (VALUES ('{\"a\": [1, 2],\"b\": \"[1, 2]\"}')) AS t(v)\n"
+ + "LIMIT 10")
+ .returns("C1=[1,2]; C2=[1, 2]; C3=[]; C4=[[1, 2]]\n");
+ }
+
+ @Test void testJsonValueError() {
+ java.sql.SQLException t =
+ assertThrows(
+ java.sql.SQLException.class,
+ () -> CalciteAssert.that()
+ .query("SELECT JSON_VALUE(v, 'lax $.a' RETURNING INTEGER) AS c1\n"
+ + "FROM (VALUES ('{\"a\": \"abc\"}')) AS t(v)\n"
+ + "LIMIT 10")
+ .returns(""));
+
+ assertThat(
+ t.getMessage(), containsString("java.lang.String cannot be cast to"));
+ }
+
+ @Test void testJsonQueryError() {
+ java.sql.SQLException t =
+ assertThrows(
+ java.sql.SQLException.class,
+ () -> CalciteAssert.that()
+ .query("SELECT JSON_QUERY(v, '$.a' RETURNING VARCHAR ARRAY"
+ + " EMPTY OBJECT ON ERROR) AS c1\n"
+ + "FROM (VALUES ('{\"a\": \"hi\"}')) AS t(v)\n"
+ + "LIMIT 10")
+ .returns(""));
+
+ assertThat(
+ t.getMessage(), containsString("EMPTY_OBJECT is illegal for given return type"));
+
+ t =
+ assertThrows(
+ java.sql.SQLException.class,
+ () -> CalciteAssert.that()
+ .query("SELECT JSON_QUERY(v, 'lax $.a' RETURNING VARCHAR ARRAY"
+ + " EMPTY OBJECT ON EMPTY) AS c1\n"
+ + "FROM (VALUES ('{\"a\": null}')) AS t(v)\n"
+ + "LIMIT 10")
+ .returns(""));
+
+ assertThat(
+ t.getMessage(), containsString("EMPTY_OBJECT is illegal for given return type"));
+
+ t =
+ assertThrows(
+ java.sql.SQLException.class,
+ () -> CalciteAssert.that()
+ .query("SELECT JSON_QUERY(v, 'lax $.a' RETURNING INTEGER) AS c1\n"
+ + "FROM (VALUES ('{\"a\": [\"a\", \"b\"]}')) AS t(v)\n"
+ + "LIMIT 10")
+ .returns(""));
+
+ assertThat(
+ t.getMessage(), containsString("java.util.ArrayList cannot be cast to"));
+ }
+
@Test void testJsonDepth() {
CalciteAssert.that()
.query("SELECT JSON_DEPTH(v) AS c1\n"
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 b69f30d..0879bde 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlJsonFunctionsTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlJsonFunctionsTest.java
@@ -413,6 +413,26 @@
SqlJsonQueryEmptyOrErrorBehavior.NULL,
SqlJsonQueryEmptyOrErrorBehavior.NULL,
is("[\"bar\"]"));
+
+ // jsonize test
+
+ assertJsonQuery(
+ JsonFunctions.JsonPathContext
+ .withJavaObj(JsonFunctions.PathMode.STRICT,
+ Collections.singletonList("bar")),
+ SqlJsonQueryWrapperBehavior.WITH_CONDITIONAL_ARRAY,
+ SqlJsonQueryEmptyOrErrorBehavior.NULL,
+ SqlJsonQueryEmptyOrErrorBehavior.NULL,
+ false,
+ is(Collections.singletonList("bar")));
+ assertJsonQuery(
+ JsonFunctions.JsonPathContext
+ .withUnknownException(new Exception("test message")),
+ SqlJsonQueryWrapperBehavior.WITH_CONDITIONAL_ARRAY,
+ SqlJsonQueryEmptyOrErrorBehavior.EMPTY_ARRAY,
+ SqlJsonQueryEmptyOrErrorBehavior.EMPTY_ARRAY,
+ false,
+ is(Collections.emptyList()));
}
@Test void testJsonize() {
@@ -706,14 +726,23 @@
SqlJsonQueryWrapperBehavior wrapperBehavior,
SqlJsonQueryEmptyOrErrorBehavior emptyBehavior,
SqlJsonQueryEmptyOrErrorBehavior errorBehavior,
- Matcher<? super String> matcher) {
+ Matcher<? super Object> matcher) {
+ assertJsonQuery(input, wrapperBehavior, emptyBehavior, errorBehavior, true, matcher);
+ }
+
+ private void assertJsonQuery(JsonFunctions.JsonPathContext input,
+ SqlJsonQueryWrapperBehavior wrapperBehavior,
+ SqlJsonQueryEmptyOrErrorBehavior emptyBehavior,
+ SqlJsonQueryEmptyOrErrorBehavior errorBehavior,
+ boolean jsonize,
+ Matcher<? super Object> matcher) {
final JsonFunctions.StatefulFunction f =
new JsonFunctions.StatefulFunction();
assertThat(
invocationDesc(BuiltInMethod.JSON_QUERY, input, wrapperBehavior,
emptyBehavior, errorBehavior),
f.jsonQuery(input, wrapperBehavior, emptyBehavior,
- errorBehavior),
+ errorBehavior, jsonize),
matcher);
}
@@ -728,7 +757,7 @@
invocationDesc(BuiltInMethod.JSON_QUERY, input, wrapperBehavior,
emptyBehavior, errorBehavior),
() -> f.jsonQuery(input, wrapperBehavior, emptyBehavior,
- errorBehavior),
+ errorBehavior, true),
matcher);
}
diff --git a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
index 21d29eb..ea61907 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -11487,6 +11487,14 @@
expr("json_query('{\"foo\":\"bar\"}', 'strict $' EMPTY OBJECT ON EMPTY "
+ "EMPTY ARRAY ON ERROR EMPTY ARRAY ON EMPTY NULL ON ERROR)")
.columnType("VARCHAR(2000)");
+
+ expr("json_query('{\"foo\":[100, null, 200]}', 'lax $.foo'"
+ + "returning integer array)")
+ .columnType("INTEGER ARRAY");
+
+ expr("json_query('{\"foo\":[[100, null, 200]]}', 'lax $.foo'"
+ + "returning integer array array)")
+ .columnType("INTEGER ARRAY ARRAY");
}
@Test void testJsonArray() {
diff --git a/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java b/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java
index ae0234f..c0bfb3b 100644
--- a/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java
+++ b/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java
@@ -8822,6 +8822,9 @@
+ "EMPTY OBJECT ON ERROR)")
.ok("JSON_QUERY('{\"foo\": \"bar\"}', "
+ "'lax $' WITHOUT ARRAY WRAPPER EMPTY ARRAY ON EMPTY EMPTY OBJECT ON ERROR)");
+ expr("json_query('{\"foo\": \"bar\"}', 'lax $' RETURNING VARCHAR ARRAY WITHOUT ARRAY WRAPPER)")
+ .ok("JSON_QUERY('{\"foo\": \"bar\"}', "
+ + "'lax $' RETURNING VARCHAR ARRAY WITHOUT ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR)");
}
@Test void testJsonObject() {