Skip to content

[fix](json) Reject malformed JSON number tokens instead of normalizing them - #68484

Open
mrhhsg wants to merge 3 commits into
apache:masterfrom
mrhhsg:fix/json-parse-reject-malformed-numbers
Open

mrhhsg wants to merge 3 commits into
apache:masterfrom
mrhhsg:fix/json-parse-reject-malformed-numbers

Conversation

@mrhhsg

@mrhhsg mrhhsg commented Sep 24, 2026

Copy link
Copy Markdown
Member

What problem does this PR solve?

Issue Number: None

Problem Summary:

JSON_PARSE, JSON_PARSE_ERROR_TO_NULL, JSON_PARSE_ERROR_TO_VALUE,
CAST(... AS JSON), JSON column loads and every other path that goes through
JsonbParser accepted invalid JSON numbers such as 01, 00, -01, 1.,
01.5 or 1.e5 and silently turned them into a different valid value: 01
became 0, 1. became 1, 1.e5 became 100000, and 1e400 became inf.
This happened at the top level as well as inside arrays and objects, so
malformed source data was normalized instead of rejected.

Root cause: JsonbParser::parse_number_success treated simdjson's
NUMBER_ERROR as success in order to support integers just above the uint64
range (simdjson reports 18446744073709551616 as NUMBER_ERROR and longer
integers as BIGINT_ERROR). However, simdjson also returns NUMBER_ERROR for
malformed tokens (leading zeros, a trailing ., an incomplete exponent,
trailing garbage) and for values beyond the double range. simdjson_result::get
leaves the caller's simdjson::ondemand::number untouched on error, so the
writer emitted its zero-initialized payload for integer-looking tokens; for
float-looking tokens the number == 0 branch re-parsed the raw token with
StringParser::string_to_float, which accepts 1., 01.5 and 1e400.

Fix: write_number only falls back to the raw token for NUMBER_ERROR and
BIGINT_ERROR; any other error is returned as InvalidArgument. The raw token is validated against the JSON number grammar
-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)? (after trimming the trailing
JSON whitespace that raw_json_token() may include); a digits-only token is
then parsed as int128, anything else as a finite double. A root number must
additionally reach the end of the document, because simdjson reports
NUMBER_ERROR for 18446744073709551616 0 before it checks for trailing
content and the raw token stops at the first token. Malformed input now fails
with InvalidArgument, so JSON_PARSE raises an error and
JSON_PARSE_ERROR_TO_NULL returns NULL. The successful path takes the number
type from the parsed number object and no longer re-parses zero values with
StringParser, since a value simdjson parsed successfully is already exact.

Before:

SELECT json_parse('01'), json_parse('[1.]'), json_parse('{"k":1.e5}'), json_parse('1e400');
-- 0    [1]    {"k":100000}    inf

After:

SELECT json_parse('01');
-- ERROR: Parse json document failed at row 0, error: [INVALID_ARGUMENT]simdjson get_number failed: NUMBER_ERROR: Problem while parsing a number, raw string is: 01
SELECT json_parse_error_to_null('01');
-- NULL

Valid large numbers keep working: 18446744073709551616 and
-9223372036854775809 are still stored as int128, and integers beyond int128
still fall back to double. As a side effect, a root number longer than
simdjson's root scalar buffer (1082 bytes) that contains a fraction or an
exponent is now accepted through the same raw-token path instead of failing;
the nested form of such numbers was already accepted.

Release note

JSON text parsing (JSON_PARSE and its variants, CAST(... AS JSON),
JSON_VALID, and loading into JSON columns) now rejects malformed JSON number
tokens such as 01, 00, 1. or 1.e5, and numbers beyond the double range
such as 1e400, instead of silently converting them to a different value.

Check List (For Author)

  • Test:
    • Unit Test: JsonbParserTest.* in be/test/util/jsonb_parser_simd_test.cpp
    • Regression test: datatype_p0/json/test_json_parse_invalid_number
  • Behavior changed: Yes (invalid JSON number tokens and numbers beyond the double range are rejected on every JSON text parsing path; previously they were normalized to a different value)
  • Does this need documentation: No

@hello-stephen

Copy link
Copy Markdown
Contributor

Thank you for your contribution to Apache Doris.
Don't know what should be done next? See How to process your PR.

