Skip to content

[fix](be) Match SNII wildcards by UTF-8 code point - #66861

Open
airborne12 wants to merge 1 commit into
apache:masterfrom
airborne12:fix-snii-utf8-wildcard
Open

[fix](be) Match SNII wildcards by UTF-8 code point#66861
airborne12 wants to merge 1 commit into
apache:masterfrom
airborne12:fix-snii-utf8-wildcard

Conversation

@airborne12

Copy link
Copy Markdown
Member

What problem does this PR solve?

Issue Number: None

Related PR: #66052

Problem Summary:

SNII evaluated wildcard question marks byte by byte, so a?b could not match a猫b, while V3 converts ? to a RE2 dot and matches one Unicode code point. The SNII matcher now validates UTF-8 and advances its existing reusable dynamic-programming rows only at code-point boundaries. This preserves the bounded two-buffer scratch design, makes invalid patterns return INVALID_ARGUMENT, and aligns SNII query results with V3 without changing the writer or stored bytes.

Release note

SNII wildcard question marks now match one UTF-8 code point, consistent with V3.

Check List (For Author)

  • Test
    • Regression test
    • Unit Test
    • Manual test (add detailed scripts or steps below)
    • No need to test or manual test. Explain why:
  • Behavior changed:
    • No.
    • Yes. ? now consumes exactly one UTF-8 code point. This is query-only; the storage format and write path are unchanged, so existing SNII indexes are upgrade-compatible.
  • Does this need documentation?
    • No.
    • Yes. (add document PR link here)
  • Release notes must contain the following three elements:
    • Problem Summary
    • User-visible Changes
    • Compatibility (storage format, upgrade, downgrade, rolling upgrade, etc.)

Tests

  • ASAN BE unit tests: SniiWildcardQueryTest.*:SniiPatternQuery.* (22/22 passed)
  • Regression: inverted_index_p0/storage_format/test_storage_format_snii_utf8_wildcard
  • Full BE build: ./build.sh --be -j 192

