Repository navigation
fix(query): manifest pruning only on the time-partition column (port upstream #1828) - #63
Merged
Merged
Conversation
`extract_primary_filter()` incorrectly prunes manifests based on time filters even if they are not on `P_TIMESTAMP` (cherry picked from commit af11d36)
Follow-up to the upstream port of parseablehq#1828. Upstream only accepts bounds on p_timestamp, which is wrong for streams with a custom time_partition: - Their manifests, staging prefixes and listing prefixes are bucketed by the time_partition column (catalog::calculate_time_bound, Event::process), so bounds on it are the only ones that can prune. Upstream dropped these, so the time range the query handler injects on the partition column no longer pruned anything. - p_timestamp there is ingestion time, unrelated to the event-time buckets, so pruning by it can drop back-filled rows. Accept a bound only when its left side is the column manifests are keyed by: time_partition if set, otherwise p_timestamp. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 24, 2026
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.
Upstream origin
Ports parseablehq/parseable@af11d36e ("fix: manifest pruning (parseablehq#1828)"). It was cherry-picked with
-x, so the first commit keeps upstream's authorship.Bug:
PartialTimeFilter::try_from_exprturned any comparison against a timestamp literal into a manifest-pruning bound. A filter on another column, e.g.created_at >= '2023-12-17T00:00:00', pruned day manifests as if it were a bound onp_timestampand silently dropped matching rows from those days.Deviation from upstream
Upstream accepts the bound only if the expression references
p_timestamp. That is wrong for streams with a customtime_partition:catalog::calculate_time_bound/create_manifestgroup files into day manifests by the min oftime_partition(falling back top_timestamp).Event::processkeys staging files by the parsed partition timestamp.query::transformandenterprise::create_time_filterinject the query's time range on that column. Under upstream's check, that injected range no longer prunes anything, so every query on a time-partitioned stream reads every manifest.p_timestampis ingestion time on those streams. It has no relation to the event-time buckets. Upstream still prunes by it, sop_timestamp >= Tdrops back-filled events whose event time is earlier than their ingestion time. That is the same class of bug the upstream commit fixes.This PR adds a second commit that accepts a bound only when its left side is a bare column equal to
time_partition.unwrap_or(p_timestamp). That is the column manifests are actually keyed by.Requiring a bare column, instead of upstream's "any column ref" check, keeps pruning for the common query shapes. I checked these with DataFusion 55's optimizer: string literals,
now() - interval,to_timestamp(...),BETWEEN, and simple casts. All of them becomecol <op> TimestampMillisecond(..)with no cast left on the column. Function-wrapped columns such asdate_trunc('day', p_timestamp) = Xdo lose their own bound. The old code pruned on them, but that was unsafe in general:date_trunc('month', c) < '2023-12-16'matches rows up to 2023-12-31, yet it pruned everything from 12-16 on. The request range that is always injected still prunes these queries.time_partitioncannot be changed after a stream is created (only its limit can), so the column manifests are keyed by stays the same.Ramifications reviewed
extract_primary_filtercallers. These areStandardTableProvider::scan,enterprise::utils::fetch_parquet_file_paths(filters on the partition column, so now prunes correctly) andutils::arrow::flight::send_to_ingester(p_timestampwithNone, unchanged).is_within_staging_window/return_listing_time_filters. Dropping a bound only widens the result (staging gets included, listing covers more), which is safe.get_manifest_list/ counts buildPartialTimeFilters directly, so they're unaffected.query::transformalways injects the request range on the partition column.table_contains_any_time_filtersinspects the unoptimized plan fromcreate_logical_plan. DataFusion's SQL planner buildsTableScans with emptyfiltersthere, so the function always returns false; it is effectively dead code. As a result, HTTP and alert queries on time-partitioned streams always carry an event-time bound and keep pruning, including queries whose only filter is onp_timestamp. An earlier version of this description said the opposite; that was wrong.transform: ap_timestamp-only scan on a time-partitioned stream now gets no manifest pruning. It also stops sending the scan to the legacy listing path (pre-manifest data), becauseis_overlapping_queryneeds a lower bound to do that. This is pre-existing behaviour for any scan without a lower bound, and none of the HTTP or alert paths reach it.Pre-existing issues found in review (not fixed here)
Five independent review agents (3× Opus 5.5, 2× GPT-6.1 Sol) reviewed this PR. They found no correctness bug introduced by it. They did confirm that manifests, staging files, listing prefixes and hot tier are all keyed by 1-minute slots of the partition column, so a file never spans two days. Pre-existing issues they found:
Exactpushdown is not enforced everywhere.expr_in_boundaryreports any minute-aligned timestamp comparison asExact, on any column.collect_from_snapshotthen truncates the file list forLIMITbynum_rows, assuming every row matches.WHERE created_at >= TIMESTAMP '…:00' LIMIT 100can return fewer than 100 rows.MemTableand the legacyListingTabledon't applyExactfilters, so unflushed rows can come back unfiltered.extract_timestamp_bound: fix(query): don't panic on out-of-range timestamp literals #64.is_within_staging_window,send_to_ingester).p_timestampon time-partitioned streams.get_bin_densitycounts a file in two adjacent bins when its time falls on a bin edge.Tests
New unit tests in
stream_schema_provider::tests:p_timestampfilter prunes, both bare and table-qualified!=comparison does not prunetime_partitioncolumn prunes (timestamp and string literals)p_timestampand unrelated columns do not prune a time-partitioned stream's manifestsWith the column check disabled, the three "does not prune" tests fail, as expected.
cargo fmt --check: cleancargo test(P_BUILD_NO_UI=1): 854 passed, 0 failedcargo clippy --all-targets: no warnings in the changed file.-D warningsfails on 21 warnings that are already onmain, in files this PR doesn't touch.🤖 Generated with Claude Code