Repository navigation
fix(query): enforce Exact filters on staging/listing scans and only report Exact for time-partition bounds - #65
Merged
Conversation
…ng scans
StandardTableProvider reports any `<expr> {<,<=,>,>=} <minute-aligned
timestamp literal>` filter as Exact, for any column, so DataFusion drops
the FilterExec above the scan. Parquet scans enforce these through the
source predicate with pushdown_filters, but MemTable (in-memory staging
records) and ListingTable (legacy listing) ignore pushed filters, so their
rows were returned unfiltered, e.g. `WHERE created_at >= '2020-01-01'` or
a minute-aligned p_timestamp bound inside the staging window. The listing
scan also applied LIMIT before any filtering.
Scan both through scan_enforcing_exact_filters, which scans the projected
plus filter columns, applies the Exact conjunction in a FilterExec, then
projects and applies the limit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
expr_in_boundary accepted a minute-aligned timestamp bound on any column, so supports_filters_pushdown reported e.g. `created_at >= '...T00:00:00'` as Exact. DataFusion then drops the FilterExec and pushes LIMIT into the scan, and collect_from_snapshot truncates the file list once the files' row counts reach the limit, assuming every row of a surviving file matches. For any column other than the one data files are partitioned by, that undercounts: `... LIMIT 100` could return 5 rows. Report Exact only when the filter's left side is the stream's time partition column (p_timestamp unless configured): data files hold one minute of that column, so a minute-aligned bound keeps or prunes each file whole. StandardTableProvider now carries the stream's time partition for this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
StandardTableProvider::supports_filters_pushdownreports a filter asExactwheneverexpr_in_boundaryaccepts it. That means any<col> {<,<=,>,>=} <minute-aligned timestamp literal>, on any column. DataFusion then removes theFilterExecabove the scan, and the scan is trusted to enforce the filter. Two places broke that trust:Unfiltered rows from in-memory staging and legacy listing. Parquet scans enforce
Exactfilters through the source predicate (exact_source_filterswithpushdown_filters = true). But in DataFusion 55,MemTable::scan(in-memory staging records) ignoresfilters, andListingTable::scan(legacy pre-manifest listing) never installs a predicate for them. Rows that should be filtered out came back. Reproduced:LIMITreturns too few rows. With noFilterExecleft, DataFusion pushesLIMITinto the scan.collect_from_snapshotthen truncates the file list once the files'num_rowsreach the limit, assuming every row of a surviving file matches. That holds only for the time-partition column, because each file holds one minute of it. ForWHERE created_at >= TIMESTAMP '2026-10-01 00:00:00' LIMIT 100, the list can be cut to a file with only 5 matching rows. The same happens forp_timestampbounds on a stream with a customtime_partition, whose files are bucketed by event time.Fix
scan_enforcing_exact_filtersis now used for theMemTablestaging scan and the legacyListingTablescan. When a filter is exact-eligible, it scans the projected columns plus the predicate's columns with no limit. It then applies the conjunction in aFilterExec, which projects back to the requested columns and applies the limit after filtering. With no such filter, the scan is unchanged.filter_pushdown: a filter isExactonly if its left side is a bare column equal to the stream's time-partition column (time_partition, elsep_timestamp) andexpr_in_boundaryholds. Everything else isInexact, so DataFusion keeps theFilterExecand does not pushLIMITdown.StandardTableProvidernow carries the stream'stime_partition, taken from the same stream metadata thatquery::transformuses.exact_source_filters(the parquet source predicate and the staging/listing enforcement) still uses the broaderexpr_in_boundaryset. It is a superset of theExactfilters, so everyExactfilter is enforced, and the extra ones only add parquet pruning under a retainedFilterExec.Found while reviewing #63; it is independent of that PR. The fix for (1) came from a review subagent (Opus 5.5) that reproduced the leak. The fix for (2) came from a separate review finding (Opus 5.5), checked against
collect_from_snapshot.Tests
stream_schema_provider/exact_filter_tests.rsexercises the realreversed_mem_tableand a realListingTableBuildertable over a local parquet file. It covers non-time columns, time bounds, mixed exact/inexact filters, projections that exclude filter columns,count(*), andLIMIT.only_minute_aligned_partition_column_bounds_are_exact: unit cases forfilter_pushdown, includingp_timestampon a time-partitioned stream.limit_is_pushed_into_scan_only_for_exact_partition_bounds: a planned SQL... LIMIT 5getslimit=5on the scan for ap_timestampbound. For acreated_atbound it gets aFilterExecand no scan limit instead.guarded_hot_tier_source_preserves_provider_identitynow also checkstime_partition.The new tests fail without the corresponding change.
cargo fmt --checkis clean.cargo testpasses: 853 passed, 0 failed.cargo clippy --all-targetsshows no warnings in the changed files (-D warningsis already red onmainfrom unrelated files).Notes
FilterExec. That is a cheap row filter and was always required for correct results.LIMITwith such filters no longer truncates the remote file list early.stream_schema_provider.rsnear fix(query): manifest pruning only on the time-partition column (port upstream #1828) #63 and fix(query): don't panic on out-of-range timestamp literals #64, but in different hunks. Expect trivial or no conflicts in any merge order.🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need.