Skip to content

fix(layout): apply struct nulls before evaluating field expressions - #10402

Closed
robert3005 wants to merge 1 commit into
developfrom
rk/struct-filter-parent-validity
Closed

robert3005 wants to merge 1 commit into
developfrom
rk/struct-filter-parent-validity

Conversation

@robert3005

Copy link
Copy Markdown
Contributor

Summary

A nullable struct's fields store values even for rows where the struct itself is null. When reading a file, StructReader evaluated field expressions without taking that into account:

  • Filters were pushed straight into the field readers, which know nothing about the parent's validity. So x.value >= 0 matched rows where x is null. This is the bug behind the failing struct tests in fix(python): translate Polars struct field access #10194: a file scan returned 2 rows where Polars, and Vortex's own in-memory evaluation, return 1.
  • Projections applied the struct's nulls to the result, not the input. That is right for expressions that turn a null input into a null output, but wrong for others: is_null(x.value) gave null instead of true on rows where x is null.
  • Pruning could use field statistics, which include the hidden values, to rule out rows that should match.

Changes

All changes are in vortex-layout/src/layouts/struct_/reader.rs, and only affect structs that are nullable (have a validity child):

  • Filter and projection now read each field, apply the struct's validity, and only then evaluate the field-scoped expression in memory. This handles single-field and multi-field expressions alike.
  • A struct-valued (pack/merge) projection still masks each output field with the struct's validity, as before. That is how a nested nullable struct stays nullable when projected through its parent; vortex-file's write_nullable_nested_struct covers it.
  • Statistics pruning is skipped for nullable structs.

Non-nullable structs, including every file's root struct, keep the existing pushdown path unchanged. The cost is that predicates on fields of nullable nested structs are no longer pushed into the field readers and their statistics are no longer used for pruning. They are evaluated in memory after reading the field. Pushing down predicates that are provably false on null input would be a possible follow-up.

Tests

Added rstest cases to struct_::reader::tests. The fixture is a nullable struct whose null row 0 still stores a = 7:

  • Filters a > 1, is_null(a), and the two-field a > 5 OR b > 4 respect the parent's nulls.
  • The projection is_null(a) returns [true, false, false].

All four fail on develop and pass with this change.

Checks run

🤖 Generated with Claude Code

https://claude.ai/code/session_01E92wUoS3LaqWVJmmS7vKVq


Generated by Claude Code

A nullable struct's fields store values even for rows where the struct is
null. StructReader pushed filters straight down to the field readers, which
know nothing about the parent's validity, so `x.value >= 0` matched rows
where `x` itself was null. Projection masked the result afterwards instead
of the input, which gave null instead of true for expressions such as
`is_null(x.value)`, and pruning could use field statistics that include the
hidden values.

For nullable structs, read each field, apply the struct's validity, and
only then evaluate the field-scoped expression, for both filters and
projections. Skip statistics pruning for nullable structs. Non-nullable
structs, including every file's root, keep full pushdown.

Signed-off-by: Claude <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01E92wUoS3LaqWVJmmS7vKVq
@codspeed

codspeed Bot commented Oct 8, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 12.51%

⚠️ Unknown Walltime execution environment detected

Using the Walltime instrument on standard Hosted Runners will lead to inconsistent data.

For the most accurate results, we recommend using CodSpeed Macro Runners: bare-metal machines fine-tuned for performance measurement consistency.

⚡ 1 improved benchmark
✅ 2174 untouched benchmarks
⏩ 534 skipped benchmarks1

Performance Changes

Mode Benchmark BASE HEAD Efficiency
⚡ WallTime bitpack_blocked_compress_avx2 7.6 µs 6.8 µs +12.51%

Tip

Curious why performance improved? Comment @codspeedbot explain why performance improved on this PR, or directly use the CodSpeed MCP with your agent.


Comparing rk/struct-filter-parent-validity (2133e00) with develop (c93ffa3)

Open in CodSpeed

Footnotes

  1. 534 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

@robert3005
robert3005 marked this pull request as draft October 8, 2026 22:17
@robert3005

Copy link
Copy Markdown
Contributor Author

will wait for @myrrc pr

@robert3005 robert3005 closed this Oct 9, 2026
@robert3005
robert3005 deleted the rk/struct-filter-parent-validity branch October 9, 2026 13:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog/fix A bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants