Skip to content

[fix](hive) Match OpenCSVSerde semantics in FileScannerV2 - #68027

Open
Gabriel39 wants to merge 8 commits into
apache:masterfrom
Gabriel39:fix/hive-csv-table-properties
Open

Gabriel39 wants to merge 8 commits into
apache:masterfrom
Gabriel39:fix/hive-csv-table-properties

Conversation

@Gabriel39

@Gabriel39 Gabriel39 commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Hive CSV tables created by Trino can store character settings only in table parameters. Doris used SerDe parameters and a generic CSV parser, producing merged, shifted, or empty data columns even when partition values were correct.

Resolve table-over-SerDe character precedence, first-Java-character normalization, defaults, and Hive's double-quote escape sentinel. Validate the effective character tuple and reject settings that cannot be represented by the existing character fields.

Implement OpenCSV row semantics in FileScannerV2. The parser and physical-record context live under be/src/format_v2/delimited_text/, with tests under be/test/format_v2/delimited_text/. The legacy be/src/format/ directory is unchanged from the PR base. Match OpenCSV 2.3 field transitions, including leading whitespace, embedded and doubled quotes, disabled quote/escape settings, and literal NUL bytes. Preserve Hadoop physical records (LF, CRLF, and CR), completed fields before an unmatched quote, missing-field NULLs, explicit empty strings, and file-start BOM handling. Decode each field once and bound delimiter metadata by the requested column prefix. Neither table nor SerDe line.delim overrides CSV input framing.

Version the contract at both boundaries: connector API 9.0 freezes the public scan-property names, types, and values; OpenCSV scans require query-wide BE execution version 15 or newer and exclude smooth-upgrade source backends. Requests without the optional semantic flag retain generic CSV decoding in FileScannerV2.

The external regression uses Hive-compatible, non-NUL character properties because PostgreSQL-backed Hive metastores cannot store NUL in text metadata. Disabled quote/escape combinations remain covered by the Hive oracle and V2 unit tests; raw file records still include binary NULs.

Release note

Fix incorrect results when FileScannerV2 reads Hive OpenCSV tables with custom character properties and OpenCSV-specific quoting or escaping. Reject unsupported character metadata explicitly. OpenCSV scans require BE execution version 15 or newer; connector plugins must use API 9.0.

Check List (For Author)

  • Test:
    • 480 Hive connector tests passed, including an actual Hive 3.1.3 oracle for 343 character tuples and 606 record cases with deterministic random inputs and binary NULs.
    • 142 connector SPI tests passed, including the API 9.0 pin and regenerated surface baselines. The metadata-method baseline is unchanged.
    • Prior FE Core validation passed 34 tests: 13 CSV wire/upgrade tests, 13 plugin loading/version tests, and eight existing scan compatibility tests. Coverage includes an API 8.0 plugin jar, execution versions 14/15, smooth-upgrade source rejection, and absent-field compatibility. This update does not modify those implementations.
    • 43 BE tests passed under ASAN. FileScannerV2 consumes the Hive-generated corpus across batches, projections, physical line endings, counts, split offsets, and BOMs, including disabled character settings and absent-flag generic decoding.
    • External regression explicitly enables FileScannerV2 and covers partitioned/unpartitioned layouts, table/SerDe properties, raw TEXTFILE records, projections, filters, counts, and metadata-only changes. Groovy compilation passed; the end-to-end external rerun remains pending CI.
    • A separate probe used Hive 3.1.3 SQL literal decoding and a real PostgreSQL 17 instance: the original fixture fails on NUL metadata; all 12 property values in the four revised dialects persist successfully.
    • FE Checkstyle, clang-format 16 on all affected C++ files, header hygiene, License Eye, and whitespace checks passed. Repository clang-tidy remains blocked by the existing unmatched NOLINTEND in core/types.h and existing reader diagnostics. The V2 parser and physical-record helper have no clang-tidy diagnostics.
  • Behavior changed: Yes, Hive CSV property resolution and FileScannerV2 row semantics now match OpenCSVSerde for the supported character settings.
  • Does this need documentation: No.

### What problem does this PR solve?

Hive CSV tables created by Trino can store `separatorChar`, `quoteChar`, and `escapeChar` only in table parameters. The CSV scan-property extractor read only SerDe parameters, so custom CSV files were parsed with default delimiters, producing merged, shifted, or empty columns.

Read CSV settings with the existing table-over-SerDe precedence and default fallback, including deriving quote trimming from the effective quote character.

### Release note

Fix incorrect results when reading Hive CSV tables whose custom CSV settings are stored in table properties.

### Check List (For Author)

- Test:
  - [x] Unit Test: six property tests and one scan-planning test covering both partitioned and unpartitioned tables. Six tests fail before the fix; all 42 targeted tests pass after it.
  - [x] Regression test added: four CSV file layouts, custom `s/q/e` settings, commas, escaped quotes, escape characters, empty strings, full-row reads, aggregates, and filters. Results are compared with the source data queried through Hive.
  - Full Hive connector suite: 469 tests passed; 1,239 tests passed including upstream modules.
  - FE Checkstyle and `git diff --check` passed.
  - Hive end-to-end execution remains pending: no reachable HiveServer2 in the local test configuration.
- Behavior changed:
  - [x] Yes. CSV scans honor table parameters before SerDe parameters instead of silently using defaults.
- Does this need documentation?
  - [x] 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

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 static review of bdf7dc618a6c7bec518f9091302cefaafb5c9904 against 0aea7140de64a7162d0a0d764e3dc1db7f40dbae. Two P1 data-correctness findings are attached inline.

Checkpoint conclusions:

  • Goal and proof: the ordinary ASCII table-only separator/quote/escape path is correctly propagated from HMS through the table handle and scan properties, but the two inline cases keep the change from fully matching the source reader.
  • Focus and scope: no additional focus was supplied; all four changed files and the complete HMS-to-FE-to-thrift-to-BE path were reviewed. The table-level line.delim expansion is outside the stated three-key fix.
  • Concurrency: the change is per-scan immutable-map lookup; no new shared state, race, atomic, or lock-order issue.
  • Lifecycle: no resource ownership, callback, cache retirement, or shutdown-order change.
  • Configuration: no Doris runtime/configuration-reload surface changes; these values are remote HMS metadata.
  • Compatibility and rolling upgrades: no SPI, thrift schema, symbol, or persisted-format change. The existing byte-sized quote/escape fields are the compatibility constraint called out inline.
  • Parallel paths: synchronous/batched and partitioned/unpartitioned planning share the same extraction path; both V1 and V2 BE CSV readers were traced. The existing per-partition SerDe limitation predates this patch.
  • Conditions and error handling: null/absent maps, table-only, SerDe-only, conflicts, per-key fallback, and ASCII defaults work. Empty, multi-character, and non-ASCII character cases need the validation/normalization described inline.
  • Test coverage: the unit tests cover ordinary precedence and defaults, and the dynamic Hive suite is a coherent ASCII oracle for real partitioned and unpartitioned tables. It lacks negative record-delimiter and post-thrift character-contract coverage.
  • Test outputs: the cross-engine JDBC results have stable ordering/cardinality by static inspection; no handwritten result file is involved.
  • Observability: no new metrics or logging are needed once unsupported metadata fails loudly.
  • Transactions and persistence: no Doris transaction, EditLog, replay, or persisted-state behavior changes.
  • Data writes: production code changes only external-table scan planning; write atomicity and cleanup are not implicated.
  • FE-to-BE propagation: the existing string delimiter and i8 quote/escape paths were checked in both readers; the latter is the second finding.
  • Performance: constant-time map lookups once per scan-node property construction; no material CPU, memory, I/O, or RPC regression found.
  • Other issues: no additional distinct finding remained after two review rounds and duplicate fencing.