Please clearly describe your PR:

  1. What problem was fixed (it's best to include specific error reporting information). How it was fixed.
  2. Which behaviors were modified. What was the previous behavior, what is it now, why was it modified, and what possible impacts might there be.
  3. What features were added. Why was this function added?
  4. Which code was refactored and why was this part of the code refactored?
  5. Which functions were optimized and what is the difference before and after the optimization?

@mrhhsg

mrhhsg commented Sep 24, 2026

Copy link
Copy Markdown
Member Author

/review-light

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Static review result: no blocking correctness issue found at exact head ebb82ff6de08a3b9c125f3fd692b649513b1bd31.

Critical checkpoint conclusions:

  • Goal and proof: the change stops treating simdjson NUMBER_ERROR/BIGINT_ERROR as unconditional success, validates fallback tokens against the exact JSON-number grammar, preserves representable large integers, and rejects non-finite conversion. The added BE unit cases and SQL regression cover malformed root/nested tokens, trailing content, uint64/int128 boundaries, long roots, overflow, underflow, constant inputs, casts, error modes, and json_valid.
  • Scope and clarity: the implementation is focused in the shared JsonbParser path, with direct boundary tests and no unrelated production changes.
  • Concurrency: not applicable. Parser, padded input, token views, and writer state are invocation-local; no shared mutable state or locking is introduced.
  • Lifecycle/static initialization: token views remain within the lifetime of the local padded input and are consumed synchronously. No static/global lifecycle or cross-TU initialization is added.
  • Configuration: no configuration item is added or changed.
  • Compatibility: this intentionally tightens acceptance of malformed or non-representable JSON text. Existing finite numbers and large-integer handling are preserved, and no JSONB physical format, symbol, protocol, or rolling-upgrade contract changes.
  • Parallel paths: JsonBinaryValue::from_json_string is the common ingress used by the SQL parse/error variants, json_valid, string-to-JSON casts, literals/constant folding, JSON SerDe, and relevant load/connector paths. The unrelated DOM/Variant parsers are not bypasses for this JSONB text contract.
  • Conditional checks: the root end-pointer check is necessary because simdjson 3.11.6 returns the numeric error before checking trailing content for these cases; nested iteration still validates the following comma/closing delimiter. The grammar, whitespace trim, int128 result check, and finite-double gate each correspond to a concrete input class.
  • Test coverage: the new unit and regression coverage is broad across positive, negative, root, nested, constant, and vectorized paths; existing ordinary-number tests continue to cover the normal success path.
  • Expected results: the new .out rows are internally consistent, deterministic (order_qt plus ORDER BY), and follow the repository's error-test conventions. I did not independently regenerate them.
  • Observability: returned errors identify the simdjson error and offending token; this local parsing change does not warrant new logs or metrics.
  • Transactions/persistence/data writes: no transaction, EditLog, publish, storage-version, delete-bitmap, or write-atomicity mechanism is changed. Load behavior changes only through the shared validation result.
  • FE/BE variables: no new cross-process or FE/BE field is introduced.
  • Performance: ordinary successful parsing avoids the old zero-value reparse. Extra grammar/conversion passes occur only on simdjson numeric-error fallback paths and remain linear; no material hot-path regression was found.
  • Other review points: header hygiene passed, and current CI reports the formatter/style gates passing. No additional correctness, memory-safety, error-propagation, portability, or material performance issue was substantiated.

User focus (/review-light, represented as -light in the bundle): no additional focus-specific issue was found; the full required review was still completed.

Validation scope: static review only. Per the runner instruction, I did not build Doris or execute the author-listed BE unit/regression tests, so their claimed execution is not independently verified here.

@mrhhsg

mrhhsg commented Sep 24, 2026

Copy link
Copy Markdown
Member Author

run buildall

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-H: Total hot run time: 27456 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
Tpch sf100 test result on commit ebb82ff6de08a3b9c125f3fd692b649513b1bd31, data reload: false

------ Round 1 ----------------------------------
============================================
q1	17654	3768	3731	3731
q2	2145	358	290	290
q3	10112	1370	775	775
q4	4693	478	356	356
q5	7480	822	564	564
q6	178	167	136	136
q7	740	790	607	607
q8	9317	1552	1453	1453
q9	5397	4187	4140	4140
q10	6818	1303	1024	1024
q11	447	270	239	239
q12	636	426	293	293
q13	18054	2604	1995	1995
q14	258	261	237	237
q15	q16	729	710	665	665
q17	1711	1180	1096	1096
q18	6463	5577	5506	5506
q19	1167	1263	1050	1050
q20	496	394	266	266
q21	5541	2948	2732	2732
q22	412	349	301	301
Total cold run time: 100448 ms
Total hot run time: 27456 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4110	4006	3988	3988
q2	704	568	524	524
q3	4454	4833	4322	4322
q4	2173	2274	1411	1411
q5	4158	4102	4061	4061
q6	219	171	127	127
q7	1699	1542	1411	1411
q8	2146	1899	2356	1899
q9	7360	7253	7298	7253
q10	3715	3602	3125	3125
q11	541	396	360	360
q12	710	710	528	528
q13	2457	2744	2134	2134
q14	301	294	261	261
q15	q16	691	714	629	629
q17	7870	7274	7018	7018
q18	11886	11106	11716	11106
q19	1164	1061	1066	1061
q20	2193	2212	1925	1925
q21	5298	4363	4400	4363
q22	521	460	396	396
Total cold run time: 64370 ms
Total hot run time: 57902 ms

@mrhhsg

mrhhsg commented Sep 24, 2026

Copy link
Copy Markdown
Member Author

run cloud_p0

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 151924 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
TPC-DS sf100 test result on commit ebb82ff6de08a3b9c125f3fd692b649513b1bd31, data reload: false

query5	4338	601	447	447
query6	424	204	193	193
query7	4846	568	302	302
query8	315	168	158	158
query9	8818	3934	3933	3933
query10	462	302	258	258
query11	5857	3529	3224	3224
query12	152	93	86	86
query13	1247	595	435	435
query14	6538	4509	4221	4221
query14_1	3942	3922	3947	3922
query15	203	200	180	180
query16	985	486	421	421
query17	899	663	533	533
query18	2428	458	344	344
query19	201	179	141	141
query20	81	83	83	83
query21	217	136	115	115
query22	13019	12945	12748	12748
query23	14041	12990	12410	12410
query23_1	12481	12608	12456	12456
query24	7245	1166	653	653
query24_1	659	697	801	697
query25	553	431	412	412
query26	1255	296	159	159
query27	2690	537	334	334
query28	4523	2001	1981	1981
query29	1647	704	492	492
query30	295	218	179	179
query31	884	747	628	628
query32	142	99	95	95
query33	507	304	238	238
query34	1172	1134	600	600
query35	711	734	634	634
query36	779	811	714	714
query37	142	102	93	93
query38	1823	1751	1686	1686
query39	716	683	654	654
query39_1	640	637	666	637
query40	225	120	96	96
query41	65	78	65	65
query42	100	98	93	93
query43	331	343	296	296
query44	1375	715	727	715
query45	189	177	158	158
query46	1066	1197	700	700
query47	1479	1503	1422	1422
query48	394	415	282	282
query49	581	403	288	288
query50	939	338	254	254
query51	10286	10298	10442	10298
query52	89	87	75	75
query53	239	255	182	182
query54	261	196	182	182
query55	81	72	68	68
query56	221	226	215	215
query57	1317	1411	1338	1338
query58	282	262	241	241
query59	1989	2077	1857	1857
query60	273	234	220	220
query61	145	136	139	136
query62	400	321	262	262
query63	214	179	176	176
query64	2782	1026	775	775
query65	3439	3413	3427	3413
query66	1790	424	301	301
query67	19830	20009	20053	20009
query68	3136	1520	959	959
query69	419	295	263	263
query70	915	816	815	815
query71	281	229	224	224
query72	2842	2517	2120	2120
query73	794	761	415	415
query74	4621	4492	4292	4292
query75	2280	2256	1962	1962
query76	2312	1104	756	756
query77	366	386	291	291
query78	8960	9050	8458	8458
query79	1209	1192	759	759
query80	510	442	370	370
query81	519	323	278	278
query82	258	169	124	124
query83	211	220	188	188
query84	292	144	115	115
query85	772	444	372	372
query86	291	250	230	230
query87	1992	1941	1833	1833
query88	3593	2736	2752	2736
query89	320	276	245	245
query90	2093	184	182	182
query91	165	157	130	130
query92	95	86	90	86
query93	1433	1379	872	872
query94	509	329	301	301
query95	651	463	333	333
query96	1027	809	313	313
query97	2422	2418	2300	2300
query98	153	147	145	145
query99	708	719	615	615
Total cold run time: 233970 ms
Total hot run time: 151924 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
ClickBench: Total hot run time: 23.89 s
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/clickbench-tools
ClickBench test result on commit ebb82ff6de08a3b9c125f3fd692b649513b1bd31, data reload: false

query1	0.00	0.00	0.00
query2	0.09	0.04	0.04
query3	0.26	0.14	0.13
query4	1.61	0.13	0.13
query5	0.23	0.21	0.22
query6	1.16	0.94	0.92
query7	0.04	0.01	0.00
query8	0.06	0.04	0.04
query9	0.38	0.33	0.34
query10	0.54	0.53	0.53
query11	0.20	0.14	0.14
query12	0.19	0.14	0.15
query13	0.46	0.47	0.47
query14	0.95	0.96	0.94
query15	0.61	0.58	0.60
query16	0.30	0.32	0.31
query17	1.06	1.08	1.04
query18	0.21	0.20	0.20
query19	2.06	1.89	1.91
query20	0.02	0.01	0.01
query21	15.49	0.21	0.14
query22	4.81	0.05	0.05
query23	16.15	0.32	0.12
query24	2.94	0.42	0.32
query25	0.10	0.06	0.04
query26	0.73	0.21	0.16
query27	0.04	0.04	0.03
query28	3.51	0.76	0.33
query29	12.51	4.11	3.20
query30	0.27	0.14	0.14
query31	2.80	0.55	0.31
query32	3.22	0.58	0.49
query33	3.15	3.22	3.25
query34	15.49	3.92	3.30
query35	3.24	3.22	3.22
query36	0.56	0.43	0.41
query37	0.09	0.06	0.06
query38	0.05	0.04	0.04
query39	0.04	0.03	0.03
query40	0.18	0.15	0.14
query41	0.09	0.03	0.03
query42	0.04	0.03	0.03
query43	0.05	0.03	0.04
Total cold run time: 95.98 s
Total hot run time: 23.89 s

…g them

### What problem does this PR solve?

Issue Number: None

Problem Summary:

`JSON_PARSE`, `JSON_PARSE_ERROR_TO_NULL`, `JSON_PARSE_ERROR_TO_VALUE`,
`CAST(... AS JSON)`, JSON column loads and every other path that goes through
`JsonbParser` accepted invalid JSON numbers such as `01`, `00`, `-01`, `1.`,
`01.5` or `1.e5` and silently turned them into a different valid value: `01`
became `0`, `1.` became `1`, `1.e5` became `100000`, and `1e400` became `inf`.
This happened at the top level as well as inside arrays and objects, so
malformed source data was normalized instead of rejected.

Root cause: `JsonbParser::parse_number_success` treated simdjson's
`NUMBER_ERROR` as success in order to support integers just above the uint64
range (simdjson reports `18446744073709551616` as `NUMBER_ERROR` and longer
integers as `BIGINT_ERROR`). However, simdjson also returns `NUMBER_ERROR` for
malformed tokens (leading zeros, a trailing `.`, an incomplete exponent,
trailing garbage) and for values beyond the double range. `simdjson_result::get`
leaves the caller's `simdjson::ondemand::number` untouched on error, so the
writer emitted its zero-initialized payload for integer-looking tokens; for
float-looking tokens the `number == 0` branch re-parsed the raw token with
`StringParser::string_to_float`, which accepts `1.`, `01.5` and `1e400`.

Fix: `write_number` only falls back to the raw token for `NUMBER_ERROR` and
`BIGINT_ERROR`; any other error is returned as `InvalidArgument`. The raw token is validated against the JSON number grammar
`-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?` (after trimming the trailing
JSON whitespace that `raw_json_token()` may include); a digits-only token is
then parsed as int128, anything else as a finite double. A root number must
additionally reach the end of the document, because simdjson reports
`NUMBER_ERROR` for `18446744073709551616 0` before it checks for trailing
content and the raw token stops at the first token. Malformed input now fails
with `InvalidArgument`, so `JSON_PARSE` raises an error and
`JSON_PARSE_ERROR_TO_NULL` returns NULL. The successful path takes the number
type from the parsed `number` object and no longer re-parses zero values with
`StringParser`, since a value simdjson parsed successfully is already exact.

Before:
```
SELECT json_parse('01'), json_parse('[1.]'), json_parse('{"k":1.e5}'), json_parse('1e400');
-- 0    [1]    {"k":100000}    inf
```
After:
```
SELECT json_parse('01');
-- ERROR: Parse json document failed at row 0, error: [INVALID_ARGUMENT]simdjson get_number failed: NUMBER_ERROR: Problem while parsing a number, raw string is: 01
SELECT json_parse_error_to_null('01');
-- NULL
```

Valid large numbers keep working: `18446744073709551616` and
`-9223372036854775809` are still stored as int128, and integers beyond int128
still fall back to double. As a side effect, a root number longer than
simdjson's root scalar buffer (1082 bytes) that contains a fraction or an
exponent is now accepted through the same raw-token path instead of failing;
the nested form of such numbers was already accepted.

### Release note

JSON text parsing (`JSON_PARSE` and its variants, `CAST(... AS JSON)`,
`JSON_VALID`, and loading into JSON columns) now rejects malformed JSON number
tokens such as `01`, `00`, `1.` or `1.e5`, and numbers beyond the double range
such as `1e400`, instead of silently converting them to a different value.

### Check List (For Author)

- Test:
    - Unit Test: `JsonbParserTest.*` in `be/test/util/jsonb_parser_simd_test.cpp`
    - Regression test: `datatype_p0/json/test_json_parse_invalid_number`
    - Regression test: `external_table_p0/dialect_compatible/sql/presto/scalar/TestJsonFunctions`
      expected output updated: `json_array_contains('[9.6E400]', 4.2)` now yields NULL
      because `9.6E400` is beyond the double range and is rejected
- Behavior changed: Yes (invalid JSON number tokens and numbers beyond the double range are rejected on every JSON text parsing path; previously they were normalized to a different value)
- Does this need documentation: No
@mrhhsg
mrhhsg force-pushed the fix/json-parse-reject-malformed-numbers branch from ebb82ff to 852c4aa Compare September 24, 2026 14:19
@mrhhsg

mrhhsg commented Sep 24, 2026

Copy link
Copy Markdown
Member Author

/review-light

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Static review outcome: request changes for one P2 resource-amplification issue in the newly added malformed-number diagnostics.

Critical checkpoint conclusions:

  • Goal: the parser change otherwise implements strict JSON-number grammar, preserves supported int128/finite-double values, and rejects malformed, trailing, and non-finite inputs. The added unit and regression cases exercise the core behavior.
  • Scope: the production change is focused on number parsing; the accompanying unit/regression and Presto expected-output updates match that scope.
  • Concurrency: parsing uses invocation-local parser/writer state and adds no threads, shared mutable state, locks, or atomic-ordering concern.
  • Lifecycle/static initialization: no nontrivial ownership, cleanup, static initialization, or cross-TU lifecycle is introduced.
  • Configuration: no configuration item or dynamic-reload behavior is added.
  • Compatibility: JSONB storage/protocol layout and function symbols are unchanged. Mixed-version nodes may temporarily differ on acceptance of malformed numeric text, which is the intended behavior change.
  • Parallel paths: JSON_PARSE variants, JSON_VALID, strict/non-strict string-to-JSON casts, and text/SerDe/load conversions converge on JsonBinaryValue::from_json_string; no alternate text parser requiring the same fix was found.
  • Conditions/error handling: the root end check and grammar/fallback branches are explained and statuses are propagated or deliberately converted to fail/NULL/default/false. The one remaining error-path problem is the unbounded diagnostic called out inline.
  • Tests: coverage includes root/nested malformed forms, trailing content, whitespace, integer boundaries, long scalars, overflow, underflow, constants, vectors, and tolerant modes. Explicit strict-cast/load cases would strengthen coverage but share the same parser path and did not reveal a separate defect.
  • Expected results: the new outputs are internally consistent, including the Presto overflow case becoming NULL. This was a static-only review; I did not run builds or tests, so author/CI results were not independently executed here.
  • Observability: no new metrics or logs are needed for this local parser change; diagnostics should remain useful but bounded.
  • Transactions/persistence/data writes: no EditLog, transaction, publication, atomic-write, crash-recovery, or persistent-format logic changes.
  • FE/BE variables: no new cross-process field or sending path is introduced.
  • Performance/memory: the accepted P2 finding is a new O(input)-sized error allocation on tolerant paths that discard the message. No other CPU, allocation, or memory-safety issue was substantiated.
  • Other issues/user focus: -light supplied no additional targeted concern. Two full review rounds converged with all candidates resolved and no additional valuable finding.

Validation: static inspection only, as required by the review harness; no build or test was run.

Comment thread be/src/util/jsonb_parser_simd.h
…rors

### What problem does this PR solve?

Issue Number: None

Related PR: apache#68484

Problem Summary:

The malformed-number diagnostics added to `JsonbParser` formatted the whole
raw token into the returned `Status`. The token can be as long as the input,
and tolerant callers such as `JSON_VALID`, `JSON_PARSE_ERROR_TO_NULL` and
`JSON_PARSE_ERROR_TO_VALUE` discard the message immediately, so rejecting a
multi-megabyte malformed number allocated another input-sized string on top of
the source column and simdjson's padded copy.

`quote_token` now quotes at most 64 bytes of the token and reports its full
length instead; every number diagnostic, including the root trailing-content
check, goes through it.

### Release note

None

### Check List (For Author)

- Test:
    - Unit Test: `JsonbParserTest.ParseJsonMalformedNumberErrorMessageIsBounded` in `be/test/util/jsonb_parser_simd_test.cpp`
    - Regression test: `datatype_p0/json/test_json_parse_invalid_number` (unchanged, error text still matches)
- Behavior changed: No (only the quoted part of the error message is bounded)
- Does this need documentation: No
@mrhhsg

mrhhsg commented Sep 24, 2026

Copy link
Copy Markdown
Member Author

/review-light

@github-actions

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: Codex exited with status 0 without a terminal turn event; review is incomplete
Workflow run: https://github.com/apache/doris/actions/runs/36027463445

Please inspect the workflow logs and rerun the review after the underlying issue is resolved.

@mrhhsg

mrhhsg commented Sep 25, 2026

Copy link
Copy Markdown
Member Author

/review-light

@github-actions

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: Codex exited with status 0 without a terminal turn event; review is incomplete
Workflow run: https://github.com/apache/doris/actions/runs/36084169563

Please inspect the workflow logs and rerun the review after the underlying issue is resolved.

@mrhhsg

mrhhsg commented Sep 25, 2026

Copy link
Copy Markdown
Member Author

/review-light

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Static review outcome: request changes for one P2 resource-amplification issue in the CAST-to-JSONB error path.

Critical checkpoint conclusions:

  • Goal and correctness: the parser now validates the JSON number grammar, preserves supported int128 and finite-double values, rejects malformed/trailing/non-finite numbers, and keeps root/nested behavior aligned.
  • Scope: the production change is focused in the shared JsonbParser path; the unit and regression updates exercise malformed root/nested forms, boundaries, overflow/underflow, constants, casts, tolerant modes, and JSON_VALID.
  • Concurrency/lifecycle/configuration: parser and writer state are invocation-local; no locks, shared mutable state, static initialization, or configuration behavior is changed.
  • Compatibility/parallel paths: JSONB storage/protocol contracts are unchanged, and the inspected JSON_PARSE, JSON_VALID, error-tolerant, constant, SerDe/load, and CAST paths converge on the reviewed parser.
  • Error and memory behavior: parser-side raw-token diagnostics are bounded, but the CAST wrapper still materializes the full source on every parse failure; the inline comment calls out this distinct downstream hole.
  • Test/validation status: expected outputs and coverage are internally consistent, but this is static-only review. I did not build Doris or execute the author-listed tests, as prohibited by the review harness.
  • User focus (-light): no additional focus-specific issue was found.

The existing parser diagnostic thread was treated as a hard duplicate fence; the inline issue below is specifically the separate CAST caller allocation.

Comment thread be/src/util/jsonb_parser_simd.h
### What problem does this PR solve?

Issue Number: None

Related PR: apache#68484

Problem Summary:

`ParseJsonbFromString::parse_json` formatted the whole source string into the
returned `Status` whenever parsing failed. For a multi-megabyte malformed value
both strict and non-strict CAST to JSONB allocated and copied another
input-sized string, and non-strict CAST discards the status right away.

The bounding helper used by `JsonbParser` number diagnostics is now exposed as
`JsonbParser::bounded_quote`, and the CAST wrapper quotes the source through
it: at most 64 bytes plus the full length.

### Release note

None

### Check List (For Author)

- Test:
    - Unit Test: `FunctionCastTest.test_strict_cast_string_to_jsonb_*` in `be/test/exprs/function/cast/cast_to_jsonb_test.cpp`, `JsonbParserTest.*`
    - Regression test: `function_p0/cast/test_try_cast`, `datatype_p0/json/test_json_parse_invalid_number`
- Behavior changed: No (only the quoted part of the error message is bounded)
- Does this need documentation: No
@mrhhsg

mrhhsg commented Sep 25, 2026

Copy link
Copy Markdown
Member Author

/review-light

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Static review result: no new blocking correctness issue found at exact head 1a1da27f6bde54a8649ed1763a37e07fc5926b2c. The two earlier P2 diagnostic-allocation issues are fixed at this head, so no duplicate inline comments are being submitted.

Critical checkpoint conclusions:

  • Goal and proof: the shared JSONB parser now rejects malformed JSON-number grammar and non-finite conversions while preserving exact int128 values and finite-double fallback. The added parser unit cases and SQL regression cover malformed root/nested tokens, trailing content, uint64/int128 boundaries, long roots, overflow, underflow, constants, casts, tolerant modes, and json_valid; the CAST unit test covers short and 1 MiB bounded diagnostics.
  • Scope and clarity: the implementation is focused in JsonbParser and the string-to-JSONB CAST error wrapper, with corresponding unit/regression and expected-output updates. No unrelated production behavior is changed.
  • Concurrency: not applicable. The parser, padded input, token views, number state, and writer are invocation-local; no shared state, threads, locks, or atomics are introduced.
  • Lifecycle/static initialization: raw-token views are consumed synchronously while the local padded input is alive, and reusable writer state is reset before each parse. No static/global lifetime or cross-TU initialization is added.
  • Configuration: no configuration item or dynamic-reload behavior is added.
  • Compatibility: the intended mixed-version difference is acceptance versus rejection of invalid numeric text. Accepted values retain the existing JSONB writer types; there is no storage layout, function symbol, wire protocol, or FE/BE contract change.
  • Parallel paths: JsonBinaryValue::from_json_string is the common ingress for json_parse modes, json_valid, strict/non-strict string CAST, JSON SerDe/load conversion, literals, and located connectors. Callers consistently propagate failure or intentionally map it to NULL/default/false.
  • Conditional checks: simdjson 3.11.6 can return NUMBER_ERROR/BIGINT_ERROR before root trailing-content validation, so the raw-token end check is necessary. Its token extent includes legal trailing JSON whitespace through EOF and ends before a following token; nested iteration still validates commas and closing delimiters. The exact grammar, int128 parse-result check, and finite-double gate each address a concrete input class.
  • Test coverage: negative and positive root, array, object, constant, and vectorized paths are covered, including bounded multi-megabyte errors. A dedicated load regression is not added, but the inspected load SerDe uses the same parser boundary. No missing test exposed a distinct correctness defect.
  • Expected results: the new result file is deterministic and consistent with the suite. The Presto change from false to NULL corresponds to parsing invalid [9.6E400]. Result files were not independently regenerated in this runner.
  • Observability: bounded errors retain the simdjson error, offending prefix, and full byte length. This local validation change does not warrant new logs or metrics.
  • Transactions/persistence/data writes: no transaction, EditLog, publish, storage-version, delete-bitmap, crash-recovery, or atomic-write mechanism is modified.
  • FE/BE variables: no cross-process field or additional sending path is introduced.
  • Performance and memory: ordinary successful numbers remain on simdjson's fast path. Recovery performs linear, allocation-free token scans only on numeric-error/oversized-number paths, and all newly relevant diagnostics are bounded. No material hot-path or memory-safety issue was substantiated.
  • Other review points: the success-only big_integer arm is unreachable under the pinned simdjson get_number() contract, header/build hygiene passed, and the full changed-file sweep left no unresolved candidate.

User focus (-light): no additional focus-specific concern was supplied or found; the full required review was still completed.

Validation scope: static review only. Per the runner instruction, I did not build Doris or execute the author-listed BE unit/regression tests. The author's reported ASAN/unit/regression runs were considered but are not independently verified here.

@mrhhsg

mrhhsg commented Sep 25, 2026

Copy link
Copy Markdown
Member Author

run buildall

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-H: Total hot run time: 27939 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
Tpch sf100 test result on commit 1a1da27f6bde54a8649ed1763a37e07fc5926b2c, data reload: false

------ Round 1 ----------------------------------
============================================
q1	17779	4040	3860	3860
q2	2197	368	309	309
q3	10025	1400	783	783
q4	4684	477	351	351
q5	7490	855	541	541
q6	182	178	141	141
q7	729	768	595	595
q8	9352	1485	1602	1485
q9	5448	4202	4176	4176
q10	6819	1325	1013	1013
q11	420	270	258	258
q12	630	411	298	298
q13	18111	2635	1996	1996
q14	266	263	236	236
q15	q16	737	718	651	651
q17	1875	1144	1021	1021
q18	6467	5623	5540	5540
q19	1326	1237	1062	1062
q20	491	399	261	261
q21	5810	3492	3059	3059
q22	452	363	303	303
Total cold run time: 101290 ms
Total hot run time: 27939 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4561	4578	4511	4511
q2	757	591	558	558
q3	4831	5310	4614	4614
q4	2251	2350	1438	1438
q5	4682	4493	4523	4493
q6	229	174	130	130
q7	1851	1701	1559	1559
q8	2497	2246	2068	2068
q9	7152	6925	6872	6872
q10	3607	3535	3081	3081
q11	513	411	370	370
q12	704	693	501	501
q13	2310	2612	2002	2002
q14	274	289	246	246
q15	q16	670	682	624	624
q17	7305	6816	6685	6685
q18	11879	11087	11755	11087
q19	1118	993	1003	993
q20	2225	2203	1932	1932
q21	5055	4109	4324	4109
q22	514	447	398	398
Total cold run time: 64985 ms
Total hot run time: 58271 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 152445 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
TPC-DS sf100 test result on commit 1a1da27f6bde54a8649ed1763a37e07fc5926b2c, data reload: false

query5	4321	612	455	455
query6	418	202	187	187
query7	4822	572	284	284
query8	328	183	163	163
query9	8804	4020	4013	4013
query10	454	296	253	253
query11	5833	3518	3234	3234
query12	156	93	87	87
query13	1267	568	415	415
query14	6517	4529	4223	4223
query14_1	4006	3993	3960	3960
query15	204	202	182	182
query16	1006	437	450	437
query17	919	677	545	545
query18	2466	466	336	336
query19	217	185	150	150
query20	90	84	81	81
query21	235	135	118	118
query22	13070	13027	12821	12821
query23	13881	12881	12333	12333
query23_1	12443	12516	12393	12393
query24	7393	1175	721	721
query24_1	669	704	720	704
query25	555	442	369	369
query26	1282	310	175	175
query27	2663	555	334	334
query28	4565	2052	1980	1980
query29	1643	697	487	487
query30	300	214	181	181
query31	888	761	620	620
query32	155	96	90	90
query33	527	310	243	243
query34	1205	1130	627	627
query35	734	749	634	634
query36	780	817	704	704
query37	146	105	88	88
query38	1827	1768	1688	1688
query39	711	707	667	667
query39_1	671	647	662	647
query40	214	124	109	109
query41	70	63	63	63
query42	94	94	90	90
query43	347	347	304	304
query44	1452	720	728	720
query45	184	178	166	166
query46	1090	1231	736	736
query47	1501	1480	1416	1416
query48	400	407	293	293
query49	578	399	287	287
query50	926	381	263	263
query51	10364	10546	10423	10423
query52	87	87	78	78
query53	237	255	177	177
query54	265	207	208	207
query55	80	74	73	73
query56	223	223	201	201
query57	1485	1506	1350	1350
query58	280	262	250	250
query59	2006	2071	1853	1853
query60	278	245	217	217
query61	148	145	150	145
query62	399	320	269	269
query63	222	178	181	178
query64	2786	1038	823	823
query65	3479	3437	3412	3412
query66	1783	429	313	313
query67	19968	20063	19729	19729
query68	3187	1517	1002	1002
query69	403	310	257	257
query70	937	808	824	808
query71	295	234	215	215
query72	2582	2594	2204	2204
query73	862	794	453	453
query74	4637	4475	4288	4288
query75	2300	2296	1950	1950
query76	2311	1138	807	807
query77	369	400	306	306
query78	8948	9106	8410	8410
query79	1196	1169	758	758
query80	535	490	371	371
query81	513	321	277	277
query82	264	160	126	126
query83	218	229	192	192
query84	290	144	113	113
query85	797	452	384	384
query86	284	249	230	230
query87	1986	1980	1860	1860
query88	3676	2774	2736	2736
query89	332	292	243	243
query90	2108	182	184	182
query91	171	156	134	134
query92	103	89	94	89
query93	1435	1571	832	832
query94	504	366	293	293
query95	664	393	427	393
query96	1093	845	328	328
query97	2422	2428	2326	2326
query98	172	150	149	149
query99	712	726	612	612
Total cold run time: 234989 ms
Total hot run time: 152445 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
ClickBench: Total hot run time: 24.08 s
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/clickbench-tools
ClickBench test result on commit 1a1da27f6bde54a8649ed1763a37e07fc5926b2c, data reload: false

query1	0.01	0.01	0.00
query2	0.09	0.05	0.05
query3	0.25	0.14	0.13
query4	1.61	0.14	0.14
query5	0.25	0.22	0.23
query6	1.14	0.93	0.96
query7	0.03	0.01	0.00
query8	0.05	0.04	0.03
query9	0.39	0.35	0.34
query10	0.55	0.53	0.55
query11	0.20	0.14	0.14
query12	0.19	0.15	0.14
query13	0.47	0.47	0.48
query14	0.93	0.96	0.94
query15	0.59	0.60	0.59
query16	0.34	0.32	0.32
query17	1.08	1.09	1.10
query18	0.21	0.20	0.20
query19	2.00	2.03	1.95
query20	0.01	0.01	0.01
query21	15.48	0.18	0.15
query22	4.93	0.05	0.05
query23	16.16	0.31	0.11
query24	2.94	0.45	0.33
query25	0.12	0.06	0.04
query26	0.72	0.21	0.17
query27	0.05	0.05	0.03
query28	3.53	0.77	0.33
query29	12.47	4.11	3.24
query30	0.29	0.15	0.16
query31	2.78	0.55	0.31
query32	3.22	0.59	0.49
query33	3.15	3.13	3.28
query34	15.47	3.98	3.30
query35	3.27	3.24	3.27
query36	0.56	0.43	0.43
query37	0.08	0.06	0.07
query38	0.05	0.04	0.04
query39	0.03	0.03	0.03
query40	0.17	0.14	0.14
query41	0.10	0.03	0.03
query42	0.04	0.03	0.03
query43	0.04	0.04	0.03
Total cold run time: 96.04 s
Total hot run time: 24.08 s

@hello-stephen

Copy link
Copy Markdown
Contributor

BE UT Coverage Report

Increment line coverage 92.31% (96/104) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 64.09% (29825/46534)
Line Coverage 48.70% (310846/638268)
Region Coverage 44.19% (250628/567110)
Branch Coverage 45.78% (116589/254659)

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 92.31% (96/104) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 76.29% (34377/45059)
Line Coverage 61.21% (386206/630979)
Region Coverage 57.58% (325194/564814)
Branch Coverage 58.44% (148356/253866)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants