Skip to content

[fix](be) Align CSV V2 enclosed field parsing#65501

Open
Gabriel39 wants to merge 4 commits into
apache:masterfrom
Gabriel39:codex/fix-csv-enclose-parsing
Open

[fix](be) Align CSV V2 enclosed field parsing#65501
Gabriel39 wants to merge 4 commits into
apache:masterfrom
Gabriel39:codex/fix-csv-enclose-parsing

Conversation

@Gabriel39

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary: FileScanner V2 used an independent CSV field-splitting state machine that diverged from the line reader for bare quotes, escapes outside enclosed fields, and configurations where escape equals enclose. It also removed double quotes before nullable string conversion, causing quoted null markers to be treated as NULL. This change reuses the line reader's separator positions, preserves quoted-string provenance through null matching, and adjusts separator positions when a UTF-8 BOM is removed.

Release note

Fix CSV FileScanner V2 parsing for enclosed fields and quoted null literals.

Check List (For Author)

  • Test: Unit Test and Regression test
    • CsvV2ReaderTest: 30 tests passed
    • test_local_tvf_csv_enclose_consistency: passed
  • Behavior changed: Yes. CSV V2 now matches the established enclosed-field parser and preserves quoted null literals as strings.
  • Does this need documentation: No

### What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary: FileScanner V2 used an independent CSV field-splitting state machine that diverged from the line reader for bare quotes, escapes outside enclosed fields, and configurations where escape equals enclose. It also removed double quotes before nullable string conversion, causing quoted null markers to be treated as NULL. Reuse the line reader's separator positions, preserve quoted-string provenance through null matching, and adjust separator positions when a UTF-8 BOM is removed.

### Release note

Fix CSV FileScanner V2 parsing for enclosed fields and quoted null literals.

### Check List (For Author)

- Test: Unit Test and Regression test
    - CsvV2ReaderTest: 30 tests passed
    - test_local_tvf_csv_enclose_consistency: passed
- Behavior changed: Yes. CSV V2 now matches the established enclosed-field parser and preserves quoted null literals as strings.
- 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?

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@Gabriel39 Gabriel39 marked this pull request as ready for review July 12, 2026 12:20
@Gabriel39 Gabriel39 requested a review from yiguolei as a code owner July 12, 2026 12:20
@Gabriel39

Copy link
Copy Markdown
Contributor 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 completed for PR 65501.

I found one correctness issue and requested changes inline.

Checkpoint conclusions:

  • Goal/test: the PR improves CSV v2 enclosed-field parsing and adds focused BE/regression coverage, but the BOM-prefixed quoted first-field case is still broken.
  • Scope: the code changes are focused on delimited CSV v2 parsing, BOM offset adjustment, quoted-null handling, and coverage.
  • Concurrency/lifecycle: no shared concurrent state or non-obvious lifecycle issue found; reader contexts are per reader and refreshed per logical row.
  • Config/compatibility/persistence: no new config, storage format, protocol, or persistence change found; old CSV behavior was compared where relevant.
  • Parallel paths: v2 reader, old CSV reader comparison, unit tests, and local TVF regression path were reviewed. The actionable issue is specific to v2 consuming enclosed separator positions computed before BOM stripping.
  • Testing: new tests cover ordinary enclosed separators, matching escape/enclose, NORMAL-state bare quotes/escapes, plain BOM, and quoted nulls, but should add BOM plus quoted first field with comma/newline.
  • Performance/observability: no separate performance or observability concern found.
  • User focus: no additional user-provided focus was supplied.
  • Validation: static review only, per the review prompt; no builds or tests were run.

Subagent process: round 1 normal/risk subagents completed, candidate M1 was accepted, and round 2 convergence subagents returned NO_NEW_VALUABLE_FINDINGS against the final comment set.

Comment thread be/src/format_v2/delimited_text/csv_reader.cpp
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65501

Problem Summary: FileScannerV2 removed a UTF-8 BOM only after the enclosed CSV line reader had already selected the first field state. A headerless record such as BOM plus `"a,b",2` was therefore treated as unquoted, and a newline inside that first quoted field could terminate the logical record early. Let the enclosed state machine structurally skip a first-record BOM before choosing its state while leaving byte removal and separator-offset adjustment to the V2 CSV reader. The option is enabled only for V2 reads starting at file offset zero, so legacy readers and non-first splits retain their existing behavior.

### Release note

Fix FileScannerV2 parsing of BOM-prefixed CSV records whose first enclosed field contains separators or newlines.

### Check List (For Author)

- Test: Unit Test
    - `CsvV2ReaderTest.*` (32 tests)
- Behavior changed: Yes, V2 now recognizes an enclosed first field immediately following a UTF-8 BOM
- Does this need documentation: No
@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

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

