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

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


The following commit(s) were added to refs/heads/branch-4.2 by this push:
     new 0b9a84e8973 branch-4.2: [fix](be) Keep following siblings when 
inserting nested JSON members #69288 (#69941)
0b9a84e8973 is described below

commit 0b9a84e897391023d33c2600054ff2843b40a11b
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Tue Nov 3 09:58:33 2026 +0800

    branch-4.2: [fix](be) Keep following siblings when inserting nested JSON 
members #69288 (#69941)
    
    Cherry-picked from #69288
    
    Co-authored-by: Jerry Hu <[email protected]>
---
 be/src/exprs/function/function_jsonb.cpp           |  9 ++++-
 be/test/exprs/function/function_jsonb_test.cpp     | 43 ++++++++++++++++++++++
 .../json_function/test_query_json_insert.out       | 17 +++++++++
 .../json_function/test_query_json_set.out          | 17 +++++++++
 .../json_function/test_query_json_insert.groovy    | 31 ++++++++++++++++
 .../json_function/test_query_json_set.groovy       | 31 ++++++++++++++++
 6 files changed, 146 insertions(+), 2 deletions(-)

diff --git a/be/src/exprs/function/function_jsonb.cpp 
b/be/src/exprs/function/function_jsonb.cpp
index 840889c2875..423d3273215 100644
--- a/be/src/exprs/function/function_jsonb.cpp
+++ b/be/src/exprs/function/function_jsonb.cpp
@@ -2252,10 +2252,15 @@ public:
 
                 if (is_last_parent && !value_written) {
                     value_written = true;
-                    writer.writeStartObject();
+                    // The current object is already open above and is waiting 
for a key, so the
+                    // new member must be appended to it directly. Opening 
another object here
+                    // would be rejected by the writer, and closing one here 
would end the
+                    // current object early; the writeEndObject below would 
then close its
+                    // parent as well, silently dropping every member that 
follows the current
+                    // one. For example, {"a":{},"b":2} with path $.a.c used 
to return
+                    // {"a":{"c":1}} instead of {"a":{"c":1},"b":2}.
                     writer.writeKey(last_leg->leg_ptr, 
static_cast<uint8_t>(last_leg->leg_len));
                     writer.writeValue(value);
-                    writer.writeEndObject();
                 }
                 writer.writeEndObject();
 
diff --git a/be/test/exprs/function/function_jsonb_test.cpp 
b/be/test/exprs/function/function_jsonb_test.cpp
index fc6744b8b1c..2c898f41d23 100644
--- a/be/test/exprs/function/function_jsonb_test.cpp
+++ b/be/test/exprs/function/function_jsonb_test.cpp
@@ -1117,6 +1117,49 @@ TEST(FunctionJsonbTEST, JsonbModifyMissingPathParent) {
     }
 }
 
