Skip to content

Select Parquet columns by field ID - #22955

Merged
rapids-bot[bot] merged 26 commits into
NVIDIA:mainfrom
mhaseeb123:fea/select-pq-cols-by-field-id
Jul 11, 2026
Merged

Select Parquet columns by field ID#22955
rapids-bot[bot] merged 26 commits into
NVIDIA:mainfrom
mhaseeb123:fea/select-pq-cols-by-field-id

Conversation

@mhaseeb123

Copy link
Copy Markdown
Contributor

Description

Contributes to #22849

This PR enables the parquet readers to select columns using parquet field ID (needed for Iceberg) where column names across table files may vary.

Checklist

  • I am familiar with the Contributing Guidelines.
  • New or existing tests cover these changes.
  • The documentation is up to date with these changes.

@copy-pr-bot

copy-pr-bot Bot commented Jun 24, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@mhaseeb123 mhaseeb123 self-assigned this Jun 24, 2026
@mhaseeb123 mhaseeb123 added feature request New feature or request 3 - Ready for Review Ready for review by team libcudf Affects libcudf (C++/CUDA) code. cuIO cuIO issue non-breaking Non-breaking change Velox Functionality that helps Velox-cudf labels Jun 24, 2026
@mhaseeb123
mhaseeb123 marked this pull request as ready for review June 24, 2026 00:52
@mhaseeb123
mhaseeb123 requested a review from a team as a code owner June 24, 2026 00:52
@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds column_field_ids as a third mutually exclusive Parquet column-selection mode. Field-ID-aware schema matching, projection mapping, hybrid-scan propagation, and test coverage were added across the reader path.

Changes

Parquet column selection by field ID

Layer / File(s) Summary
Public API: field ID option and builder method
cpp/include/cudf/io/parquet.hpp
Adds _column_field_ids, the getter and setter, exclusivity checks against names and indices, and the fluent builder method.
column_path_from_index helper
cpp/src/io/parquet/column_path_helpers.hpp, cpp/src/io/parquet/column_path_helpers.cpp
Declares and implements the schema-tree traversal that builds a dot-separated column path from a schema index.
expression_transform_helpers: three-mode field-ID mapping
cpp/src/io/parquet/expression_transform_helpers.hpp, cpp/src/io/parquet/expression_transform_helpers.cpp
Changes schema_tree to std::span, expands validation to names, indices, and field IDs, adds index bounds checks, and maps field IDs to column paths.
reader_impl and helpers: field-ID projection and schema matching
cpp/src/io/parquet/reader_impl.hpp, cpp/src/io/parquet/reader_impl.cpp, cpp/src/io/parquet/reader_impl_helpers.hpp, cpp/src/io/parquet/reader_impl_helpers.cpp
Adds field-ID-aware projection handling, mismatched-schema detection, schema-child lookup by field ID, and field-ID-aware column matching across projected paths and additional sources.
Hybrid scan field-ID propagation
cpp/src/io/parquet/experimental/hybrid_scan_helpers.hpp, cpp/src/io/parquet/experimental/hybrid_scan_helpers.cpp, cpp/src/io/parquet/experimental/hybrid_scan_impl.cpp
Extends hybrid scan payload selection to accept match_schema_by_field_id, and passes field-ID selection state through all selection paths and mismatched-schema detection.
Tests: flat, nested, filtered, and mismatched-schema coverage
cpp/tests/io/parquet_common.hpp, cpp/tests/io/parquet_common.cpp, cpp/tests/io/parquet_reader_test.cpp
Updates Parquet test helpers to write field IDs, and expands reader tests to cover field-ID selection, nested selection, filter projection, and mismatched-schema cases.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Suggested labels

improvement