Reviewer Checklist

  • The title is of the form type(scope): [Issue #xxx] summary
  • The PR has a detailed problem description
  • The PR has a detailed solution description
  • The PR has a detailed test plan
  • The PR has a detailed compatibility description

@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?

@airborne12

Copy link
Copy Markdown
Member Author

/review

@airborne12

Copy link
Copy Markdown
Member Author

run buildall

@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.

Automated review status: converged after two rounds; requesting changes for one data-correctness issue.

The UTF-8 matcher is correct for valid inputs, and the added unit and integration coverage demonstrates SNII/V3 ? parity. The inline finding is a separate compatibility regression: legal raw keyword values containing malformed UTF-8 are silently omitted by nontrivial wildcards.

Critical checkpoints:

  1. Goal and proof: code-point matching for valid UTF-8 is implemented and exercised by matcher, logical-index, and ordered SNII/V3 SEARCH tests; malformed stored-term compatibility is not preserved.
  2. Scope and clarity: the production diff is focused on wildcard matching and invalid-pattern handling; no unrelated production change was found.
  3. Concurrency: the matcher is request-local; existing query-cache and single-flight keys include the raw normalized pattern, query type, and expansion cap, with no new lock or shared-state defect.
  4. Lifecycle and static initialization: the pattern view remains live for synchronous enumeration, scratch storage is request-scoped, and no cross-TU static dependency is added.
  5. Configuration: no configuration item is added or changed.
  6. Compatibility: there is no storage-format or protocol change, but existing and newly written raw keyword indexes can contain legal non-UTF-8 bytes; the inline issue changes their results and can publish/cache an incomplete bitmap.
  7. Parallel paths: standard and Lucene SEARCH parsing, analyzed and non-analyzed PREFIX/WILDCARD routing, SNII and V3 execution, raw and escaped dictionaries, cache hit/miss, null masking, and expansion caps were checked.
  8. Conditions and errors: valid UTF-8 boundary validation makes width jumps safe; invalid patterns fail before enumeration, but invalid stored terms are incorrectly converted to successful non-matches as described inline.
  9. Test coverage: valid 2-, 3-, and 4-byte code points, literals, star/question combinations, malformed matcher inputs, allocation reuse, union ordering, nulls, and end-to-end SNII/V3 results are covered. Add a writer-to-SEARCH regression for the accepted issue.
  10. Test results: the checked-in ordered output matches the suite, and the PR reports 22/22 targeted ASAN unit tests, the named regression, and a full BE build. This review did not run builds or tests because the review prompt forbids them.
  11. Observability: existing timers, cache counters, status propagation, and VLOGs are adequate for valid execution; the accepted silent omission is precisely the missing failure signal.
  12. Transactions and persistence: no transaction, EditLog, visibility-version, MoW, or metadata-persistence behavior is changed.
  13. Data writes and crash behavior: the production writer and on-disk bytes are unchanged; no new write atomicity or cleanup path is introduced.
  14. FE-BE variables and compatibility carriers: no query enum, thrift field, function symbol, or transmitted variable is added.
  15. Performance and other checks: prefix seeking and two reusable DP rows remain intact; validation is linear per visited term and introduces no per-term heap allocation. No second correctness, lifecycle, or performance issue survived verification.

User focus: no additional focus was provided. All candidates were independently verified, deduplicated, accepted, or dismissed; Round 2 returned NO_NEW_VALUABLE_FINDINGS on both full-review tracks and the separate risk track.

: pattern_(pattern), pattern_valid_(is_valid_utf8(pattern)) {}

bool operator()(std::string_view text) {
if (!pattern_valid_ || !is_valid_utf8(text)) {

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.

[P1] Preserve wildcard matches for raw keyword terms

SNII's non-analyzed writer persists raw VARCHAR bytes without UTF-8 validation, so a legal value such as cast(unhex('61FF') as string) creates a term containing bytes 0x61 0xff that reaches this guard. Returning false makes body:a* and body:** silently omit that row, even though the old byte matcher returned it and body:* still returns it through MATCH_ALL_DOCS. This changes results for existing indexes and makes equivalent wildcard forms disagree. Please preserve compatible handling where possible, or propagate a non-OK/bypass result for malformed dictionary terms instead of treating them as clean non-matches; add an end-to-end SNII keyword case covering these three patterns.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Confirmed independently with a real writer-to-reader reproduction. Before the fix, a raw keyword term 61FF produced a* -> {valid-only}, ** -> {valid-only}, and * -> {valid-only} at the SNII query API, omitting the malformed term. Commit bd9a2b5 keeps query patterns strict UTF-8, uses code-point transitions for valid dictionary terms, and falls back to the legacy byte transitions only for malformed stored terms. The same corpus now returns the raw row for a*, **, and * while retaining the UTF-8 ? behavior. Added both BE writer/reader coverage and a real SNII regression case using CAST(UNHEX(61FF) AS STRING); 23 affected ASAN UTs and the generated-then-normal regression suite pass. This is query-only: no writer or on-disk format change, so existing SNII indexes are fixed by upgrade.

@hello-stephen

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

------ Round 1 ----------------------------------
orders	Doris	NULL	NULL	0	0	0	NULL	0	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
============================================
q1	17629	3145	3133	3133
q2	q3	10851	880	506	506
q4	4679	253	206	206
q5	7664	589	403	403
q6	144	123	94	94
q7	530	502	391	391
q8	9247	880	925	880
q9	3476	2392	2387	2387
q10	6514	842	731	731
q11	453	257	248	248
q12	695	405	330	330
q13	17894	1532	1169	1169
q14	157	151	138	138
q15	q16	472	396	365	365
q17	831	811	813	811
q18	3071	2303	2282	2282
q19	1128	915	780	780
q20	720	541	442	442
q21	5326	1866	1884	1866
q22	327	268	230	230
Total cold run time: 91808 ms
Total hot run time: 17392 ms

----- Round 2, with runtime_filter_mode=off -----
orders	Doris	NULL	NULL	150000000	42	6422171781	NULL	22778155	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
============================================
q1	3558	3493	3431	3431
q2	q3	2258	2382	2139	2139
q4	1192	1179	901	901
q5	2222	2150	2143	2143
q6	171	119	90	90
q7	1064	907	888	888
q8	1614	1427	1446	1427
q9	3181	3122	3128	3122
q10	1854	1806	1611	1611
q11	365	279	256	256
q12	454	432	338	338
q13	1512	1552	1178	1178
q14	171	182	162	162
q15	q16	406	396	355	355
q17	1068	1060	1037	1037
q18	4989	4439	4767	4439
q19	852	845	832	832
q20	974	944	806	806
q21	3815	3148	3315	3148
q22	419	352	340	340
Total cold run time: 32139 ms
Total hot run time: 28643 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 81270 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 73af19e377fa35a598a489d06f561a0770153093, data reload: false

query5	4278	415	350	350
query6	431	168	157	157
query7	4820	451	269	269
query8	301	129	123	123
query9	8693	2965	2939	2939
query10	391	258	213	213
query11	5426	1047	928	928
query12	120	72	76	72
query13	1190	460	305	305
query14	6100	2240	2118	2118
query14_1	2016	2007	2008	2007
query15	189	120	112	112
query16	1059	386	363	363
query17	820	469	387	387
query18	2340	326	246	246
query19	174	146	115	115
query20	74	72	75	72
query21	221	117	106	106
query22	5283	5290	5190	5190
query23	6745	6257	5900	5900
query23_1	6093	6112	6192	6112
query24	7299	1095	726	726
query24_1	757	803	775	775
query25	410	281	253	253
query26	1246	281	167	167
query27	2717	446	278	278
query28	4634	1485	1512	1485
query29	920	441	339	339
query30	279	182	152	152
query31	889	427	349	349
query32	96	50	48	48
query33	449	204	168	168
query34	999	839	513	513
query35	399	404	342	342
query36	567	574	542	542
query37	118	80	69	69
query38	1001	852	818	818
query39	517	477	508	477
query39_1	478	458	449	449
query40	220	125	112	112
query41	53	53	51	51
query42	79	82	84	82
query43	246	244	225	225
query44	
query45	108	102	101	101
query46	791	837	554	554
query47	754	785	727	727
query48	320	315	235	235
query49	563	239	185	185
query50	863	344	261	261
query51	8274	8304	8269	8269
query52	73	84	70	70
query53	203	215	157	157
query54	228	175	246	175
query55	79	61	53	53
query56	261	247	217	217
query57	730	664	658	658
query58	242	186	198	186
query59	1231	1233	1128	1128
query60	287	203	195	195
query61	155	129	114	114
query62	390	214	189	189
query63	191	164	158	158
query64	2765	692	626	626
query65	
query66	1919	311	261	261
query67	9983	10277	9976	9976
query68	
query69	397	220	192	192
query70	634	651	638	638
query71	309	271	245	245
query72	2463	1786	1633	1633
query73	680	591	359	359
query74	2022	1290	1155	1155
query75	1286	1167	1059	1059
query76	2358	743	562	562
query77	264	276	223	223
query78	3922	3515	3200	3200
query79	3125	786	614	614
query80	1578	400	342	342
query81	525	195	174	174
query82	628	131	102	102
query83	315	249	235	235
query84	
query85	857	452	380	380
query86	513	174	171	171
query87	1000	981	908	908
query88	3058	2156	2134	2134
query89	318	227	203	203
query90	2102	147	144	144
query91	155	142	134	134
query92	71	48	48	48
query93	2041	1110	813	813
query94	710	268	234	234
query95	639	385	444	385
query96	791	562	254	254
query97	1091	1105	1047	1047
query98	188	146	141	141
query99	427	345	318	318
Total cold run time: 175438 ms
Total hot run time: 81270 ms

### What problem does this PR solve?

Issue Number: None

Related PR: apache#66052

Problem Summary: SNII evaluated wildcard question marks byte by byte, so a pattern such as a?b could not match a猫b although the V3 inverted index treats the question mark as one Unicode character. Switching all terms to strict UTF-8 initially caused a compatibility regression: keyword indexes can contain arbitrary VARCHAR bytes, and already-written malformed terms such as 61FF stopped matching a*, **, and *. The matcher now advances valid terms by UTF-8 code point while falling back to the legacy byte semantics for malformed stored terms. Query patterns remain strict UTF-8 and invalid patterns return INVALID_ARGUMENT. The existing reusable DP scratch remains bounded.

### Release note

SNII wildcard question marks now match one UTF-8 code point for valid text while preserving wildcard matches for existing raw keyword bytes.

### Check List (For Author)

- Test: Unit Test and Regression test
    - ASAN BE unit tests: SniiWildcardQueryTest.* and SniiPatternQuery.* (23 passed)
    - Regression test: test_storage_format_snii_utf8_wildcard (generated output and normal run passed)
    - Full BE build with -j 192
- Behavior changed: Yes. SNII wildcard matching is code-point based for valid UTF-8 and retains legacy byte matching for malformed stored terms; the storage format and write path are unchanged.
- Does this need documentation: No
@airborne12
airborne12 force-pushed the fix-snii-utf8-wildcard branch from 73af19e to bd9a2b5 Compare August 18, 2026 06:56
@airborne12

Copy link
Copy Markdown
Member Author

run buildall

@airborne12

Copy link
Copy Markdown
Member Author

/review

@github-actions

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: All Codex review accounts are usage-limited; earliest retry is 2026-08-20T03:35:00Z.
Workflow run: https://github.com/apache/doris/actions/runs/32204882601

Please trigger /review again after that time.

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