I found three blocking issues in this PR.

Checkpoint conclusions:

  • Goal fit: the PR improves CSV v2 enclosed-field consistency and quoted-null handling, but the new separator-position reuse still has correctness gaps in edge cases.
  • Scope/focus: the code changes are mostly focused on CSV v2 parsing and its tests.
  • Concurrency/lifecycle: no shared-thread lifecycle issue found; the relevant lifecycle issue is parser state preserved across one logical line reader refill, covered inline.
  • Compatibility: two parser edge cases can now abort or split incorrectly; one new regression suite can fail in single-BE remote/isolated environments before reaching the parser behavior.
  • Tests: added BE/regression coverage is useful, but it does not cover separator/delimiter overlap, cross-refill multi-character separators, or the single-BE fixture-copy path.
  • Observability/config/persistence/transactions: no new config, persistence, transaction, FE-BE protocol, or observability requirement found for this change.

User focus: no additional user-provided review focus was supplied.

Validation note: this was a static review only, per the review-runner instructions; I did not run builds or tests.

Comment thread be/src/format_v2/delimited_text/csv_reader.cpp
Comment thread be/src/format/file_reader/new_plain_text_line_reader.cpp
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65501

Problem Summary: CSV V2 could record column separators that overlap line delimiter bytes or re-record an accepted multi-character separator after an output-buffer refill, leading to invalid split offsets and BE checks. The local TVF regression suite also skipped fixture copies for single-BE environments. Limit separator searches to the returned logical line, clamp refill backtracking after the last accepted separator, copy fixtures for every backend, and add focused unit coverage.

### Release note

Prevent CSV V2 failures when column separators overlap line delimiters or cross line-reader refill boundaries.

### Check List (For Author)

- Test: Unit Test
    - CsvV2ReaderTest: 34 tests passed
- Behavior changed: Yes. CSV V2 no longer records separator offsets outside the returned logical line or duplicates accepted separators across refills.
- Does this need documentation: No
@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@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 summary for PR #65501 (head 0be70b5):

I found no new distinct inline findings beyond the existing review threads already present on this PR.

Critical checkpoint conclusions:

  • Goal/test coverage: The patch targets CSV v2 enclosed-field parsing by reusing the line-reader state machine for separator positions, BOM adjustment, refill-boundary handling, and quoted null behavior. Added BE unit coverage and a local TVF regression cover the main offset, BOM, delimiter-overlap, refill, matching escape/enclose, and quoted-null cases.
  • Scope: The change is focused on the CSV/text line-reader and v2 delimited reader path, with no unrelated refactor found.
  • Concurrency/lifecycle: No shared concurrent state or locking path is introduced. Parser state is per reader and refreshed per logical record; skipped/header rows do not reuse separator positions.
  • Compatibility/parallel paths: Old CSV still constructs EncloseCsvLineReaderCtx with default no v2 BOM-skip behavior; the shared backtracking/search-bound changes were checked against old CSV splitting. No FE-BE thrift/config/storage-format/persistence changes were introduced.
  • Error handling/data correctness: Offset assertions in _split_line() remain invariant checks; current head bounds separator search before delimiter bytes and adjusts BOM-shifted positions before splitting.
  • Tests: BE tests and the new regression are present and deterministic. The regression copies fixtures to each backend, pins backend_id, enables enable_file_scanner_v2, and uses ordered result checks.
  • Performance/observability: No new logging/metrics needed for this localized parser fix; added separator-position copying is limited to enclosed CSV splitting and no obvious hot-path regression stood out.

User focus: no additional review focus was provided.

Validation: Static review only, as requested by the review prompt. The local checkout is shallow/grafted and the base commit is not present, so an exact git diff --check range could not run; the bundled authoritative patch reverse-applied cleanly with whitespace warnings enabled.

@hello-stephen

Copy link
Copy Markdown
Contributor

BE UT Coverage Report

Increment line coverage 92.86% (65/70) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 56.94% (23560/41376)
Line Coverage 40.60% (230380/567451)
Region Coverage 36.52% (182142/498756)
Branch Coverage 37.58% (81273/216269)

### What problem does this PR solve?

Issue Number: None

Related PR: apache#65501

Problem Summary: Hive text field splitting only inspected the byte immediately before a delimiter, so a delimiter after an even-length escape run was incorrectly treated as escaped in both V1 and V2. Hive OpenCSV planning also enabled double-quote trimming for custom quote characters, removing literal double quotes from fields. Count the complete escape run before delimiters, share the parity rule across both reader paths, and only enable double-quote trimming when the configured OpenCSV quote character is a double quote.

### Release note

Match Hive LazySimpleSerDe escape parity and preserve literal double quotes with custom OpenCSV quote characters.