Verification status: static review only. Per the review contract, I did not run builds or tests. Checkstyle and lightweight CI checks are passing; FE UT, compile, and performance were still pending at submission time. The author reports connector/unit suites passing and explicitly says the Hive end-to-end run is pending; those claims were not independently executed here.

String quoteChar = getParamOrDefault(params, QUOTE_CHAR, "\"");
getParamOrDefault(params, tableParams, SEPARATOR_CHAR, ","));
result.put(ScanNodePropertyKeys.TEXT_LINE_DELIMITER,
getParamOrDefault(params, tableParams, LINE_DELIM, DEFAULT_LINE_DELIM));

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] Keep table-level line.delim from changing record boundaries

The stated Trino layout has only separator/quote/escape in table parameters. Hive OpenCSVSerde never reads line.delim, and Hadoop TextInputFormat's actual record-delimiter setting is textinputformat.record.delimiter; however, this now forwards any unrelated table line.delim to BE, where both CSV readers split records on it. A newline-delimited table carrying TBLPROPERTIES ('line.delim'='|') will therefore return different rows in Doris than Hive. Please keep this lookup on the previous SerDe-only path (or the newline default), apply table precedence only to the three supported CSV character keys, and add a negative record-boundary case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 9c7e82e. Table-over-SerDe lookup is now limited to separatorChar, quoteChar, and escapeChar. CSV line.delim retains its previous SerDe-only override and newline default.

Added property tests for ignored table line.delim values and preserved SerDe overrides, plus assertions after real scan-node construction and a Thrift round trip. The Hive regression suite now places pipes in payloads while setting table line.delim to |, across all four partitioned/unpartitioned layouts. Local unit/wire tests pass; Hive end-to-end execution remains pending because HiveServer2 is unavailable.

getParamOrDefault(params, SEPARATOR_CHAR, ","));
result.put(ScanNodePropertyKeys.TEXT_LINE_DELIMITER, getLineDelimiter(params));
String quoteChar = getParamOrDefault(params, QUOTE_CHAR, "\"");
getParamOrDefault(params, tableParams, SEPARATOR_CHAR, ","));

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] Normalize table CSV values before forwarding them

Hive OpenCSVSerde consumes the first Java character of each of these three properties and fails on an empty value, but this new table-level path forwards the raw strings: separatorChar='||' becomes a two-byte BE delimiter, empty quote/escape values are silently omitted downstream, and a one-character non-ASCII quote/escape such as é is reduced by getBytes()[0] to one byte in thrift i8. Those cases therefore still return wrong rows or silently differ from the source reader. Please normalize non-empty values to the first character, reject empty values, and either reject non-byte-representable quote/escape characters or extend the FE/BE contract; add coverage after thrift construction rather than only at the property-map layer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 9c7e82e with a shared CSV-character resolver. It resolves precedence first, takes the first Java character as OpenCSVSerde does, rejects empty values and leading surrogate code units, and rejects non-ASCII quote/escape characters before the existing i8 fields can truncate them. Valid UTF-8 separators remain strings. Quote trimming is derived from the normalized character.

Replaced the incorrect empty-means-disabled test and added boundary tests for both property sources, numeric-looking values, precedence over invalid lower-priority values, and Unicode. Five FE Core tests call the real scan-node attribute builder and assert the deserialized Thrift payload. The Hive regression suite adds metadata-only multi-character changes and unsupported quote/escape errors. Empty metadata is tested at the unit boundary because Hive itself rejects it while validating ALTER TABLE.

Validation: 474 Hive connector tests passed (1,244 with upstream modules); all five FE Core wire tests and FE Checkstyle passed. The six new property-boundary tests failed before this follow-up fix. The Hive regression suite compiles, but its end-to-end assertions could not run without HiveServer2.

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage `` 🎉
Increment coverage report
Complete coverage report

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

Static review of exact head 9c7e82edec422fc6e438a139569a707ff3447c32. Requesting changes for two distinct Hive/OpenCSV compatibility defects: the explicit default-escape sentinel is sent to the BE with the wrong effective value, and the resolved separator/quote/escape tuple is not checked against OpenCSV's joint invariants. The two earlier inline threads at this head were treated as hard duplicate fences and are not repeated.

Critical checkpoint conclusions:

  • Goal and scope: the focused five-file change correctly adds table-over-SerDe lookup, preserves the SerDe-only record delimiter, and covers the normal scan/wire path, but the goal is incomplete for the two accepted boundaries.
  • Concurrency, lifecycle, and configuration: no shared mutable state, threads, locks, lifecycle ownership, static initialization, or dynamic configuration behavior is added.
  • Error handling and conditions: planning exceptions propagate normally and the individual empty/representation checks are clear; the missing sentinel mapping and tuple validation are the blocking condition gaps.
  • Compatibility, parallel paths, and FE-to-BE transport: the existing scan-property/Thrift fields are used by the common synchronous/batch planning path and both BE CSV readers. UTF-8 separators and ASCII quote/escape transport are otherwise sound, but both readers receive the two incorrect configurations described inline.
  • Tests and results: connector, FE-core wire, and Hive regression coverage is structurally strong for precedence, normalization, partition layouts, and the prior fixes, but does not cover either accepted defect. Per the review contract, no build or test was run here; the PR also reports that the Hive end-to-end assertions were not executed because HiveServer2 was unavailable.
  • Persistence, transactions, writes, and schema: not applicable to this scan-only change. The unsupported OpenCSV write path predates the PR and is not newly regressed.
  • Performance, memory/nullability, and observability: work is constant per scan-property construction; no memory ownership, BE nullable, or new operational-observability concern was introduced.

User focus: no additional focus was provided. Review status: complete static review with two accepted inline findings.

// BE's extra double-quote trimming is valid only for the effective double-quote enclosure.
result.put(ScanNodePropertyKeys.TEXT_TRIM_DOUBLE_QUOTES, String.valueOf("\"".equals(quoteChar)));
result.put(ScanNodePropertyKeys.TEXT_ESCAPE, getCsvCharacter(params, tableParams, ESCAPE_CHAR, '\\'));
result.put(ScanNodePropertyKeys.TEXT_NULL_FORMAT, "");

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 Hive's explicit default-escape sentinel

Hive does not pass an explicit raw escapeChar='"' through to OpenCSV. OpenCSVSerde.newReader recognizes that writer-default value and selects the reader constructor whose parser escape is backslash. Here it reaches TEXT_ESCAPE as a double quote, so both BE readers use quote as the escape character; a valid file with a backslash-escaped embedded quote can therefore return a different value from Hive. Please map this raw sentinel to the reader-effective backslash before emitting the scan property, and cover the explicit table/SerDe value through the Thrift and Hive-read paths. This is separate from the earlier first-character/byte-representation thread.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 81d106e. The resolver now uses Hive's writer-default escape and maps a normalized double quote to the reader-effective backslash before validation and scan-property emission. Table/SerDe precedence still applies before interpreting the sentinel.

Added property tests for both sources, multi-character sentinel values, and precedence in both directions; FE tests verify the backslash after a real scan-node/Thrift round trip. A test using Hive 3.1.3's actual OpenCSVSerde checks backslash-escaped embedded quotes with both standard and custom quote characters. The Hive regression suite writes with an explicit backslash, switches only the metadata to the sentinel in each property source, and compares source, Hive CSV, and Doris results.

Validation: all 479 Hive connector tests (1,249 including upstream modules) and eight FE Core wire tests pass. The new property and Hive-reader tests reproduce the pre-fix failures. The HiveServer2 end-to-end suite compiles but remains pending because the local service is unavailable.

result.put(ScanNodePropertyKeys.TEXT_LINE_DELIMITER, getLineDelimiter(params));
String quoteChar = getParamOrDefault(params, QUOTE_CHAR, "\"");
getCsvCharacter(params, tableParams, SEPARATOR_CHAR, ','));
// OpenCSVSerde does not use table-level line.delim to frame records. Preserve the existing

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.

[P2] Validate the resolved characters as one OpenCSV configuration

