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

yiguolei pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-4.1 by this push:
     new 6f069fc759e branch-4.1: [fix](function) Reject even argument count of 
json_set/json_insert/json_replace at analysis time #67804 (#67914)
6f069fc759e is described below

commit 6f069fc759e99bc6cbc96699316613fffe2c4e00
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Mon Sep 14 09:43:14 2026 +0800

    branch-4.1: [fix](function) Reject even argument count of 
json_set/json_insert/json_replace at analysis time #67804 (#67914)
    
    Cherry-picked from #67804
    
    Co-authored-by: Jerry Hu <[email protected]>
---
 .../expressions/functions/scalar/JsonInsert.java   | 10 ++++++++++
 .../expressions/functions/scalar/JsonReplace.java  | 10 ++++++++++
 .../expressions/functions/scalar/JsonSet.java      | 10 ++++++++++
 .../analysis/CheckExpressionLegalityTest.java      | 18 ++++++++++++++++++
 .../json_function/test_query_json_insert.out       |  3 +++
 .../json_function/test_query_json_replace.out      |  3 +++
 .../json_function/test_query_json_set.out          |  3 +++
 .../json_function/test_query_json_insert.groovy    | 22 ++++++++++++++++++++++
 .../json_function/test_query_json_replace.groovy   | 22 ++++++++++++++++++++++
 .../json_function/test_query_json_set.groovy       | 22 ++++++++++++++++++++++
 10 files changed, 123 insertions(+)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/JsonInsert.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/JsonInsert.java
index a99df63983c..06bedc8d522 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/JsonInsert.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/JsonInsert.java
@@ -18,6 +18,7 @@
 package org.apache.doris.nereids.trees.expressions.functions.scalar;
 
 import org.apache.doris.catalog.FunctionSignature;
+import org.apache.doris.nereids.exceptions.AnalysisException;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.functions.AlwaysNullable;
 import org.apache.doris.nereids.trees.expressions.functions.CustomSignature;
@@ -64,6 +65,15 @@ public class JsonInsert extends ScalarFunction implements 
CustomSignature, Alway
         return FunctionSignature.of(JsonType.INSTANCE, arguments);
     }
 
+    @Override
+    public void checkLegalityBeforeTypeCoercion() {
+        // arguments are a JSON document followed by (path, value) pairs, so 
arity must be odd
+        if ((arity() & 1) == 0) {
+            throw new AnalysisException(getName() + " requires a JSON document 
followed by path/value pairs,"
+                    + " so the number of arguments must be odd, but got " + 
arity() + ": " + this.toSql());
+        }
+    }
+
     /**
      * withChildren.
      */
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/JsonReplace.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/JsonReplace.java
index a4d06ddbaff..c626d3ed5a8 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/JsonReplace.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/JsonReplace.java
@@ -18,6 +18,7 @@
 package org.apache.doris.nereids.trees.expressions.functions.scalar;
 
 import org.apache.doris.catalog.FunctionSignature;
+import org.apache.doris.nereids.exceptions.AnalysisException;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.functions.AlwaysNullable;
 import org.apache.doris.nereids.trees.expressions.functions.CustomSignature;
@@ -64,6 +65,15 @@ public class JsonReplace extends ScalarFunction implements 
CustomSignature, Alwa
         return FunctionSignature.of(JsonType.INSTANCE, arguments);
     }
 
+    @Override
+    public void checkLegalityBeforeTypeCoercion() {
+        // arguments are a JSON document followed by (path, value) pairs, so 
arity must be odd
+        if ((arity() & 1) == 0) {
+            throw new AnalysisException(getName() + " requires a JSON document 
followed by path/value pairs,"
+                    + " so the number of arguments must be odd, but got " + 
arity() + ": " + this.toSql());
+        }
+    }
+
     /**
      * withChildren.
      */
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/JsonSet.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/JsonSet.java
index cdc9ed82b4f..0f017d3d0d0 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/JsonSet.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/JsonSet.java
@@ -18,6 +18,7 @@
 package org.apache.doris.nereids.trees.expressions.functions.scalar;
 
 import org.apache.doris.catalog.FunctionSignature;
+import org.apache.doris.nereids.exceptions.AnalysisException;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.functions.AlwaysNullable;
 import org.apache.doris.nereids.trees.expressions.functions.CustomSignature;
@@ -64,6 +65,15 @@ public class JsonSet extends ScalarFunction implements 
CustomSignature, AlwaysNu
         return FunctionSignature.of(JsonType.INSTANCE, arguments);
     }
 
