GH-50967: [C++] Allow CSV reader to ignore extra columns in rows with more columns - #51118
GH-50967: [C++] Allow CSV reader to ignore extra columns in rows with more columns#51118HuaHuaY wants to merge 5 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new C++ CSV parsing option to tolerate rows with more fields than expected by ignoring any extra columns, instead of treating such rows as invalid. This extends the existing “pad short rows” behavior on the other side of the mismatch spectrum and makes the CSV reader more flexible for imperfect inputs.
Changes:
- Introduces
ParseOptions::ignore_extra_columns(defaultfalse) to ignore surplus fields when the expected column count is known. - Updates the CSV block parser to avoid materializing/parsing data for ignored extra fields while still scanning correctly for delimiters/newlines.
- Adds parser- and reader-level tests covering extra columns (including trailing delimiters) and updates dataset
CsvFileFormat::Equalsto account for the new option.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| cpp/src/arrow/dataset/file_csv.cc | Includes ignore_extra_columns in CsvFileFormat::Equals so format equality reflects the new parse behavior. |
| cpp/src/arrow/csv/options.h | Adds the ignore_extra_columns parse option to the public ParseOptions API. |
| cpp/src/arrow/csv/parser.cc | Implements ignoring of extra fields in the core parsing state machine (including bulk filter path). |
| cpp/src/arrow/csv/parser_test.cc | Adds a unit test validating that extra columns are ignored at the block parser level. |
| cpp/src/arrow/csv/reader_test.cc | Adds an integration test validating TableReader behavior with extra columns. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
52277b3 to
3e198d8
Compare
|
@ursabot please benchmark lang=C++ |
|
Benchmark runs are scheduled for commit 3e198d8. Watch https://buildkite.com/apache-arrow and https://conbench.arrow-dev.org for updates. A comment will be posted here when the runs are complete. |
3e198d8 to
0f0c8c3
Compare
|
@ursabot please benchmark lang=C++ |
|
Benchmark runs are scheduled for commit 0f0c8c3. Watch https://buildkite.com/apache-arrow and https://conbench.arrow-dev.org for updates. A comment will be posted here when the runs are complete. |
There was a problem hiding this comment.
🟢 Approval recommended
The change is opt-in, localized to CSV parsing, and is covered by new parser- and reader-level tests for the intended behavior.
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 0 new
- Review effort level: Lite
0f0c8c3 to
a02ebcf
Compare
| template <typename... CompiledBools, typename Fn, typename... Rest> | ||
| requires std::invocable<Fn, CompiledBools..., | ||
| std::conditional_t<true, std::true_type, Rest>...> | ||
| decltype(auto) DispatchBool(Fn&& fn, Rest... rest) { |
There was a problem hiding this comment.
I place this utility function here because it can also be used later when handling SpecializedOptions in chunker.cc.
|
@ursabot please benchmark lang=C++ |
|
Benchmark runs are scheduled for commit a02ebcf. Watch https://buildkite.com/apache-arrow and https://conbench.arrow-dev.org for updates. A comment will be posted here when the runs are complete. |
There was a problem hiding this comment.
🟡 Changes recommended
There are confirmed build/correctness issues (missing required header includes for DispatchBool, and incorrect handling of implicit trailing empty field metadata at EOF) that should be fixed before merge.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 6/6 changed files
- Comments generated: 3
- Review effort level: Lite
| AbortLine: | ||
| // Not a full line except perhaps if in final block | ||
| if (is_final) { | ||
| if constexpr (IgnoreExtraColumns) { | ||
| // Handle an implicit trailing empty field after a delimiter. | ||
| ignoring_extra_field = IsExtraField(); | ||
| } | ||
| goto LineEnd; | ||
| } |
There was a problem hiding this comment.
This appears to be a legacy issue and was not introduced by the current PR.
855f742 to
a80a3aa
Compare
| template <typename... CompiledBools, typename Fn, typename... Rest> | ||
| requires std::invocable<Fn, CompiledBools..., | ||
| std::conditional_t<true, std::true_type, Rest>...> | ||
| decltype(auto) DispatchBool(Fn&& fn, Rest... rest) { |
There was a problem hiding this comment.
I feel this is a bit too strict; it actually makes the code complicated.
|
@ursabot please benchmark lang=C++ |
|
Benchmark runs are scheduled for commit a80a3aa. Watch https://buildkite.com/apache-arrow and https://conbench.arrow-dev.org for updates. A comment will be posted here when the runs are complete. |
|
@ursabot please benchmark lang=C++ |
|
Benchmark runs are scheduled for commit 2ac3639. Watch https://buildkite.com/apache-arrow and https://conbench.arrow-dev.org for updates. A comment will be posted here when the runs are complete. |
Is this comment no longer being sent? |
|
I think the benchmark performance regression caused by this PR has basically disappeared. @pitrou Please take a look again. |
|
|
||
| template <bool... CompiledBools, typename Fn, typename... Rest> | ||
| decltype(auto) DispatchBool(Fn&& fn, Rest... rest) | ||
| requires requires { |
There was a problem hiding this comment.
Yes. The first requires is a syntactic construct. The second requires is a requires expression. We need the second requires when checking whether a statement compiles, rather than when passing a concept.
Indeed, it's not, because we disabled some of the benchmarking machines due to resource consumption issues (cc @rok ). Or at least I think that's the reason. |
Machines are still running, but maybe during optimization (I was changing node sizes etc) some callbacks were not executed. Please benchmark again if you need to and ping me if conbench doesn't post back. |
|
When walking through the benchmark results directly from https://conbench.arrow-dev.org/, we see indeed that there's no regression anymore. |
2ac3639 to
3000cb4
Compare
|
I rebase the code and add one more commit, which adds some comments about |
There was a problem hiding this comment.
🟢 Approval recommended
The new option is consistently wired through parsing and dataset inspection, and is covered by targeted parser and reader tests.
Review details
- Files reviewed: 6/6 changed files
- Comments generated: 0 new
- Review effort level: Lite
|
@github-actions crossbow submit -g cpp |
|
Revision: 3000cb4 Submitted crossbow builds: ursacomputing/crossbow @ actions-5d85f52df3 |
Rationale for this change
Currently, the C++ CSV reader rejects rows with more columns than expected. We can allow users to ignore the extra values instead of throwing exception.
What changes are included in this PR?
Add an option
ignore_extra_columnsin CSVstruct ParseOptionsthat ignores extra columns.Are these changes tested?
Yes.
Are there any user-facing changes?
Add an option
ignore_extra_columnsin CSVstruct ParseOptions.