mrhhsg opened a new pull request, #69288:
URL: https://github.com/apache/doris/pull/69288

   ### What problem does this PR solve?
   
   Issue Number: None
   
   Related PR: None
   
   Problem Summary:
   
   `JSON_SET` / `JSON_INSERT` (and the `jsonb_set` / `jsonb_insert` spellings) 
silently dropped every member that followed a nested object when they inserted 
a missing member into that object.
   
   Reproduction:
   
   ```sql
   mysql> SELECT JSON_SET('{"a":{},"b":2}', '$.a.c', 1);
   +-----------------------------------------+
   | json_set('{"a":{},"b":2}', '$.a.c', 1)  |
   +-----------------------------------------+
   | {"a":{"c":1}}                           |
   +-----------------------------------------+
   ```
   
   The sibling member `"b"` is lost and no error is reported. The same happens 
with a non-empty nested object (`{"a":{"d":4},"b":2}` with `$.a.c` returns 
`{"a":{"d":4,"c":1}}`) and at deeper nesting (`{"a":{"b":{}},"x":1}` with 
`$.a.b.c` returns `{"a":{"b":{"c":1}}}`, dropping `"x"`).
   
   Root cause: in `FunctionJsonbModify::write_json_value` 
(`be/src/exprs/function/function_jsonb.cpp`), the object branch for "last 
parent, member not present" opened and closed an extra object inside the object 
that was already open:
   
   - The extra `writeStartObject()` is rejected by the `JsonbWriter` state 
machine: the current object is already open and waiting for a key, and 
`verifyValueState()` only accepts `Object + WS_Key` or `Array + WS_Value`. Its 
return value is ignored, so the call is a silent no-op.
   - The extra `writeEndObject()` succeeds and closes the current object early. 
The unconditional `writeEndObject()` at the end of the branch then closes the 
enclosing object as well, so when the caller resumes writing the remaining 
members of that object, its `writeKey` / `writeValue` calls fail and their 
return values are ignored too.
   - The writer's size counters have already been backfilled for the 
prematurely closed object, so the result document passes validation and the 
query succeeds with the fields missing.
   
   Fix: remove the extra `writeStartObject()` / `writeEndObject()` pair and 
append the new key/value directly to the already-open object; the existing 
paired `writeEndObject()` closes it normally. After the fix:
   
   ```sql
   SELECT JSON_SET('{"a":{},"b":2}', '$.a.c', 1);          -- 
{"a":{"c":1},"b":2}
   SELECT JSON_SET('{"a":{"d":4},"b":2}', '$.a.c', 1);     -- 
{"a":{"d":4,"c":1},"b":2}
   SELECT JSON_SET('{"a":{"b":{}},"x":1}', '$.a.b.c', 1);  -- 
{"a":{"b":{"c":1}},"x":1}
   ```
   
   Only the "nested object followed by sibling members" shape changes. Inserts 
at the top level, inserts into an object inside an array, `JSON_REPLACE` and 
`JSON_REMOVE` keep their current output and are covered by control cases in the 
new unit test. The change does not affect the JSONB binary format, RPC, 
metadata, configuration or the FE, and existing data needs no migration.
   
   ### Release note
   
   Fix `JSON_SET` and `JSON_INSERT` silently dropping the sibling members that 
follow a nested object when a new member is inserted into that nested object.
   
   ### Check List (For Author)
   
   - Test
       - [x] Regression test
       - [x] Unit Test
       - [ ] Manual test (add detailed scripts or steps below)
       - [ ] No need to test or manual test. Explain why:
           - [ ] This is a refactor/code format and no logic has been changed.
           - [ ] Previous test can cover this change.
           - [ ] No code files have been changed.
           - [ ] Other reason (add your reason)
   
   - Behavior changed:
       - [ ] No.
       - [x] Yes. `JSON_SET` / `JSON_INSERT` now preserve all members of the 
enclosing objects instead of dropping the members that follow a nested object. 
This is the reported bug fix; top-level inserts, inserts into an object inside 
an array, `JSON_REPLACE` and `JSON_REMOVE` are unchanged and covered by control 
cases.
   
   - Does this need documentation?
       - [x] No.
   
   ### Testing details
   
   **Unit test (BE)**
   
   - New test 
`FunctionJsonbTEST.JsonbModifyNestedMissingMemberKeepsFollowingSiblings` in 
`be/test/exprs/function/function_jsonb_test.cpp`. It covers 7 data rows (empty 
nested object, non-empty nested object, sibling written before the target, 
two-level nesting, mixed-type siblings, empty key, and an object element inside 
an array as a control) against all four names `jsonb_insert`, `jsonb_set`, 
`json_insert`, `json_set`, plus a `jsonb_replace` control.
   - `./run-be-ut.sh --run 
--filter=FunctionJsonbTEST.JsonbModifyNestedMissingMemberKeepsFollowingSiblings 
-j 48` — PASSED (1 test).
   - `./run-be-ut.sh --run --filter=FunctionJsonbTEST.* -j 48` — PASSED (20/20 
tests).
   - Verification against the unfixed code: with the product change temporarily 
reverted, the new test reports 20 assertion failures (5 data rows x 4 function 
names), for example `result: {"a":{"c":1}}, expected result: 
{"a":{"c":1},"b":2}`. The two control rows (sibling written before the target; 
object inside an array) and the `jsonb_replace` case still pass, which pins the 
failure to the changed branch.
   
   **Regression test**
   
   - `test_query_json_insert` and `test_query_json_set` 
(`query_p0/sql_functions/json_function`) — both suites pass. Each suite gains 5 
cases: 4 constant SQL cases (minimal reproduction, non-empty nested object, 
sibling-before control, two-level nesting) and 1 case over a real `json` column 
with `ORDER BY id`.
   - The checked-in `.out` files were generated by the regression framework 
(not written by hand) and pass again on a re-run without regenerating them.
   
   **Formatting / static checks**
   
   - `build-support/clang-format.sh` (clang-format 16): exit code 0, no file 
changed.
   - `build-support/check-build-hygiene.sh`: all checks passed.
   - `build-support/run-clang-tidy.sh --build-dir be/build_ASAN --files 
be/src/exprs/function/function_jsonb.cpp 
be/test/exprs/function/function_jsonb_test.cpp`: could not complete in the 
local environment because the installed clang-tidy cannot find `stddef.h` (a 
local toolchain issue). All reported size/complexity warnings are in functions 
that predate this change, and none is on a changed line.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to