Suggested reviewers

  • qbacpey
  • davidwendt
  • bdice
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: selecting Parquet columns by field ID.
Description check ✅ Passed The description matches the changeset and explains the field-ID column selection feature and test coverage.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cpp/src/io/parquet/expression_transform_helpers.cpp`:
- Around line 277-281: The validation using std::cmp_less for selected_col_idx
only checks the upper bound against root.children_idx.size() but does not
validate that the index is non-negative. A negative selected_col_idx would
bypass this check and cause undefined behavior when used to index into
root.children_idx on the following line. Add an additional validation check to
ensure selected_col_idx is greater than or equal to zero, either as a separate
CUDF_EXPECTS call or by combining it with the existing upper bound check using a
logical AND condition.

In `@cpp/src/io/parquet/reader_impl_helpers.cpp`:
- Around line 1924-1930: The field-ID based lookup is currently only applied
when col_name_info has explicit child paths, but the all-children branch that
handles non-leaf field ID selection still uses ordinal-based pairing for
descendants. Locate the branch that handles the case where a non-leaf field is
selected without explicit child paths (the all-children case), and modify it to
use field-ID lookup similar to the code shown in the diff. Specifically, replace
the ordinal-based child pairing logic with calls to
find_schema_child_for_mapping (instead of just find_schema_child) when iterating
through all children of the selected field, ensuring that nested children are
matched by field ID across sources rather than by position.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: c661b3f7-43fd-4b6c-870a-17ce7cc8efd7

📥 Commits

Reviewing files that changed from the base of the PR and between d7d9751 and 5781078.

📒 Files selected for processing (14)
  • cpp/include/cudf/io/parquet.hpp
  • cpp/src/io/parquet/column_path_helpers.cpp
  • cpp/src/io/parquet/column_path_helpers.hpp
  • cpp/src/io/parquet/experimental/hybrid_scan_helpers.cpp
  • cpp/src/io/parquet/experimental/hybrid_scan_helpers.hpp
  • cpp/src/io/parquet/experimental/hybrid_scan_impl.cpp
  • cpp/src/io/parquet/expression_transform_helpers.cpp
  • cpp/src/io/parquet/expression_transform_helpers.hpp
  • cpp/src/io/parquet/reader_impl.cpp
  • cpp/src/io/parquet/reader_impl_helpers.cpp
  • cpp/src/io/parquet/reader_impl_helpers.hpp
  • cpp/tests/io/parquet_common.cpp
  • cpp/tests/io/parquet_common.hpp
  • cpp/tests/io/parquet_reader_test.cpp

Comment thread cpp/src/io/parquet/expression_transform_helpers.cpp Outdated
Comment thread cpp/src/io/parquet/reader_impl_helpers.cpp Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cpp/tests/io/parquet_reader_test.cpp`:
- Around line 641-645: The regression test in the parquet_reader_test.cpp file
is not exercising child field-ID selection as intended because
column_field_ids({1}) only selects the top-level record struct. To fix this,
modify the column_field_ids call in the parquet_reader_options builder to select
a child field ID (such as {2} or {3} representing child fields like x or y)
instead of the top-level field, and then add assertions that verify the
projected nested output shape is correct. This ensures the test properly catches
bugs in mismatched-schema child lookup by field ID.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 3eba06e6-f115-4ed3-afb5-611e4e689556

📥 Commits

Reviewing files that changed from the base of the PR and between 5781078 and 4b99d07.

📒 Files selected for processing (3)
  • cpp/src/io/parquet/expression_transform_helpers.cpp
  • cpp/src/io/parquet/reader_impl_helpers.cpp
  • cpp/tests/io/parquet_reader_test.cpp
🚧 Files skipped from review as they are similar to previous changes (2)
  • cpp/src/io/parquet/expression_transform_helpers.cpp
  • cpp/src/io/parquet/reader_impl_helpers.cpp