+    @Override
+    public void checkLegalityBeforeTypeCoercion() {
+        // arguments are a JSON document followed by (path, value) pairs, so 
arity must be odd
+        if ((arity() & 1) == 0) {
+            throw new AnalysisException(getName() + " requires a JSON document 
followed by path/value pairs,"
+                    + " so the number of arguments must be odd, but got " + 
arity() + ": " + this.toSql());
+        }
+    }
+
     /**
      * withChildren.
      */
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/CheckExpressionLegalityTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/CheckExpressionLegalityTest.java
index 447156afb38..21c11e6797e 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/CheckExpressionLegalityTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/CheckExpressionLegalityTest.java
@@ -70,6 +70,24 @@ public class CheckExpressionLegalityTest implements 
MemoPatternMatchSupported {
                 });
     }
 
+    @Test
+    public void testJsonModifyFunctionsRejectEvenArity() {
+        ConnectContext connectContext = MemoTestUtils.createConnectContext();
+        for (String function : new String[] {"json_set", "jsonb_set", 
"json_insert", "jsonb_insert",
+                "json_replace", "jsonb_replace"}) {
+            ExceptionChecker.expectThrowsWithMsg(AnalysisException.class,
+                    "number of arguments must be odd, but got 4", () ->
+                            PlanChecker.from(connectContext)
+                                    .analyze("select " + function + "('{}', 
'$.a', 1, '$.b')"));
+            ExceptionChecker.expectThrowsWithMsg(AnalysisException.class,
+                    "number of arguments must be odd, but got 6", () ->
+                            PlanChecker.from(connectContext)
+                                    .analyze("select " + function + "('{}', 
'$.a', 1, '$.b', 2, '$.c')"));
+            PlanChecker.from(connectContext).analyze("select " + function + 
"('{}', '$.a', 1)");
+            PlanChecker.from(connectContext).analyze("select " + function + 
"('{}', '$.a', 1, '$.b', 2)");
+        }
+    }
+
     @Test
     public void testCountDistinctBitmap() {
         ConnectContext connectContext = MemoTestUtils.createConnectContext();
diff --git 
a/regression-test/data/query_p0/sql_functions/json_function/test_query_json_insert.out
 
b/regression-test/data/query_p0/sql_functions/json_function/test_query_json_insert.out
index 03210f0f6a2..72c47e601ee 100644
--- 
a/regression-test/data/query_p0/sql_functions/json_function/test_query_json_insert.out
+++ 
b/regression-test/data/query_p0/sql_functions/json_function/test_query_json_insert.out
@@ -90,3 +90,6 @@
 -- !insert7 --
 {"a":200}
 
+-- !insert_odd_arity_ok --
+{"a":1}        {"a":1,"b":2}   {"a":1,"b":2}
+
diff --git 
a/regression-test/data/query_p0/sql_functions/json_function/test_query_json_replace.out
 
b/regression-test/data/query_p0/sql_functions/json_function/test_query_json_replace.out
index ac8cfb45010..3a67a270a04 100644
--- 
a/regression-test/data/query_p0/sql_functions/json_function/test_query_json_replace.out
+++ 
b/regression-test/data/query_p0/sql_functions/json_function/test_query_json_replace.out
@@ -80,3 +80,6 @@ null
 -- !replace8 --
 {"a":100}
 
+-- !replace_odd_arity_ok --
+{}     {}      {}
+
diff --git 
a/regression-test/data/query_p0/sql_functions/json_function/test_query_json_set.out
 
b/regression-test/data/query_p0/sql_functions/json_function/test_query_json_set.out
index b57a50e314b..e198f86db0b 100644
--- 
a/regression-test/data/query_p0/sql_functions/json_function/test_query_json_set.out
+++ 
b/regression-test/data/query_p0/sql_functions/json_function/test_query_json_set.out
@@ -85,3 +85,6 @@ null
 -- !set8 --
 {"a":100}
 
+-- !set_odd_arity_ok --
+{"a":1}        {"a":1,"b":2}   {"a":1,"b":2}
+
diff --git 
a/regression-test/suites/query_p0/sql_functions/json_function/test_query_json_insert.groovy
 
b/regression-test/suites/query_p0/sql_functions/json_function/test_query_json_insert.groovy
index 95c0cc3ab64..59bd3159a35 100644
--- 
a/regression-test/suites/query_p0/sql_functions/json_function/test_query_json_insert.groovy
+++ 
b/regression-test/suites/query_p0/sql_functions/json_function/test_query_json_insert.groovy
@@ -127,4 +127,26 @@ suite("test_query_json_insert", "query,arrow_flight_sql") {
         sql "select json_insert('1', '\$.', 4);"
         exception "Json path error: Invalid Json Path for value"
     }
+
+    // arguments must be a JSON document followed by path/value pairs, so an 
even count is rejected by FE
+    test {
+        sql "select json_insert('{}', '\$.a', 1, '\$.b');"
+        exception "number of arguments must be odd, but got 4"
+    }
+
+    test {
+        sql "select json_insert('{}', '\$.a', 1, '\$.b', 2, '\$.c');"
+        exception "number of arguments must be odd, but got 6"
+    }
+
+    test {
+        sql "select jsonb_insert('{}', '\$.a', 1, '\$.b');"
+        exception "number of arguments must be odd, but got 4"
+    }
+
+    test {
+        sql "explain select json_insert('{}', '\$.a', 1, '\$.b');"
+        exception "number of arguments must be odd, but got 4"
+    }
+    qt_insert_odd_arity_ok """select json_insert('{}', '\$.a', 1), 
json_insert('{}', '\$.a', 1, '\$.b', 2), jsonb_insert('{}', '\$.a', 1, '\$.b', 
2);"""
 }
diff --git 
a/regression-test/suites/query_p0/sql_functions/json_function/test_query_json_replace.groovy
 
b/regression-test/suites/query_p0/sql_functions/json_function/test_query_json_replace.groovy
index 69f7142eff7..0b91ca39dbb 100644
--- 
a/regression-test/suites/query_p0/sql_functions/json_function/test_query_json_replace.groovy
+++ 
b/regression-test/suites/query_p0/sql_functions/json_function/test_query_json_replace.groovy
@@ -133,4 +133,26 @@ suite("test_query_json_replace", "query") {
         sql "select json_replace('1', '\$.', 4);"
         exception "Json path error: Invalid Json Path for value"
     }
+
+    // arguments must be a JSON document followed by path/value pairs, so an 
even count is rejected by FE
+    test {
+        sql "select json_replace('{}', '\$.a', 1, '\$.b');"
+        exception "number of arguments must be odd, but got 4"
+    }
+
+    test {
+        sql "select json_replace('{}', '\$.a', 1, '\$.b', 2, '\$.c');"
+        exception "number of arguments must be odd, but got 6"
+    }
+
+    test {
+        sql "select jsonb_replace('{}', '\$.a', 1, '\$.b');"
+        exception "number of arguments must be odd, but got 4"
+    }
+
+    test {
+        sql "explain select json_replace('{}', '\$.a', 1, '\$.b');"
+        exception "number of arguments must be odd, but got 4"
+    }
+    qt_replace_odd_arity_ok """select json_replace('{}', '\$.a', 1), 
json_replace('{}', '\$.a', 1, '\$.b', 2), jsonb_replace('{}', '\$.a', 1, 
'\$.b', 2);"""
 }
diff --git 
a/regression-test/suites/query_p0/sql_functions/json_function/test_query_json_set.groovy
 
b/regression-test/suites/query_p0/sql_functions/json_function/test_query_json_set.groovy
index 705e6046705..91174fa8827 100644
--- 
a/regression-test/suites/query_p0/sql_functions/json_function/test_query_json_set.groovy
+++ 
b/regression-test/suites/query_p0/sql_functions/json_function/test_query_json_set.groovy
@@ -120,4 +120,26 @@ suite("test_query_json_set", "query") {
         sql "select json_set('1', '\$.', 4);"
         exception "Json path error: Invalid Json Path for value"
     }
+
+    // arguments must be a JSON document followed by path/value pairs, so an 
even count is rejected by FE
+    test {
+        sql "select json_set('{}', '\$.a', 1, '\$.b');"
+        exception "number of arguments must be odd, but got 4"
+    }
+
+    test {
+        sql "select json_set('{}', '\$.a', 1, '\$.b', 2, '\$.c');"
+        exception "number of arguments must be odd, but got 6"
+    }
+
+    test {
+        sql "select jsonb_set('{}', '\$.a', 1, '\$.b');"
+        exception "number of arguments must be odd, but got 4"
+    }
+
+    test {
+        sql "explain select json_set('{}', '\$.a', 1, '\$.b');"
+        exception "number of arguments must be odd, but got 4"
+    }
+    qt_set_odd_arity_ok """select json_set('{}', '\$.a', 1), json_set('{}', 
'\$.a', 1, '\$.b', 2), jsonb_set('{}', '\$.a', 1, '\$.b', 2);"""
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to