Hive's OpenCSV parser rejects a NUL separator and any equal non-NUL pair among separator, quote, and escape, but these values are validated independently here. For example, table-level separatorChar='|' plus quoteChar='|' now reaches the BE and is parsed ambiguously even though Hive rejects the same table while constructing its reader. Please validate the three parser-effective characters together after applying the default-escape sentinel, and add conflict/NUL cases while keeping a raw double-quote quote/escape pair valid. This is distinct from the prior empty/non-ASCII validation thread.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 81d106e. Validation now operates on the complete parser-effective tuple after first-character normalization, property precedence, and default-escape sentinel conversion. A NUL separator or any equal active pair fails during planning. NUL quote/escape remain valid disabled settings, and a raw double-quote quote/escape pair stays valid because the reader-effective escape is backslash.

Added pairwise conflict tests from table, SerDe, and mixed sources, normalization-induced conflicts, sentinel-induced conflicts, and NUL acceptance/rejection tests. The new compatibility test uses Hive 3.1.3's actual reader as the oracle for 343 tuples across those three property sources. FE tests cover rejection before Thrift construction and zero-valued quote/escape bytes after serialization. The Hive regression suite also includes all three pairwise conflicts and a sentinel-induced conflict.

All 479 Hive connector tests (1,249 with upstream modules), including the actual Hive-reader comparisons, and all eight FE Core wire tests pass. End-to-end HiveServer2 execution remains pending locally.

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17554	3034	3001	3001
q2	2105	250	229	229
q3	10215	863	521	521
q4	4666	243	201	201
q5	7683	556	385	385
q6	136	115	90	90
q7	522	482	374	374
q8	9239	832	912	832
q9	3490	2421	2418	2418
q10	6495	840	732	732
q11	397	198	176	176
q12	616	267	194	194
q13	18152	1539	1131	1131
q14	159	144	138	138
q15	q16	425	398	363	363
q17	1330	900	795	795
q18	3032	2247	2247	2247
q19	1096	925	791	791
q20	385	284	205	205
q21	5299	1778	1813	1778
q22	333	265	224	224
Total cold run time: 93329 ms
Total hot run time: 16825 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	3412	3339	3352	3339
q2	504	400	361	361
q3	2237	2336	2176	2176
q4	1189	1169	902	902
q5	2188	2109	2138	2109
q6	169	121	90	90
q7	1027	915	874	874
q8	1591	1421	1401	1401
q9	3142	3120	3111	3111
q10	1847	1768	1608	1608
q11	365	271	246	246
q12	449	432	338	338
q13	1473	1525	1156	1156
q14	179	169	161	161
q15	q16	395	398	353	353
q17	3592	3387	3264	3264
q18	4811	4410	4749	4410
q19	882	804	907	804
q20	1018	975	822	822
q21	3893	3159	3337	3159
q22	400	354	320	320
Total cold run time: 34763 ms
Total hot run time: 31004 ms

### What problem does this PR solve?

Related PR: apache#68027

Problem Summary: An explicit double-quote escape property is Hive's writer-default sentinel, but its reader uses backslash for that value. Passing the raw sentinel to the BE corrupts escaped fields. Resolve the sentinel before emitting scan properties and reject NUL separators or duplicate active parser characters, while allowing disabled NUL quote/escape characters.

### Release note

Honor Hive's explicit default CSV escape and reject invalid CSV character combinations before scanning.

### Check List (For Author)

- Test: 479 Hive connector tests (1,249 including upstream modules), eight FE Core Thrift tests, real Hive reader comparisons for 343 character tuples with three property sources, FE Checkstyle, and Groovy compilation. HiveServer2 end-to-end execution remains pending.
- Behavior changed: Yes. Match Hive's reader-effective escape and character constraints.
- 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.

Static review of 81d106e4a9a0d53fc170203da13f0194831510db; no builds or tests were run, as required by the review task.

Requesting changes for three P2 Hive/OpenCSV row-semantics mismatches that affect both scanner implementations: leading-whitespace quote recognition, disabled-quote start-of-field escaping, and doubled enclosures when escape is disabled.

Checkpoint summary: the change is focused, and table/SerDe precedence, first-character normalization, the escape sentinel, effective-tuple validation, table-wide partition propagation, Thrift transport, test-only dependency packaging, and Surefire composition are otherwise coherent. No new concurrency, lifecycle, runtime-configuration, transaction/persistence, write-atomicity, observability, storage-format, FE/BE schema, or material performance risk was found. Tests cover property resolution, tuple acceptance, and wire transport broadly, but they do not execute these row states or deterministically cover both V1 and V2. There was no additional user-provided review focus.

// Trino stores CSV settings in table parameters. Honor Hive's table-over-SerDe precedence
// so valid CSV files are not silently split with the default delimiter and quote characters.
String separator = getCsvCharacter(params, tableParams, SEPARATOR_CHAR, ',');
String quoteChar = getCsvCharacter(params, tableParams, QUOTE_CHAR, '"');

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.

[P2] Match OpenCSV's leading-whitespace quote handling

Forwarding the table-level quote still does not make these rows Hive-compatible because OpenCSV defaults ignoreLeadingWhiteSpace to true, while the shared Doris line reader recognizes an enclosure only as the literal first byte of a field. With separator |, quote q, escape e, and record x| qa|bq|c, Hive/OpenCSV discards the two spaces, enters quote state at q, and returns [x, a|b, c]. Both Doris readers see the first space, enter NORMAL state, and split on the | inside qa|bq, producing four fields. Please implement the OpenCSV leading-whitespace quote transition for this Hive CSV path and add a row-level oracle case under both scanner settings.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in a101477. Both scanners now select a shared Hive OpenCSV parser with the full field-state transitions, including leading Unicode whitespace and embedded quotes. The 606-record corpus is generated by the actual Hive 3.1.3 SerDe and exercised through both BE readers.

// so valid CSV files are not silently split with the default delimiter and quote characters.
String separator = getCsvCharacter(params, tableParams, SEPARATOR_CHAR, ',');
String quoteChar = getCsvCharacter(params, tableParams, QUOTE_CHAR, '"');
String escapeChar = getCsvCharacter(params, tableParams, ESCAPE_CHAR, '"');

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.

[P2] Preserve quote doubling when escape is disabled

This accepts an active quote with escapeChar=NUL, but OpenCSV's doubled-quote rule is independent of the configured escape while both Doris value decoders disable all CSV unescaping when the escape byte is zero. With separator ,, quote q, escape NUL, and record qaqqbq,tail, Hive/OpenCSV collapses the middle qq and returns [aqb, tail]. Both Doris readers frame and trim the outer quotes correctly, then skip escape_string_for_csv, returning [aqqb, tail]. The tuple oracle only checks empty-record acceptance, so it misses the wrong value. Please preserve doubled-enclosure decoding with a NUL escape in both readers (or reject this unsupported tuple) and add a row-level oracle case under both scanner settings.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in a101477. Doubled quotes are decoded independently of whether the escape character is NUL. Fields are decoded during OpenCSV parsing and materialized without a second generic CSV unescape pass. Disabled-escape cases are covered by the Hive-generated corpus in both scanners.

if ("\0".equals(separator)) {
throw new DorisConnectorException("Invalid OpenCSVSerde property 'separatorChar': must not be NUL");
}
// OpenCSV requires distinct active characters; NUL disables quote/escape and may be shared by both.

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.

[P2] Cover disabled-quote rows, not only tuple acceptance