### Check List (For Author)

- Test: Unit Test
    - HiveTextFieldSplitterTest, TextV2ReaderTest, and custom-enclose CsvV2ReaderTest: 32 tests passed
    - FE checkstyle passed; HiveScanNodeTest compilation was blocked by unrelated missing cloud storage SDK dependencies in the remote validation worktree
- Behavior changed: Yes. Hive text delimiters after even escape runs now split fields, and custom OpenCSV quote characters no longer trim literal double quotes.
- Does this need documentation: No
@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@hello-stephen

Copy link
Copy Markdown
Contributor

BE UT Coverage Report

Increment line coverage 93.83% (76/81) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 56.91% (23548/41377)
Line Coverage 40.57% (230233/567458)
Region Coverage 36.46% (181821/498753)
Branch Coverage 37.56% (81228/216265)

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 0.00% (0/2) 🎉
Increment coverage report
Complete coverage report

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17830	4101	4034	4034
q2	2043	326	202	202
q3	10283	1451	813	813
q4	4687	485	343	343
q5	7514	884	618	618
q6	185	175	140	140
q7	798	848	621	621
q8	9404	1582	1539	1539
q9	6185	4431	4390	4390
q10	6798	1803	1493	1493
q11	518	337	319	319
q12	704	563	448	448
q13	18116	3479	2772	2772
q14	274	269	246	246
q15	q16	792	779	718	718
q17	1040	977	1048	977
q18	7224	5853	5547	5547
q19	1612	1271	1066	1066
q20	748	663	544	544
q21	5965	2636	2385	2385
q22	436	349	298	298
Total cold run time: 103156 ms
Total hot run time: 29513 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4462	4393	4447	4393
q2	314	324	229	229
q3	4592	4938	4404	4404
q4	2058	2149	1372	1372
q5	4473	4369	4355	4355
q6	226	176	127	127
q7	2236	1981	1633	1633
q8	2542	2227	2342	2227
q9	8243	7916	7796	7796
q10	4793	4753	4349	4349
q11	574	422	408	408
q12	755	771	532	532
q13	3313	3662	2945	2945
q14	301	299	280	280
q15	q16	710	760	664	664
q17	1393	1415	1518	1415
q18	7842	7316	7201	7201
q19	1150	1036	1040	1036
q20	2219	2222	1930	1930
q21	5263	4569	4468	4468
q22	547	483	415	415
Total cold run time: 58006 ms
Total hot run time: 52179 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 180709 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 0cd87392d5d5c0a390f8f6cdcdb69bb106cb240a, data reload: false

query5	4306	634	496	496
query6	454	224	199	199
query7	4856	605	348	348
query8	380	189	172	172
query9	8743	4105	4097	4097
query10	451	356	288	288
query11	5931	2352	2212	2212
query12	160	106	107	106
query13	1312	632	478	478
query14	6620	5349	4992	4992
query14_1	4320	4331	4325	4325
query15	217	206	181	181
query16	1034	495	444	444
query17	1166	735	615	615
query18	2742	475	354	354
query19	265	181	145	145
query20	117	110	108	108
query21	245	158	130	130
query22	13759	13659	13552	13552
query23	17505	16564	16193	16193
query23_1	16246	16242	16309	16242
query24	7445	1780	1285	1285
query24_1	1330	1307	1325	1307
query25	528	447	355	355
query26	1331	368	208	208
query27	2488	552	370	370
query28	4394	2020	2014	2014
query29	1070	612	469	469
query30	344	258	228	228
query31	1116	1104	996	996
query32	114	66	59	59
query33	512	317	268	268
query34	1163	1135	640	640
query35	772	792	666	666
query36	1412	1374	1159	1159
query37	156	111	93	93
query38	1877	1714	1658	1658
query39	939	930	898	898
query39_1	902	877	895	877
query40	249	167	141	141
query41	64	66	67	66
query42	96	94	92	92
query43	327	318	289	289
query44	1416	789	779	779
query45	206	196	182	182
query46	1116	1193	718	718
query47	2374	2296	2211	2211
query48	385	396	297	297
query49	592	432	319	319
query50	1025	428	338	338
query51	10860	10710	10755	10710
query52	85	88	83	83
query53	253	292	200	200
query54	319	255	230	230
query55	83	73	69	69
query56	318	305	305	305
query57	1420	1405	1322	1322
query58	283	288	271	271
query59	1586	1705	1499	1499
query60	342	287	269	269
query61	178	180	175	175
query62	692	645	577	577
query63	251	214	214	214
query64	2942	1020	858	858
query65	4925	4710	4805	4710
query66	1753	533	391	391
query67	29630	29493	29512	29493
query68	3193	1472	949	949
query69	414	325	264	264
query70	1086	967	913	913
query71	322	324	309	309
query72	3052	2717	2398	2398
query73	832	762	438	438
query74	5090	4967	4788	4788
query75	2659	2613	2219	2219
query76	2329	1178	779	779
query77	358	383	287	287
query78	12335	12440	11774	11774
query79	1418	1164	741	741
query80	655	558	455	455
query81	473	333	288	288
query82	573	160	131	131
query83	395	317	292	292
query84	289	159	128	128
query85	923	584	503	503
query86	374	297	277	277
query87	1823	1834	1780	1780
query88	3710	2849	2804	2804
query89	444	409	372	372
query90	1979	211	200	200
query91	202	190	163	163
query92	62	60	59	59
query93	1527	1582	1023	1023
query94	537	370	318	318
query95	802	589	479	479
query96	1071	784	351	351
query97	2716	2672	2545	2545
query98	211	211	203	203
query99	1147	1163	1038	1038
Total cold run time: 265965 ms
Total hot run time: 180709 ms

