This is an automated email from the ASF dual-hosted git repository.
snuyanzin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/flink.git
The following commit(s) were added to refs/heads/master by this push:
new 45588b31410 [FLINK-37695][table] Fix parsing for built-in function
JSON in JSON_OBJECT for all positions
45588b31410 is described below
commit 45588b31410fc612051b0294d379c08036f04079
Author: Gustavo de Morais <[email protected]>
AuthorDate: Wed Apr 30 16:19:33 2025 +0200
[FLINK-37695][table] Fix parsing for built-in function JSON in JSON_OBJECT
for all positions
---
.../table/planner/codegen/ExprCodeGenerator.scala | 12 ++---
.../table/planner/codegen/JsonGenerateUtils.scala | 11 +++++
.../planner/functions/JsonFunctionsITCase.java | 57 +++++++++++++++++++---
3 files changed, 66 insertions(+), 14 deletions(-)
diff --git
a/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/codegen/ExprCodeGenerator.scala
b/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/codegen/ExprCodeGenerator.scala
index d2f0f3e5e2d..c625bfd89b3 100644
---
a/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/codegen/ExprCodeGenerator.scala
+++
b/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/codegen/ExprCodeGenerator.scala
@@ -29,7 +29,7 @@ import
org.apache.flink.table.planner.calcite.{FlinkTypeFactory, RexDistinctKeyV
import org.apache.flink.table.planner.codegen.CodeGenUtils._
import org.apache.flink.table.planner.codegen.GeneratedExpression.{NEVER_NULL,
NO_CODE}
import org.apache.flink.table.planner.codegen.GenerateUtils._
-import
org.apache.flink.table.planner.codegen.JsonGenerateUtils.{isJsonArrayOperand,
isJsonFunctionOperand, isJsonObjectOperand}
+import
org.apache.flink.table.planner.codegen.JsonGenerateUtils.{isJsonFunctionOperand,
isSupportedJsonOperand}
import org.apache.flink.table.planner.codegen.calls._
import org.apache.flink.table.planner.codegen.calls.ScalarOperatorGens._
import
org.apache.flink.table.planner.codegen.calls.SearchOperatorGen.generateSearch
@@ -464,8 +464,8 @@ class ExprCodeGenerator(ctx: CodeGeneratorContext,
nullableInput: Boolean)
// throw exception if json function is called outside JSON_OBJECT or
JSON_ARRAY function
if (isJsonFunctionOperand(call)) {
throw new ValidationException(
- "The JSON() function is currently only supported inside a
JSON_OBJECT() or JSON_ARRAY()" +
- " function. Example: JSON_OBJECT('a', JSON('{\"key\": \"value\"}'))
or " +
+ "The JSON() function is currently only supported inside JSON_ARRAY()
or as the VALUE param" +
+ " of JSON_OBJECT(). Example: JSON_OBJECT('a', JSON('{\"key\":
\"value\"}')) or " +
"JSON_ARRAY(JSON('{\"key\": \"value\"}')).")
}
@@ -486,10 +486,8 @@ class ExprCodeGenerator(ctx: CodeGeneratorContext,
nullableInput: Boolean)
call.getOperator.getReturnTypeInference == ReturnTypes.ARG0 =>
generateNullLiteral(resultType)
- // We only support JSON function operands as the value param of a
JSON_OBJECT or JSON_ARRAY function
- case (operand: RexNode, i)
- if isJsonFunctionOperand(operand) &&
- (isJsonArrayOperand(call) || i == 2 && isJsonObjectOperand(call))
=>
+ // We only support the JSON function inside of JSON_OBJECT or JSON_ARRAY
+ case (operand: RexNode, i) if isSupportedJsonOperand(operand, call, i) =>
generateJsonCall(operand)
case (o @ _, _) => o.accept(this)
diff --git
a/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/codegen/JsonGenerateUtils.scala
b/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/codegen/JsonGenerateUtils.scala
index 6ac502fda24..823837d2f5e 100644
---
a/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/codegen/JsonGenerateUtils.scala
+++
b/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/codegen/JsonGenerateUtils.scala
@@ -227,6 +227,17 @@ object JsonGenerateUtils {
}
}
+ /**
+ * Determines whether a JSON function is allowed in the current context.
JSON functions are
+ * allowed as values in JSON_ARRAY calls or as value parameters in
JSON_OBJECT calls. In the case
+ * of a JSON_OBJECT call, we do (i % 2) == 0 to check if it's being used in
second parameter, the
+ * values' parameter.
+ */
+ def isSupportedJsonOperand(operand: RexNode, call: RexNode, i: Int): Boolean
= {
+ isJsonFunctionOperand(operand) &&
+ (isJsonArrayOperand(call) || isJsonObjectOperand(call) && (i % 2) == 0)
+ }
+
/** Generates a method to convert arrays into [[ArrayNode]]. */
private def generateArrayConverter(
ctx: CodeGeneratorContext,
diff --git
a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/functions/JsonFunctionsITCase.java
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/functions/JsonFunctionsITCase.java
index 2ce19701dc4..e26401951c4 100644
---
a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/functions/JsonFunctionsITCase.java
+++
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/functions/JsonFunctionsITCase.java
@@ -782,11 +782,54 @@ class JsonFunctionsITCase extends BuiltInFunctionTestBase
{
.testTableApiRuntimeError(
jsonObject(JsonOnNull.NULL, "K", json("{")),
TableRuntimeException.class,
- "Invalid JSON string in JSON(value) function"),
- // Tests for JSON calls inside of JSON_ARRAY
- TestSetSpec.forFunction(BuiltInFunctionDefinitions.JSON_ARRAY)
- .onFieldsWithData("{\"key\":\"value\"}", "{\"key\":
{\"value\": 42}}")
- .andDataTypes(STRING(), STRING())
+ "Invalid JSON string in JSON(value) function")
+
+ // Tests for JSON_OBJECT with multiple parameters
+ .testResult(
+ jsonObject(JsonOnNull.NULL, "key1", "val",
"key2", json($("f1"))),
+ "JSON_OBJECT(KEY 'key1' VALUE 'val', KEY
'key2' VALUE JSON(f1))",
+
"{\"key1\":\"val\",\"key2\":{\"key\":{\"value\":42}}}",
+ STRING().notNull())
+ .testResult(
+ jsonObject(
+ JsonOnNull.NULL,
+ "key1",
+ json($("f0")),
+ "key2",
+ json($("f1"))),
+ "JSON_OBJECT(KEY 'key1' VALUE JSON(f0), KEY
'key2' VALUE JSON(f1))",
+
"{\"key1\":{\"key\":\"value\"},\"key2\":{\"key\":{\"value\":42}}}",
+ STRING().notNull())
+ .testResult(
+ jsonObject(
+ JsonOnNull.NULL,
+ "outerKey",
+ "outerValue",
+ "nestedObject",
+ jsonObject(JsonOnNull.NULL,
"innerKey", json($("f0")))),
+ "JSON_OBJECT(KEY 'outerKey' VALUE
'outerValue', KEY 'nestedObject' VALUE JSON_OBJECT(KEY 'innerKey' VALUE
JSON(f0)))",
+
"{\"nestedObject\":{\"innerKey\":{\"key\":\"value\"}},\"outerKey\":\"outerValue\"}",
+ STRING().notNull())
+ .testResult(
+ jsonObject(
+ JsonOnNull.NULL,
+ "p1",
+ json($("f0")),
+ "p2",
+ json($("f1")),
+ "p3",
+ json("[1, 2, 3]")),
+ "JSON_OBJECT(KEY 'p1' VALUE JSON(f0), KEY 'p2'
VALUE JSON(f1), KEY 'p3' VALUE JSON('[1, 2, 3]'))",
+
"{\"p1\":{\"key\":\"value\"},\"p2\":{\"key\":{\"value\":42}},\"p3\":[1,2,3]}",
+ STRING().notNull())
+ .testSqlValidationError(
+ "JSON_OBJECT(KEY JSON('{}') VALUE 'value'
ABSENT ON NULL)",
+ "The JSON() function is currently only
supported inside JSON_ARRAY() or as the VALUE param of JSON_OBJECT()")
+ .testTableApiValidationError(
+ jsonObject(JsonOnNull.NULL, json($("f0")),
"value"),
+ "Invalid function call:\n"
+ + "JSON_OBJECT(SYMBOL NOT NULL,
STRING, CHAR(5) NOT NULL)")
+ // Tests for JSON calls inside of JSON_ARRAY
.testResult(
jsonArray(JsonOnNull.NULL, json("{}")),
"JSON_ARRAY(JSON('{}'))",
@@ -865,10 +908,10 @@ class JsonFunctionsITCase extends BuiltInFunctionTestBase
{
"line: 1, column: 1")
.testTableApiValidationError(
json($("f0")),
- "The JSON() function is currently only
supported inside a JSON_OBJECT() or JSON_ARRAY() function.")
+ "The JSON() function is currently only
supported inside JSON_ARRAY() or as the VALUE param of JSON_OBJECT()")
.testSqlValidationError(
"JSON(f0)",
- "The JSON() function is currently only
supported inside a JSON_OBJECT() or JSON_ARRAY() function."));
+ "The JSON() function is currently only
supported inside JSON_ARRAY() or as the VALUE param of JSON_OBJECT()"));
}
private static List<TestSetSpec> jsonObjectSpec() {