This accepts quoteChar=NUL with an active escape, but the BE unescape semantics do not match OpenCSV at the start of an unquoted field. For example, with separator ,, quote NUL, and escape e, Hive/OpenCSV reads eeabc,tail as abc,tail: its parser starts with inField=false, so both leading escapes are discarded. Both Doris readers instead use plain field splitting and escape_string_for_csv, which collapses ee to one e, returning eabc. Hive's writer itself emits eeabc for a value beginning with e under this tuple, so this is reachable without a malformed file. The 343-tuple oracle only compares acceptance and therefore misses the wrong row. Please align both BE readers with the OpenCSV field-state behavior (or reject tuples Doris cannot read equivalently) and add a row-level oracle case. This is distinct from the earlier validation thread: the tuple is accepted by both sides, but its data is decoded differently.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in a101477. The Hive parser now follows OpenCSV's inField/inQuotes escape rules even when quoting is disabled, including removal of leading escapes. Binary and disabled-character records are compared with the actual Hive SerDe and tested through both BE readers.

@github-actions

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 19th, 2026 8:24 AM.
Workflow run: https://github.com/apache/doris/actions/runs/35046805840

The selected account is excluded until 2026-09-19T08:24:00Z. Please trigger /review again; another configured account may be available.

### What problem does this PR solve?

Related PR: apache#68027

Problem Summary: The CSV metadata regression set line.delim before INSERT. Hive's text output format honors this property when writing records, so the fixture contained pipe-terminated records even though the test expected newline-delimited input. Set the property only after writing the files, and verify Hive can still read the original source rows before checking Doris.

### Release note

None

### Check List (For Author)

- Test: Reproduced the record-boundary difference with the actual Hive 3.1.3 text output format; Groovy suite compilation and git diff --check passed. Full external regression rerun pending.
- Behavior changed: No production behavior change; correct regression data preparation.
- Does this need documentation: No.
@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@Gabriel39

Copy link
Copy Markdown
Contributor Author

Fixed the external regression failure in 630c1e7.

The CSV fixture set table-level line.delim before INSERT. Hive's text output format uses that property when writing record terminators, so the test generated pipe-delimited records while expecting newline-delimited input. The suite now sets the property only after writing the files and checks that Hive CSV reads still match the source rows before checking Doris.

Validation: a local probe using Hive 3.1.3's actual text output format reproduced first|second| with the old order and newline-separated records with the corrected order. Groovy suite compilation and git diff --check passed. The corrected end-to-end suite still needs the external CI rerun. No production code changes were needed for this fixture failure.

@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-only review of exact head 630c1e7714e3335996be605866ec02c8342961fb against base 0aea7140de64a7162d0a0d764e3dc1db7f40dbae. I found three additional P2 Hive/OpenCSV compatibility failures and left them inline. I did not repeat the existing current-head threads for leading whitespace before quotes, doubled quotes with disabled escape, or active escapes with disabled quote; those cases also remain applicable.

Critical-checkpoint conclusions:

  • Goal and proof: The change correctly resolves table-over-SerDe character properties, first-character/default-escape normalization, tuple validation, and the existing FE-to-BE transport. It does not yet accomplish end-to-end Hive row compatibility: the three new counterexamples plus the three existing current-head threads produce different values, field counts, or record counts.
  • Scope/focus: Production scope is localized to HiveTextProperties; the POM and remaining files support tests. No unrelated SPI, engine, or catalog surface changed.
  • Concurrency: The resolver is stateless planning code with method-local values and immutable handle data. It adds no thread, shared mutable state, lock, or deadlock risk.
  • Lifecycle: Metadata follows the existing handle/cache refresh lifecycle and survives handle rebuilding. No resource owner, close path, reference cycle, or static-initialization dependency was added.
  • Configuration: No Doris configuration item or dynamic-update contract was introduced.
  • Compatibility/rolling upgrade: No Thrift schema, connector SPI, symbol, or persisted format changed; existing optional string/i8 fields preserve mixed-version wire compatibility. The blockers are semantic compatibility with Hive/OpenCSV, not serialization compatibility.
  • Parallel paths: Partitioned/unpartitioned and batch/non-batch planning share the resolved properties. Legacy CSV and FileScannerV2 both consume the same tuple and share the failing framing/decoding mechanisms, so neither is an unaffected fallback.
  • Conditions/errors: Empty, surrogate, non-ASCII i8, NUL-separator, and active-character conflicts now fail loudly after effective precedence and sentinel conversion. The remaining failures are downstream parser-state mismatches, not silent FE fallback.
  • Tests/results: The property, provider, and Thrift tests are focused and their assertions are correct for resolution/transport. The 343-tuple oracle checks only empty-record acceptance, the regression covers one ordinary active tuple without deterministically running both scanner modes, and the reported HiveServer2 end-to-end run remains pending. Value/record differential tests are required for all concrete failures. No build or test was run during this review, as required by the review environment.
  • Observability: No new distributed or long-lived operation needs logs or metrics; actionable planning exceptions and existing scan profiles are sufficient.
  • Persistence/transactions/data writes: Doris persistence, EditLog, transaction, and production write paths are untouched. Hive writes are regression-fixture setup only.
  • FE-to-BE variables: The change reuses existing fields, and I traced table metadata through HiveTableHandle, HiveScanPlanProvider, PluginDrivenScanNode, Thrift, and both readers. Transport is sound, including explicit zero bytes and UTF-8 separators.
  • Performance: Resolution is bounded map lookup and constant-time validation per scan-property build, with no material row-path, memory, or I/O cost.
  • Other/user focus: No separate user focus was supplied. Packaging remains test-only, and no additional security, nullability, atomicity, or compatibility issue survived the final sweep.

The review completed two bounded rounds; every second-round reviewer returned NO_NEW_VALUABLE_FINDINGS, all candidates were adjudicated, and the live head/base and duplicate fence were refreshed immediately before submission.

// Trino stores CSV settings in table parameters. Honor Hive's table-over-SerDe precedence
// so valid CSV files are not silently split with the default delimiter and quote characters.
String separator = getCsvCharacter(params, tableParams, SEPARATOR_CHAR, ',');
String quoteChar = getCsvCharacter(params, tableParams, QUOTE_CHAR, '"');

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.

[P2] Preserve Hive's physical record boundaries for custom quotes

With table-only separatorChar='|', quoteChar='q', and escapeChar='e', consider file bytes qleft\nrightq|tail\n. Hive's TextInputFormat gives the SerDe each physical line separately; OpenCSV reaches EOF with an unmatched pending quote on each call, so Hive exposes two null-valued rows. Once this assignment forwards q, both Doris scanner modes use EncloseCsvLineReaderCtx, which reads past the first newline until the second-line q and instead returns one row ['left\nright', 'tail']. This is separate from the resolved line.delim issue because the delimiter stays newline; activating the quote changes record ownership. Please preserve Hive's physical-line boundary (or reject this unsupported tuple) and cover the case with both scanner settings.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in a101477. Hive CSV uses Hadoop-style physical LF/CRLF/CR records instead of quote-dependent line framing. An unmatched quote drops the pending field while preserving completed fields from that record. Both readers are tested for physical boundaries, counts, projections, split offsets, and empty records; the external regression explicitly selects both scanner modes.

// Trino stores CSV settings in table parameters. Honor Hive's table-over-SerDe precedence
// so valid CSV files are not silently split with the default delimiter and quote characters.
String separator = getCsvCharacter(params, tableParams, SEPARATOR_CHAR, ',');
String quoteChar = getCsvCharacter(params, tableParams, QUOTE_CHAR, '"');

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.

[P2] Match OpenCSV's embedded-quote state transition

This still diverges after a non-whitespace prefix, independently of the existing leading-whitespace thread. With table-only separatorChar='|', quoteChar='q', escapeChar='e', the row abcqleft|rightq|tail is parsed by OpenCSV as ['abcqleft|right', 'tail', NULL]: the first q is retained but also enters quoted state, so the enclosed separator is data. Doris sees the initial a, moves permanently to NORMAL, and both scanner modes split both separators into ['abcqleft', 'rightq', 'tail']. A whitespace-only correction will not cover this branch; the Hive CSV framing path needs OpenCSV's full field quote transitions, with row-level tests under both scanners.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in a101477. The shared Hive parser implements the complete embedded-quote transition, including the whole-record UTF-16 position rule and retention of the embedded quote when appropriate. It also applies OpenCSV escape semantics to the resulting field. Coverage uses actual Hive-generated row expectations and both real reader implementations, rather than only character-tuple acceptance.