@hello-stephen

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

query1	0.00	0.00	0.00
query2	0.09	0.05	0.05
query3	0.25	0.14	0.13
query4	1.61	0.13	0.14
query5	0.28	0.23	0.22
query6	1.23	1.07	1.08
query7	0.04	0.00	0.00
query8	0.05	0.04	0.03
query9	0.38	0.33	0.32
query10	0.55	0.59	0.54
query11	0.19	0.15	0.14
query12	0.18	0.14	0.15
query13	0.48	0.47	0.47
query14	1.04	1.00	0.99
query15	0.62	0.60	0.59
query16	0.32	0.33	0.31
query17	1.09	1.14	1.11
query18	0.24	0.21	0.22
query19	2.07	2.00	1.92
query20	0.01	0.01	0.02
query21	15.43	0.23	0.13
query22	4.80	0.06	0.05
query23	16.12	0.31	0.12
query24	2.97	0.41	0.33
query25	0.13	0.05	0.04
query26	0.73	0.22	0.16
query27	0.04	0.04	0.04
query28	3.57	0.94	0.52
query29	12.48	4.09	3.21
query30	0.28	0.16	0.15
query31	2.78	0.61	0.32
query32	3.22	0.61	0.48
query33	3.16	3.20	3.28
query34	15.74	4.25	3.54
query35	3.51	3.56	3.52
query36	0.57	0.44	0.41
query37	0.09	0.07	0.07
query38	0.05	0.04	0.04
query39	0.04	0.03	0.03
query40	0.18	0.17	0.15
query41	0.09	0.04	0.03
query42	0.04	0.03	0.03
query43	0.05	0.04	0.03
Total cold run time: 96.79 s
Total hot run time: 24.98 s

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 93.83% (76/81) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 74.74% (30174/40372)
Line Coverage 58.72% (331130/563896)
Region Coverage 55.50% (277837/500649)
Branch Coverage 56.77% (122809/216343)

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 0.00% (0/2) 🎉
Increment coverage report
Complete coverage report

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@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 completed for PR 65501 at head 0cd87392d5d5c0a390f8f6cdcdb69bb106cb240a.

I found no additional distinct issues to report in this round. I treated the existing inline review threads as already-known context and did not duplicate them. The current head appears to address the previously raised hazards around enclosed CSV separator positions, BOM-before-quote handling, output-buffer refill backtracking, and local TVF fixture staging.

Critical checkpoint conclusions:

  • Goal and tests: the change aligns CSV FileScanner V2 enclosed-field splitting with the line-reader state machine, preserves quoted null literals, fixes Hive text even/odd escape handling, and narrows OpenCSV double-quote trimming to the double-quote enclosure case. The PR includes focused BE unit tests, an FE unit test, and a regression suite/output for the main behavior.
  • Scope: the implementation is focused on CSV/text parsing, FE OpenCSV scan attributes, and directly related tests.
  • Concurrency and lifecycle: no new concurrent shared state was introduced. The parser state is per reader; separator positions are cleared on each logical-line refresh and consumed before the next refresh.
  • Compatibility and parallel paths: legacy CSV and Hive text paths were checked for parity. FE/BE thrift behavior remains compatible for default OpenCSV double quotes and custom quote characters.
  • Error handling and data correctness: no ignored status or silent continuation issue was found in the changed control flow. The remaining separator-position assertions are supported by the current line-reader invariants.
  • Test quality: added tests cover bare quotes/escapes, matching escape/enclose, custom enclosure, delimiter overlap, refill, BOM before quoted fields, quoted null literals, and Hive double escapes. I did not run builds or tests because this runner was instructed to perform review only.

User focus: no additional user-provided review focus was present.

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