Comment thread cpp/tests/io/parquet_reader_test.cpp
{col_idx, normalize_column_path(schema_tree[schema_idx].name, case_sensitive_names)});
});
}
std::transform(

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.

Mostly whitespace here (try hiding it in your view). The only interesting part is under else if (selected_column_field_ids.has_value()) { block.

*/
[[nodiscard]] auto const& get_column_indices() const { return _column_indices; }

/**

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.

Pretty standard setters and getters

decimal_type_id,
case_sensitive_names);
case_sensitive_names,
match_schema_by_field_id);

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.

Just plumbed in a new boolean field

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.

these are mutually exclusive right? should you instead switch to an enum to reduce the number of arguments? the api is experimental after all.

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.

these are mutually exclusive right?
Yes

should you instead switch to an enum to reduce the number of arguments? the api is experimental after all.

We are calling the base reader's select_columns API (being updated to take in another bool here).

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.

sure but can we update that API also to reduce argument bloat everywhere

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.

Done 3dfb09f

cudf::host_span<cudf::host_span<uint8_t const> const> footer_bytes,
parquet_reader_options const& options)
{
auto const has_cols_from_mismatched_srcs =

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.

Separate out this logic: We can have mismatched columns if allowed AND selecting columns by names or field IDs

hybrid_scan_reader_impl::hybrid_scan_reader_impl(
cudf::host_span<FileMetaData const> parquet_metadatas, parquet_reader_options const& options)
{
auto const has_cols_from_mismatched_srcs =

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.

Same here


namespace cudf::io::parquet::detail {

std::string column_path_from_index(std::span<SchemaElement const> schema_tree, int schema_idx)

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.

Given a ColumnChunk field ID, keep going to its parent until root and then construct a dot (.) separated string path

Comment thread cpp/src/io/parquet/reader_impl_helpers.cpp Outdated
for (std::size_t i = 0; i < column_names.size(); ++i) {
md.column_metadata[i].set_name(column_names[i]);
}
for (std::size_t i = 0; i < field_ids.size(); ++i) {

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.

Allow setting field_ids in the written parquet files in tests

find_schema_child(src_schema_elem, child_col_name_info.name),
find_schema_child(dst_schema_elem, child_col_name_info.name, pfm_idx),
pfm_idx);
auto const src_child_idx = find_schema_child(src_schema_elem, child_col_name_info.name);

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.

For structs, make sure all children (not necessarily arranged in the same order though) have the same field ID as well.

: -1;
};

auto const find_schema_child_for_mapping = [&](SchemaElement const& src_schema_elem,

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.

Either calls find_schema_child (by name) or find_schema_child_by_field_id

CUDF_TEST_EXPECT_COLUMNS_EQUAL(result.tbl->view().column(3), c);
}

{

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.

Modify existing column selection tests here to test column selection with and without filters by field ID

Comment thread cpp/src/io/parquet/reader_impl.cpp Outdated
Comment thread cpp/src/io/parquet/reader_impl_helpers.cpp Outdated
@mhaseeb123
mhaseeb123 force-pushed the fea/select-pq-cols-by-field-id branch from 0914161 to fe2485d Compare July 1, 2026 00:22
@NVIDIA NVIDIA deleted a comment from copy-pr-bot Bot Jul 1, 2026
@mhaseeb123 mhaseeb123 added 4 - Needs Review Waiting for reviewer to review or respond and removed 3 - Ready for Review Ready for review by team labels Jul 1, 2026
@mhaseeb123
mhaseeb123 requested a review from vuule July 9, 2026 21:11
Comment thread cpp/include/cudf/io/parquet.hpp Outdated
Comment thread cpp/src/io/parquet/reader_impl_helpers.hpp Outdated
Comment thread cpp/src/io/parquet/reader_impl.hpp Outdated
Comment thread cpp/src/io/parquet/experimental/hybrid_scan_impl.cpp Outdated
CUDF_EXPECTS(
not(selected_column_names.has_value() and selected_column_indices.has_value()),
"Parquet reader encountered column selection by both names and indices simultaneously");
CUDF_EXPECTS(static_cast<int>(selected_column_names.has_value()) +

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 feel like we should not be doing this. The options object has the invariant that only one of these is set.

Comment thread cpp/src/io/parquet/reader_impl.cpp Outdated
Comment thread cpp/tests/io/parquet_reader_test.cpp
@mhaseeb123
mhaseeb123 requested a review from vuule July 10, 2026 18:33
@mhaseeb123 mhaseeb123 added 5 - Ready to Merge Testing and reviews complete, ready to merge and removed 4 - Needs Review Waiting for reviewer to review or respond labels Jul 10, 2026
@mhaseeb123

Copy link
Copy Markdown
Contributor Author

/merge

@rapids-bot
rapids-bot Bot merged commit 3aa6568 into NVIDIA:main Jul 11, 2026
261 of 263 checks passed
@mhaseeb123
mhaseeb123 deleted the fea/select-pq-cols-by-field-id branch July 13, 2026 16:05
rapids-bot Bot pushed a commit that referenced this pull request Jul 20, 2026
Follow up ##22955

This PR adds python bindings for Parquet column selection by field ID

Authors:
  - Muhammad Haseeb (https://github.com/mhaseeb123)

Approvers:
  - Matthew Murray (https://github.com/Matt711)

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

Labels

5 - Ready to Merge Testing and reviews complete, ready to merge cuIO cuIO issue feature request New feature or request libcudf Affects libcudf (C++/CUDA) code. non-breaking Non-breaking change Velox Functionality that helps Velox-cudf

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants