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]