+TEST(FunctionJsonbTEST, JsonbModifyNestedMissingMemberKeepsFollowingSiblings) {
+    InputTypeSet input_types = {PrimitiveType::TYPE_JSONB, 
PrimitiveType::TYPE_VARCHAR,
+                                PrimitiveType::TYPE_JSONB};
+
+    // Adding a missing member to a nested object must keep the members that 
follow that
+    // object in its ancestors. Before the fix the enclosing object was closed 
one level
+    // too early, so every following sibling was silently dropped.
+    DataSet data_set = {
+            // Nested empty object followed by a sibling.
+            {{STRING(R"({"a":{},"b":2})"), STRING(R"($.a.c)"), STRING("1")},
+             STRING(R"({"a":{"c":1},"b":2})")},
+            // Non-empty nested object.
+            {{STRING(R"({"a":{"d":4},"b":2})"), STRING(R"($.a.c)"), 
STRING("1")},
+             STRING(R"({"a":{"d":4,"c":1},"b":2})")},
+            // Control: a sibling written before the nested object was already 
correct.
+            {{STRING(R"({"b":2,"a":{}})"), STRING(R"($.a.c)"), STRING("1")},
+             STRING(R"({"b":2,"a":{"c":1}})")},
+            // Two nested levels: the premature close dropped the sibling one 
level up.
+            {{STRING(R"({"a":{"b":{}},"x":1})"), STRING(R"($.a.b.c)"), 
STRING("1")},
+             STRING(R"({"a":{"b":{"c":1}},"x":1})")},
+            // Following siblings of any type must be preserved.
+            {{STRING(R"({"a":{},"b":[1,2],"c":{"d":3}})"), STRING(R"($.a.e)"), 
STRING("1")},
+             STRING(R"({"a":{"e":1},"b":[1,2],"c":{"d":3}})")},
+            // An empty key can be the inserted member.
+            {{STRING(R"({"a":{},"b":2})"), STRING(R"($.a."")"), STRING("1")},
+             STRING(R"({"a":{"":1},"b":2})")},
+            // An object element inside an array was not affected; keep it as 
a guard.
+            {{STRING(R"({"a":[{"x":1}],"b":2})"), STRING(R"($.a[0].y)"), 
STRING("1")},
+             STRING(R"({"a":[{"x":1,"y":1}],"b":2})")},
+    };
+    for (const auto* func_name : {"jsonb_insert", "jsonb_set", "json_insert", 
"json_set"}) {
+        static_cast<void>(check_function<DataTypeJsonb, true>(func_name, 
input_types, data_set));
+    }
+
+    // jsonb_replace rewrites an existing member and already preserved its 
siblings.
+    DataSet replace_data_set = {
+            {{STRING(R"({"a":{"d":4},"b":2})"), STRING("$.a.d"), STRING("5")},
+             STRING(R"({"a":{"d":5},"b":2})")},
+    };
+    static_cast<void>(
+            check_function<DataTypeJsonb, true>("jsonb_replace", input_types, 
replace_data_set));
+}
+
 TEST(FunctionJsonbTEST, JsonbModifyMemberPathOnArray) {
     InputTypeSet input_types = {PrimitiveType::TYPE_JSONB, 
PrimitiveType::TYPE_VARCHAR,
                                 PrimitiveType::TYPE_JSONB};
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 e43d2018d7a..1b539ed3496 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
@@ -96,3 +96,20 @@
 -- !insert_odd_arity_ok --
 {"a":1}        {"a":1,"b":2}   {"a":1,"b":2}
 
+-- !insert_nested_sibling --
+{"a":{"c":1},"b":2}
+
+-- !insert_nested_sibling_nonempty --
+{"a":{"d":4,"c":1},"b":2}
+
+-- !insert_nested_sibling_before --
+{"b":2,"a":{"c":1}}
+
+-- !insert_deep_nested_sibling --
+{"a":{"b":{"c":1}},"x":1}
+
+-- !insert_nested_sibling_json_col --
+1      {"a":{"c":1},"b":2}
+2      {"b":2,"a":{"c":1}}
+3      {"a":{"d":4,"c":1},"b":2}
+
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 e198f86db0b..4561d577261 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
@@ -88,3 +88,20 @@ null
 -- !set_odd_arity_ok --
 {"a":1}        {"a":1,"b":2}   {"a":1,"b":2}
 
+-- !set_nested_sibling --
+{"a":{"c":1},"b":2}
+
+-- !set_nested_sibling_nonempty --
+{"a":{"d":4,"c":1},"b":2}
+
+-- !set_nested_sibling_before --
+{"b":2,"a":{"c":1}}
+
+-- !set_deep_nested_sibling --
+{"a":{"b":{"c":1}},"x":1}
+
+-- !set_nested_sibling_json_col --
+1      {"a":{"c":1},"b":2}
+2      {"b":2,"a":{"c":1}}
+3      {"a":{"d":4,"c":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 25d91e8c03c..a61bc842e90 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
@@ -150,4 +150,35 @@ suite("test_query_json_insert", "query,arrow_flight_sql") {
         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);"""
+
+    // Inserting a missing member into a nested object must keep the members 
that follow it.
+    order_qt_insert_nested_sibling """select json_insert('{"a":{},"b":2}', 
'\$.a.c', 1);"""
+    order_qt_insert_nested_sibling_nonempty """select 
json_insert('{"a":{"d":4},"b":2}', '\$.a.c', 1);"""
+    order_qt_insert_nested_sibling_before """select 
json_insert('{"b":2,"a":{}}', '\$.a.c', 1);"""
+    order_qt_insert_deep_nested_sibling """select 
json_insert('{"a":{"b":{}},"x":1}', '\$.a.b.c', 1);"""
+
+    sql "DROP TABLE IF EXISTS test_query_json_insert_nested_sibling"
+    sql """
+            CREATE TABLE test_query_json_insert_nested_sibling (
+              `id` int(11) not null,
+              `j` json
+            ) ENGINE=OLAP
+            DUPLICATE KEY(`id`)
+            DISTRIBUTED BY HASH(`id`) BUCKETS 1
+            PROPERTIES (
+            "replication_allocation" = "tag.location.default: 1",
+            "in_memory" = "false",
+            "storage_format" = "V2"
+            );
+        """
+    sql """
+        insert into test_query_json_insert_nested_sibling values
+            (1, '{"a":{},"b":2}'),
+            (2, '{"b":2,"a":{}}'),
+            (3, '{"a":{"d":4},"b":2}');
+    """
+    order_qt_insert_nested_sibling_json_col """
+        select id, json_insert(j, '\$.a.c', 1) as v
+        from test_query_json_insert_nested_sibling order by id;
+    """
 }
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 91174fa8827..17f9fe2612a 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
@@ -142,4 +142,35 @@ suite("test_query_json_set", "query") {
         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);"""
+
+    // Setting a missing member on a nested object must keep the members that 
follow it.
+    order_qt_set_nested_sibling """select json_set('{"a":{},"b":2}', '\$.a.c', 
1);"""
+    order_qt_set_nested_sibling_nonempty """select 
json_set('{"a":{"d":4},"b":2}', '\$.a.c', 1);"""
+    order_qt_set_nested_sibling_before """select json_set('{"b":2,"a":{}}', 
'\$.a.c', 1);"""
+    order_qt_set_deep_nested_sibling """select 
json_set('{"a":{"b":{}},"x":1}', '\$.a.b.c', 1);"""
+
+    sql "DROP TABLE IF EXISTS test_query_json_set_nested_sibling"
+    sql """
+            CREATE TABLE test_query_json_set_nested_sibling (
+              `id` int(11) not null,
+              `j` json
+            ) ENGINE=OLAP
+            DUPLICATE KEY(`id`)
+            DISTRIBUTED BY HASH(`id`) BUCKETS 1
+            PROPERTIES (
+            "replication_allocation" = "tag.location.default: 1",
+            "in_memory" = "false",
+            "storage_format" = "V2"
+            );
+        """
+    sql """
+        insert into test_query_json_set_nested_sibling values
+            (1, '{"a":{},"b":2}'),
+            (2, '{"b":2,"a":{}}'),
+            (3, '{"a":{"d":4},"b":2}');
+    """
+    order_qt_set_nested_sibling_json_col """
+        select id, json_set(j, '\$.a.c', 1) as v
+        from test_query_json_set_nested_sibling order by id;
+    """
 }


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

Reply via email to