Skip to content

Parquet row filter struct access tree#23217

Open
SubhamSinghal wants to merge 4 commits into
apache:mainfrom
SubhamSinghal:parquet-row-filter-struct-access-tree
Open

Parquet row filter struct access tree#23217
SubhamSinghal wants to merge 4 commits into
apache:mainfrom
SubhamSinghal:parquet-row-filter-struct-access-tree

Conversation

@SubhamSinghal

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Rationale for this change

Are these changes tested?

Yes

Are there any user-facing changes?

No. This is an internal refactor

@github-actions github-actions Bot added the datasource Changes to the datasource crate label Jun 27, 2026

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

@SubhamSinghal
Thanks!
This LGTM. I have one small non-blocking suggestion for future cleanup.


let pruned_data_type = prune_struct_type(field.data_type(), field_paths);
let pruned_data_type = prune_struct_type(field.data_type(), node);
Arc::new(Field::new(

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.

Small future cleanup suggestion: if this internal projected schema is expected to preserve wrapper field metadata for pruned structs, it might be worth copying the metadata when rebuilding the root and intermediate Fields here and in prune_struct_type.

I don't think this PR introduces the issue, and it probably is not scan output visible since the terminal fields are cloned and the decoder projection can restore the requested output schema. This is mostly a future proofing and test coverage suggestion.

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.

@SubhamSinghal SubhamSinghal force-pushed the parquet-row-filter-struct-access-tree branch from cfdce5a to 928c566 Compare June 30, 2026 16:14

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

@SubhamSinghal
Thanks for the iteration.

Looks 👍 to me

@alamb alamb requested a review from adriangb July 2, 2026 21:25

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

This is super exciting! I'd love to run some benchmarks on this to show the improvements. We have two struct benchamrks, but I think neither cover this case:

  • benches/parquet_struct_query.rs — filters are all single struct-field predicates (WHERE s['id'] = 5, length(s['value']) > 100). Flat schema s{id, value}. Never exercises multi-conjunct shared-prefix planning.
  • benches/parquet_struct_projection.rs — has a nice nested schema (s{inner{large_string, small_int}, extra_string}) but is projection-only: zero WHERE clauses. Doesn't touch the row-filter path at all.

Could you add a new benchmark, ideally as it's own PR so that we can run it on this PR after rebasing, that shows the perf improvement?

Comment on lines +971 to +974
Arc::new(
Field::new(field.name(), pruned_data_type, field.is_nullable())
.with_metadata(field.metadata().clone()),
)

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.

The .with_metadata() seems like a real bug fix - could we isolate that into it's own PR if possible?

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.

sure, will raise a new PR with this fix.

let prefix = std::iter::once(root_name.as_str())
.chain(access.field_path.iter().map(|p| p.as_str()))
.collect::<Vec<_>>();
let mut leaf_indices = Vec::with_capacity(access_tree.roots.len() * 2);

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.

Is this pre-allocation just a guess, or is it based on something? If it' just a guess, could you help understand how you arrived at this? I tend to prefer to not pre-allocate if there's not a clear benchmark or provable reason to do so. I've been bitten before by excessive pre-allocation causing high memory use.

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.

Dropped in the latest push. The * 2 was a guess — my rough mental model was "each accessed root contributes ~2 selected leaves on average," but there was no benchmark behind it

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

Labels

datasource Changes to the datasource crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor: Build a reusable struct-access path tree for Parquet row-filter planning

3 participants