// so valid CSV files are not silently split with the default delimiter and quote characters.
String separator = getCsvCharacter(params, tableParams, SEPARATOR_CHAR, ',');
String quoteChar = getCsvCharacter(params, tableParams, QUOTE_CHAR, '"');
String escapeChar = getCsvCharacter(params, tableParams, ESCAPE_CHAR, '"');

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.

[P2] Handle literal NUL consistently when quote and escape are disabled

The accepted tuple quoteChar=NUL, escapeChar=NUL disagrees on writer-reachable data. For a\0b,tail, OpenCSV 2.3 still takes its c == escape branch for the literal NUL and drops it, returning ['ab', 'tail']; its writer emits that NUL unchanged when both features are disabled. Doris selects plain splitting and skips CSV unescaping when both fields are zero, so both scanner modes preserve ['a\0b', 'tail']. This is distinct from the existing one-feature-disabled threads. Please either reproduce OpenCSV's literal-NUL behavior or reject the tuple as unsupported, and add a binary row-level oracle.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in a101477. NUL participates in OpenCSV's escape-first branch even when both quote and escape are disabled. The shared corpus stores binary inputs and expected fields as hex, distinguishing NULL from empty strings. Both scanners consume that corpus, and the regression creates raw TEXTFILE fixtures containing literal NUL bytes.

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17563	3041	3017	3017
q2	2092	250	222	222
q3	10233	991	523	523
q4	4674	255	214	214
q5	7667	579	382	382
q6	140	117	97	97
q7	557	528	392	392
q8	9244	895	914	895
q9	3531	2447	2444	2444
q10	6522	880	738	738
q11	392	201	183	183
q12	614	271	203	203
q13	18119	1566	1179	1179
q14	169	156	143	143
q15	q16	441	403	379	379
q17	1338	925	819	819
q18	3172	2321	2353	2321
q19	1288	961	806	806
q20	394	296	205	205
q21	5634	1817	1944	1817
q22	341	267	237	237
Total cold run time: 94125 ms
Total hot run time: 17216 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	3366	3300	3290	3290
q2	525	402	379	379
q3	2282	2373	2253	2253
q4	1251	1226	910	910
q5	2286	2229	2179	2179
q6	174	125	90	90
q7	1061	961	910	910
q8	1623	1432	1426	1426
q9	3267	3247	3240	3240
q10	1973	1902	1708	1708
q11	367	282	261	261
q12	474	448	351	351
q13	1518	1579	1180	1180
q14	181	185	176	176
q15	q16	405	400	364	364
q17	3655	3396	3382	3382
q18	5011	4596	5117	4596
q19	951	885	859	859
q20	1044	1017	872	872
q21	3921	3202	3299	3202
q22	397	352	343	343
Total cold run time: 35732 ms
Total hot run time: 31971 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 83099 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 630c1e7714e3335996be605866ec02c8342961fb, data reload: false

query5	4236	416	346	346
query6	385	134	124	124
query7	4937	429	236	236
query8	288	131	114	114
query9	8722	2914	2921	2914
query10	410	228	188	188
query11	5401	1056	943	943
query12	125	77	72	72
query13	1208	421	322	322
query14	6127	2285	2152	2152
query14_1	2049	2035	2040	2035
query15	175	121	105	105
query16	940	356	347	347
query17	777	426	350	350
query18	2336	319	229	229
query19	169	143	105	105
query20	70	68	67	67
query21	194	99	87	87
query22	5813	5479	5406	5406
query23	7090	6474	6247	6247
query23_1	6224	6092	6333	6092
query24	7295	1104	765	765
query24_1	777	766	785	766
query25	404	282	225	225
query26	1219	246	125	125
query27	2776	444	254	254
query28	4663	1513	1503	1503
query29	905	419	340	340
query30	257	156	130	130
query31	826	409	347	347
query32	131	81	75	75
query33	514	218	184	184
query34	984	831	481	481
query35	412	407	360	360
query36	584	571	503	503
query37	120	82	69	69
query38	1016	884	849	849
query39	498	499	483	483
query39_1	489	466	464	464
query40	204	93	79	79
query41	61	58	59	58
query42	82	74	75	74
query43	244	248	219	219
query44	1008	538	544	538
query45	115	112	104	104
query46	760	865	543	543
query47	770	784	732	732
query48	315	310	222	222
query49	544	245	213	213
query50	756	273	200	200
query51	7980	8061	7910	7910
query52	70	74	64	64
query53	196	206	152	152
query54	230	189	229	189
query55	79	61	57	57
query56	198	187	175	175
query57	723	703	677	677
query58	203	166	169	166
query59	1278	1281	1157	1157
query60	245	178	167	167
query61	123	117	109	109
query62	375	206	183	183
query63	174	143	137	137
query64	2675	700	564	564
query65	1680	1735	1644	1644
query66	1798	254	193	193
query67	9809	9910	10026	9910
query68	2867	1227	734	734
query69	335	225	197	197
query70	694	615	631	615
query71	252	175	166	166
query72	2255	1719	1509	1509
query73	636	567	320	320
query74	1984	1255	1175	1175
query75	1227	1105	977	977
query76	2301	724	537	537
query77	263	256	214	214
query78	4232	3818	3370	3370
query79	2144	803	589	589
query80	1564	325	261	261
query81	494	161	137	137
query82	617	120	95	95
query83	277	216	185	185
query84	293	105	90	90
query85	801	344	282	282
query86	377	163	191	163
query87	1044	987	927	927
query88	2799	2113	2117	2113
query89	289	197	172	172
query90	1977	130	128	128
query91	129	118	94	94
query92	74	70	67	67
query93	1344	1089	713	713
query94	651	254	220	220
query95	508	249	222	222
query96	813	592	286	286
query97	1107	1060	1023	1023
query98	145	134	138	134
query99	428	357	313	313
Total cold run time: 178602 ms
Total hot run time: 83099 ms

@hello-stephen

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

query1	0.00	0.01	0.00
query2	0.08	0.04	0.03
query3	0.26	0.11	0.11
query4	1.60	0.10	0.10
query5	0.17	0.17	0.15
query6	1.26	0.72	0.70
query7	0.03	0.00	0.01
query8	0.04	0.03	0.03
query9	0.29	0.21	0.22
query10	0.34	0.35	0.35
query11	0.15	0.11	0.11
query12	0.14	0.12	0.12
query13	0.32	0.32	0.31
query14	0.47	0.47	0.48
query15	0.37	0.36	0.35
query16	0.21	0.23	0.25
query17	0.67	0.76	0.71
query18	0.19	0.17	0.17
query19	1.16	1.18	1.21
query20	0.02	0.01	0.01
query21	15.50	0.15	0.11
query22	5.08	0.04	0.04
query23	16.18	0.26	0.10
query24	2.92	0.30	0.24
query25	0.10	0.04	0.04
query26	0.76	0.16	0.11
query27	0.04	0.03	0.02
query28	3.64	0.55	0.29
query29	12.44	3.23	2.56
query30	0.25	0.12	0.12
query31	2.76	0.39	0.18
query32	3.50	0.33	0.24
query33	1.62	1.60	1.53
query34	15.40	2.29	1.85
query35	1.83	1.75	1.77
query36	0.47	0.29	0.29
query37	0.07	0.04	0.04
query38	0.05	0.03	0.03
query39	0.03	0.03	0.02
query40	0.12	0.08	0.08
query41	0.08	0.03	0.02
query42	0.03	0.03	0.02
query43	0.04	0.03	0.03
Total cold run time: 90.68 s
Total hot run time: 15.05 s

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

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

### What problem does this PR solve?

Problem Summary: Hive CSV character properties alone do not select OpenCSV
field states. Generic parsing can shift embedded-quote fields, merge physical
records, and misdecode disabled escape/quote settings. Add an explicit Hive
CSV scan attribute and a shared parser for both readers, with Hive-generated
record expectations and deterministic scanner coverage.

### Release note

Fix Hive OpenCSV row decoding, physical record boundaries, and missing fields.

### Check List (For Author)

- Test: 480 Hive connector tests, 9 FE Core tests, 44 BE tests under ASAN;
  parser corpus under ASAN/UBSAN; Groovy compilation, FE Checkstyle,
  clang-format 16, and header hygiene. External regression pending CI.
- Behavior changed: Yes, supported Hive CSV scans use OpenCSVSerde semantics.
- Does this need documentation: No.
@Gabriel39
Gabriel39 requested a review from yiguolei as a code owner September 16, 2026 09:56
@Gabriel39 Gabriel39 changed the title [fix](fe) Honor table-level Hive CSV properties [fix](hive) Match OpenCSVSerde property and row semantics Sep 16, 2026
@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

### What problem does this PR solve?

Related PR: apache#68027

Problem Summary: License Eye cannot infer a comment style for the generated TSV
oracle. The FE test compares every line with regenerated Hive output and the BE
tests parse every line as a record. Follow existing golden-fixture exclusions
with a single documented path entry so the data stays unchanged.

### Release note

None

### Check List (For Author)

- Test: License Eye v0.8.0 header checks; git diff --check
- Behavior changed: No
- Does this need documentation: No
@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@github-actions

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: Codex completed, but no new pull request review was submitted for the current head SHA.
Workflow run: https://github.com/apache/doris/actions/runs/35082328359

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

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

Static review of exact head 15decc03b6957fe4212b7de85fd8ee913d888810 against target snapshot 0aea7140de64a7162d0a0d764e3dc1db7f40dbae.

Requesting changes for two P1 compatibility failures: the new public connector property remains under API major 8, so mismatched Hive plugin/FE pairs silently lose OpenCSV mode; and the new optional Thrift flag has no old-BE fence, so a rolling-upgrade backend silently selects generic CSV semantics. Both produce data-dependent wrong query results rather than a compatibility error.

Critical checkpoints:

  • Goal and proof: for a current plugin, FE, and BE, the change now matches Hive/OpenCSV field states and physical records across both scanners. The Hive-generated oracle, BE reader tests, FE property/wire tests, and external regression cover the intended behavior, but they do not make the two mixed-version deployments safe.
  • Scope/focus: all 21 authoritative changed paths and their upstream/downstream call chains were reviewed. The implementation is otherwise focused; no additional user focus was provided.
  • Concurrency and lifecycle: parser/reader state is per reader; no new shared concurrent state or lock path is introduced. Parser-owned decoded slices are consumed before reuse, and BOM/header/split/EOF/count lifecycles were traced without another issue.
  • Configuration and conditions: no runtime configuration item is added. OpenCSV tuple normalization and special quote/escape/NUL conditions are covered by the existing resolved threads and were not duplicated here.
  • Compatibility and FE-BE transport: current-version property propagation reaches the shared scan-level attributes and both V1/V2 readers, but the plugin/FE API-major gate and FE/old-BE execution boundary are both missing required fences; see the inline comments.
  • Parallel paths: both scanner implementations, projected/missing/empty/null materialization, batch/streaming splits, compression, and physical LF/CRLF/CR ownership were checked. No additional divergence survived the two-round review.
  • Tests/results: the added unit/oracle/regression coverage is substantial and discoverable. This reviewer did not run builds or tests because the review runner contract explicitly prohibits them, so this conclusion is static-only and does not independently verify the author-reported results.
  • Observability, persistence, and writes: no new persistent state, transaction/data-write path, or observability requirement is introduced.
  • Performance: decoded-buffer reuse and bounded requested-field parsing avoid a distinct material regression; no actionable performance issue was found.

Review status: complete static review after two rounds; all candidates were deduplicated and adjudicated, and the second-round normal and risk-focused reviewers returned NO_NEW_VALUABLE_FINDINGS.

public static final String TEXT_ENCLOSE = TEXT_PROPERTY_PREFIX + "enclose";

/** {@code "true"} for Hive OpenCSV field states and physical record boundaries. */
public static final String TEXT_HIVE_OPEN_CSV = TEXT_PROPERTY_PREFIX + "hive_open_csv";

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] Version the connector SPI property addition

This is a new engine-read member of the shared connector SPI, but the connector API and its pinned test remain at 8.0 even though this repository defines every SPI-surface addition as a major change. Because this String constant is compile-time-inlined, a new API-8 Hive plugin still loads on an old API-8 FE, emits the literal key, and the old FE silently ignores it; the reverse pairing never emits it. Both paths fall back to generic CSV semantics instead of OpenCSV. Please bump the connector API major/update its pin and extend the frozen surface/baseline to include these public engine-read property keys so this protocol change cannot bypass the version gate.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 3cb2835. The connector API is now 9.0, with its pinned version updated in the same commit. ConnectorPluginSurfaceTest records every public ScanNodePropertyKeys field by name, type, and literal value, so an inlined key/value change becomes a visible API-surface change. Both baselines were regenerated; the metadata-method baseline remains identical.

The actual plugin directory loader now rejects an API 8.0 probe jar on this FE; the existing matching-version and other-major tests also pass. Validation: all 142 connector SPI tests, 480 Hive connector tests, and 34 FE Core tests passed. The old-version loader and version/surface checks reproduced the failures before the fix.

13: optional bool openx_json_ignore_malformed = false;

// Hive OpenCSVSerde has different field states and physical record boundaries from load CSV.
14: optional bool hive_open_csv = false;

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] Fence this semantic flag from old backends

An older BE legally ignores unknown optional field 14, so it sees the default false and runs the generic CSV reader even though the new FE promised OpenCSVSerde semantics. That fallback is observably different for records already covered by this PR (for example custom quotes/leading whitespace), yet this change neither advances/checks a minimum BE execution version nor marks the Hive scan with REQUIRED_CURRENT_BACKEND_SEMANTICS. During a rolling upgrade, placement can therefore change query results instead of failing. Please gate OpenCSV scans on a BE version that implements this field (including the smooth-upgrade-source fence) and add an absent-field/mixed-version test.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 3cb2835. FE and BE now define OpenCSV support at execution version 15. The real scan attribute builder rejects OpenCSV when the configured query-wide execution version is below 15, and Hive emits REQUIRED_CURRENT_BACKEND_SEMANTICS so the existing scheduler fence rejects eligible smooth-upgrade source backends even at version 15.

Added FE tests for version 14 rejection, version 15 acceptance, a mixed candidate list containing a smooth-upgrade source, and the absent-flag wire contract. Both actual BE scanners additionally verify that an absent flag retains legacy decoding while the same bytes with the flag use OpenCSV decoding. The BE version manager accepts both legacy version 14 and the new version 15. Validation: 34 FE Core tests and 47 BE tests under ASAN passed; the missing FE fences and unsupported BE version were reproduced before the fix.

### What problem does this PR solve?

Related PR: apache#68027

Problem Summary: Inlined SPI property keys and optional Thrift fields can be
ignored by older components, silently restoring generic CSV decoding. Bump the
connector API to 9.0, freeze scan property names and values in its surface
baseline, and require backend execution version 15 for OpenCSV scans. Mark those
scans for the existing smooth-upgrade-source fence. Preserve legacy decoding
when the semantic flag is absent.

### Release note

Hive OpenCSV scans require backend execution version 15 or newer. Connector
plugins must use API 9.0.

### Check List (For Author)

- Test: Connector SPI and Hive suites, FE scan and plugin compatibility tests,
  ASAN CSV reader tests, FE Checkstyle, clang-format 16, header hygiene,
  License Eye, and git diff --check. Clang-tidy is blocked by an existing
  unmatched NOLINTEND in core/types.h; the version manager has no diagnostics.
- Behavior changed: Yes, reject incompatible OpenCSV execution during upgrades.
- Does this need documentation: No
@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@github-actions

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: Codex completed, but no new pull request review was submitted for the current head SHA.
Workflow run: https://github.com/apache/doris/actions/runs/35087619815

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

@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 of exact head 3cb283539ef1e85ff10aa22a9d9ff58a6043fe35 against target snapshot 0aea7140de64a7162d0a0d764e3dc1db7f40dbae. Requesting changes for one P1 BE memory-safety issue: the new parser can allocate and retain a whole decoded-row copy without Doris allocator prechecks while the raw row and output coexist. Existing review threads were treated as hard duplicate fences and were not repeated.

Critical-checkpoint conclusions:

  • Goal and proof: the change implements Hive OpenCSVSerde character, field-state, physical-record, projection, and missing-versus-empty semantics across both CSV scanners. The Hive-generated oracle plus FE, BE, and regression tests are broad, but the accepted wide-record allocation path remains unsafe, and no test proves controlled failure/release under a query memory limit.
  • Focus: the 28 changed files are coherently scoped to property extraction, FE-to-BE transport/version fencing, shared parsing/record handling, and coverage.
  • Concurrency: parser state is reader-local and scheduling/version state uses existing immutable paths; no new shared mutable state, lock, atomic, or deadlock issue was found.
  • Lifecycle and static initialization: each range reader owns its parser and consumes decoded slices before the next parse. There is no new cross-TU static dependency or reference cycle; the blocking lifecycle issue is retained high-water scratch capacity until range-reader destruction.
  • Configuration: no new runtime configuration item is added. The existing UTF-8-validation option is propagated through the established scan path, so no dynamic-update gap was found.
  • Compatibility and rolling upgrades: connector API 9, execution version 15, optional Thrift defaulting, FE construction checks, and the smooth-upgrade-source scheduling fence cover old/new plugin, FE, and BE combinations without silent fallback.
  • Parallel paths: V1 and FileScannerV2 both use the shared parser and physical-record context, including projection and count paths; no missed parallel implementation remained.
  • Conditions and errors: tuple validation, NUL/disabled characters, default-escape conversion, execution-version guards, missing fields, and UTF-8 modes were traced and match their stated invariants. Errors propagate through existing Status/exception boundaries with adequate context.
  • Test coverage: parser-oracle prefixes, dense/reordered projections, missing versus empty values, LF/CRLF/CR, BOM, split offsets, counts, tuple validation, wire defaults, API loading, and version fences are covered. The external Hive regression is end-to-end in structure, but this review did not execute it.
  • Test results: added expectations and generated oracle rows are internally consistent by static inspection. Per the review contract, I ran no builds or tests; current CI has format, checkstyle, license, dependency, secrets, and Cloud UT passing, while BE UT, FE UT, compile, and performance are still pending.
  • Observability: no new distributed operation or metric is required; existing scan/planning errors and profiles are adequate once the memory path fails before allocation.
  • Transactions and persistence: no Doris transaction, EditLog, replay, master-failover, or persisted-format behavior is changed.
  • Data writes: production changes are read-side only, so write atomicity and crash cleanup are not implicated.
  • FE-to-BE variables: the new OpenCSV flag and character properties reach the common scan attributes, optional Thrift field, and both BE readers; all identified send and receive paths are updated.
  • Performance and memory: field parsing is prefix-bounded and otherwise linear, but the accepted decoded-row scratch creates an additional row-sized allocation, lacks allocator prechecks, and retains outlier capacity. No other material CPU, I/O, or RPC regression survived review.
  • Other issues: the pre-existing multi-header split limitation and raw-byte behavior when UTF-8 validation is disabled are not introduced defects. No additional issue remained after two bounded rounds, and every Round 2 reviewer returned NO_NEW_VALUABLE_FINDINGS.

User focus: no additional focus was supplied. Completion status: complete static review with one accepted inline finding; no builds, tests, or source edits were performed.

Comment thread be/src/format_v2/delimited_text/hive_csv_parser.h
Restore the legacy format readers, relocate CSV helpers and tests to format_v2, and avoid NUL metadata in PostgreSQL-backed Hive regression fixtures.
@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@Gabriel39

Copy link
Copy Markdown
Contributor Author

Scoped the CSV reader fix to FileScannerV2 and restored be/src/format/ and be/test/format/ to the PR base. The OpenCSV parser, physical-record helper, and reader tests now live under the V2 directories. The external regression explicitly enables V2 and restores the original session setting afterward.

Also corrected the CSV regression fixture: Hive decodes the disabled-character SQL literal to NUL, which a PostgreSQL-backed metastore cannot persist in text properties. The integration fixture now uses four valid dialects. Disabled quote/escape settings and binary NUL data remain covered by the Hive oracle and V2 unit tests.

Validation: 43 BE tests passed under ASAN, 480 Hive connector tests passed, Groovy compilation and FE Checkstyle passed, and all affected C++ files passed clang-format 16. License Eye and header hygiene passed. A real Hive SQL-decoding/PostgreSQL probe reproduced the original metadata failure and successfully persisted all 12 revised property values. The V2 parser and line helper are clang-tidy clean; existing reader/header diagnostics still prevent a clean repository-wide result. Full external regression validation awaits the new CI run.

@Gabriel39 Gabriel39 changed the title [fix](hive) Match OpenCSVSerde property and row semantics [fix](hive) Match OpenCSVSerde semantics in FileScannerV2 Sep 16, 2026

@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 at exact head f5efe3445cf31d704039d337e6485b54719b13ba: requesting changes for two newly substantiated issues.

  1. The OpenCSV semantic flag can still execute through the legacy scanner when enable_file_scanner_v2=false, silently changing valid Hive rows.
  2. The new physical-line context rescans accumulated compressed records from byte zero, making large valid records quadratic to frame.

Critical checkpoints:

  • Goal/data correctness: the V2 parser and table-over-SerDe property path otherwise match the inspected Hive/OpenCSV cases; the goal is incomplete because the parallel V1 path can bypass them.
  • Scope/compatibility/configuration: connector API 9, the optional Thrift field, execution version 15, and smooth-upgrade fencing are coherent; the existing session scanner selector is the remaining compatibility gap.
  • Concurrency/lifecycle/null/error handling: state is per reader with no new shared locks or threads; returned slices are consumed before parser reuse; missing versus empty/NULL handling and fail-loud version checks are coherent.
  • Performance/memory: the quadratic compressed-record scan is newly reported. The existing allocator/retained-scratch P1 at discussion_r4025769228 remains open and was not duplicated.
  • Tests/results: the deterministic Hive oracle, FE wire/SPI tests, direct V2 BE tests, and regression are broad, but they force or directly construct V2 and use uncompressed input, so they miss both findings. The external end-to-end rerun remains pending per the PR description.
  • Persistence/transactions/writes/observability: this is a read-only scan change; no EditLog, transaction, or data-write path is modified, and no additional observability gap was found.
  • FE-to-BE contract: the new property reaches Thrift and FileScannerV2, but not the legacy reader selected by the session option.

Validation was static-only as required; no builds or tests were run. No additional user focus was specified.

throw new UserException("Hive OpenCSVSerde requires backend execution version "
+ Config.HIVE_OPEN_CSV_MIN_BE_EXEC_VERSION + " or newer during rolling upgrade");
}
attrs.setHiveOpenCsv(true);

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] Require FileScannerV2 for this semantic flag. This is distinct from the old-BE fence and earlier parser-state threads: a current version-15 BE receives hive_open_csv, but FileScanLocalState::should_use_file_scanner_v2() still honors enable_file_scanner_v2=false and routes the scan through legacy FileScanner, whose CSV reader never consumes the flag. That is a silent correctness fallback: with the default read_csv_empty_line_as_null=false, a blank physical record is skipped by V1, while Hive/OpenCSVSerde (and this V2 implementation) returns an all-NULL row. The regression's checkV2 helper forces V2 on, so it cannot catch this. Please force V2 for OpenCSV scans (as the ADBC path does) or reject planning when V2 is disabled.

namespace doris::format::csv {

const uint8_t* HiveCsvLineReaderCtx::read_line(const uint8_t* start, size_t len) {
for (size_t i = 0; i < len; ++i) {

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] Resume delimiter search after compressed refills. NewPlainTextLineReader calls this context repeatedly with the entire accumulated output buffer until a delimiter appears, but this loop restarts at byte 0 each time. Compressed input is refilled in 2 MiB chunks, so a valid incompressible N-byte physical record is searched over prefixes of roughly 2 MiB, 4 MiB, ... N (about 256 GiB of byte checks for a 1 GiB record) before the CSV parser scans it again. The previous enclosure-aware context retained _idx, and the added tests are all uncompressed. Please retain a per-record search offset, rechecking only a terminal CR for CRLF lookahead, and reset it in refresh().

@hello-stephen

Copy link
Copy Markdown
Contributor

Cloud UT Coverage Report

Increment line coverage 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 77.52% (2059/2656)
Line Coverage 65.82% (37894/57569)
Region Coverage 53.20% (35482/66701)
Branch Coverage 56.64% (11394/20116)

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 100.00% (3/3) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 76.18% (34339/45078)
Line Coverage 61.13% (385702/631001)
Region Coverage 57.66% (325557/564604)
Branch Coverage 58.31% (147909/253657)

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 50.00% (2/4) 🎉
Increment coverage report
Complete coverage report

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17572	3029	3011	3011
q2	2101	264	225	225
q3	10245	923	521	521
q4	4672	249	206	206
q5	7681	588	393	393
q6	136	113	94	94
q7	538	522	411	411
q8	9239	930	911	911
q9	3590	2519	2401	2401
q10	6527	875	727	727
q11	391	199	182	182
q12	615	256	202	202
q13	18152	1594	1194	1194
q14	165	153	147	147
q15	q16	458	400	368	368
q17	1376	917	778	778
q18	3158	2364	2303	2303
q19	1106	933	801	801
q20	372	276	200	200
q21	5258	1633	1847	1633
q22	323	270	232	232
Total cold run time: 93675 ms
Total hot run time: 16940 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	3383	3313	3297	3297
q2	498	397	377	377
q3	2278	2313	2167	2167
q4	1221	1192	913	913
q5	2244	2191	2176	2176
q6	167	122	90	90
q7	1036	932	918	918
q8	1583	1399	1428	1399
q9	3273	3231	3207	3207
q10	1910	1868	1652	1652
q11	356	272	258	258
q12	464	443	346	346
q13	1457	1557	1183	1183
q14	175	167	179	167
q15	q16	403	398	364	364
q17	3665	3315	3311	3311
q18	4982	4559	5023	4559
q19	958	880	853	853
q20	1021	981	836	836
q21	3901	3252	3279	3252
q22	390	347	310	310
Total cold run time: 35365 ms
Total hot run time: 31635 ms

@hello-stephen

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

query5	4281	410	331	331
query6	390	135	130	130
query7	4952	426	239	239
query8	302	121	125	121
query9	8706	2915	2920	2915
query10	390	219	209	209
query11	5371	1047	909	909
query12	115	73	75	73
query13	1188	463	317	317
query14	6160	2287	2181	2181
query14_1	2029	1985	2011	1985
query15	173	120	106	106
query16	895	365	337	337
query17	755	431	346	346
query18	2320	328	229	229
query19	154	135	99	99
query20	74	70	69	69
query21	205	104	86	86
query22	5470	5532	5416	5416
query23	6887	6388	6038	6038
query23_1	6315	6303	6111	6111
query24	7286	1088	764	764
query24_1	777	785	783	783
query25	401	274	242	242
query26	1227	234	129	129
query27	2805	442	253	253
query28	4658	1501	1496	1496
query29	898	417	331	331
query30	250	156	128	128
query31	857	406	336	336
query32	125	70	68	68
query33	444	201	163	163
query34	976	813	481	481
query35	412	407	348	348
query36	564	558	528	528
query37	123	77	67	67
query38	1013	854	844	844
query39	509	511	499	499
query39_1	470	467	470	467
query40	198	89	76	76
query41	56	52	51	51
query42	77	73	71	71
query43	246	243	219	219
query44	994	540	540	540
query45	115	107	101	101
query46	806	859	557	557
query47	793	784	732	732
query48	310	299	235	235
query49	539	249	194	194
query50	757	255	195	195
query51	8040	8047	7937	7937
query52	68	78	65	65
query53	191	194	145	145
query54	218	170	159	159
query55	77	59	59	59
query56	322	179	169	169
query57	730	715	667	667
query58	209	186	171	171
query59	1261	1262	1123	1123
query60	251	202	190	190
query61	130	127	147	127
query62	361	205	191	191
query63	171	145	142	142
query64	2893	789	558	558
query65	1671	1656	1673	1656
query66	1851	261	206	206
query67	10188	10044	10221	10044
query68	3034	1150	734	734
query69	341	211	195	195
query70	682	627	625	625
query71	247	181	176	176
query72	2314	1642	1477	1477
query73	659	607	337	337
query74	1985	1253	1144	1144
query75	1218	1122	968	968
query76	2401	708	532	532
query77	261	248	205	205
query78	4020	3921	3340	3340
query79	2246	890	553	553
query80	1598	340	270	270
query81	486	158	139	139
query82	618	128	98	98
query83	284	214	192	192
query84	292	108	89	89
query85	768	344	278	278
query86	389	179	180	179
query87	1045	999	917	917
query88	2817	2143	2106	2106
query89	295	201	178	178
query90	2002	135	126	126
query91	129	120	94	94
query92	81	69	73	69
query93	1366	1153	709	709
query94	633	255	188	188
query95	532	331	232	232
query96	808	616	262	262
query97	1120	1105	1041	1041
query98	146	134	134	134
query99	421	351	311	311
Total cold run time: 179100 ms
Total hot run time: 82872 ms

@hello-stephen

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

query1	0.01	0.00	0.00
query2	0.08	0.04	0.04
query3	0.24	0.11	0.12
query4	1.62	0.09	0.10
query5	0.18	0.16	0.16
query6	1.26	0.72	0.70
query7	0.04	0.01	0.00
query8	0.04	0.03	0.03
query9	0.28	0.22	0.22
query10	0.36	0.33	0.35
query11	0.16	0.12	0.12
query12	0.15	0.13	0.13
query13	0.31	0.31	0.32
query14	0.47	0.46	0.47
query15	0.38	0.39	0.36
query16	0.24	0.23	0.24
query17	0.71	0.74	0.73
query18	0.19	0.17	0.17
query19	1.23	1.23	1.19
query20	0.01	0.01	0.01
query21	15.51	0.18	0.11
query22	5.04	0.04	0.04
query23	16.16	0.25	0.09
query24	2.98	0.32	0.26
query25	0.10	0.05	0.04
query26	0.84	0.18	0.11
query27	0.04	0.04	0.03
query28	3.64	0.55	0.27
query29	12.51	3.23	2.58
query30	0.26	0.11	0.12
query31	2.76	0.38	0.18
query32	3.51	0.33	0.24
query33	1.49	1.41	1.47
query34	15.35	2.20	1.76
query35	1.84	1.77	1.77
query36	0.47	0.30	0.29
query37	0.06	0.04	0.04
query38	0.04	0.03	0.02
query39	0.03	0.02	0.02
query40	0.12	0.08	0.07
query41	0.07	0.02	0.02
query42	0.03	0.03	0.02
query43	0.03	0.03	0.02
Total cold run time: 90.84 s
Total hot run time: 14.89 s

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants