Skip to content

transform: detect index lookups from the filter as written - #38717

Draft
frankmcsherry wants to merge 2 commits into
MaterializeInc:mainfrom
frankmcsherry:literal-constraints-key-bounds
Draft

transform: detect index lookups from the filter as written#38717
frankmcsherry wants to merge 2 commits into
MaterializeInc:mainfrom
frankmcsherry:literal-constraints-key-bounds

Conversation

@frankmcsherry

Copy link
Copy Markdown
Contributor

LiteralConstraints decides whether a filter over an indexed relation can be served by looking up literal key values in the index. Today that decision is made on a disjunctive normal form of the filter, prepared first and undone afterwards, and the preparation stops at a size guard. Past the guard the transform finds no lookup: a long IN list combined with another disjunction, a multi-column IN list, or an IN list on one indexed column next to an unrelated list on another, all plan as full scans (database-issues#1924).

This PR makes detection read the filter as written. key_bounds.rs is an exact analysis of what a predicate implies about a list of key expressions: a disjunction of boxes, each bounding every key field to a set of literals or leaving it unbounded; AND intersects boxes pairwise, OR unions them. match_index asks it whether the predicates pin every field of a candidate key and for which values. Nothing is approximated: a predicate the analysis cannot read leaves the key unbounded, and the only limit is on the number of values enumerated for a lookup, tied to FOLD_CONSTANTS_LIMIT, the size of constant collection a plan may carry.

Everything else in the transform is unchanged. Preparation, dropping contradictory disjuncts, undo_preparation, and the DNF-based removal of constraints from the filter are the same code and produce the same expressions. One consequence is stated in the module doc: detection now has more reach than removal, so a filter can yield a lookup whose constraints removal cannot take out, and the filter then stays whole above the lookup. Removal used to assert that every disjunct carried a key constraint; that held only because the old detection never let such a shape through (a = NULL OR a = 2 now looks up (2)), and it now declines instead.

What changes in plans

Against main's literal_constraints.slt, one plan differs: WHERE a = NULL OR a = 2 becomes a lookup of (2) with the filter kept. The new tests in that file are the shapes the transform previously scanned: IN lists past the old size guard, pair lists on a two-column index, a covered list alongside uncovered ones, a cross product of two covered lists, and two disjoint pair lists (empty result, asserted on rows). The datadriven transform specs are unchanged.

Relation to #38515

The analysis is that PR's; the box budget, widening, the widened bit, the contradiction-pruning pass and the removal by per-predicate exactness are deliberately not carried over, so that the transform's simplification and removal behaviour is untouched and the PR has no thresholds or heuristics beyond the existing constant-size limit. Moving contradiction simplification into the fixpoint loop, and deleting the DNF machinery, are follow-ups.

frankmcsherry and others added 2 commits September 8, 2026 13:45
LiteralConstraints decided which index to use, and with which values, on a
disjunctive normal form of the filter, prepared first and undone afterwards,
and the preparation stops at a size guard. Past the guard no lookup was
found. Detection now reads the filter as it is: KeyBounds is an exact
analysis of the key values a predicate admits, as a disjunction of boxes
that AND intersects and OR unions, with no approximation and no budget. Its
one limit is on the values enumerated for a lookup, tied to the constant
size limit a plan already observes.

Preparation, contradictory-disjunct removal, undo_preparation and the
DNF-based constraint removal are unchanged and produce the same
expressions. Detection therefore has more reach than removal, and a filter
can yield a lookup whose constraints removal cannot take out; the filter
then stays whole above the lookup. Removal asserted that every disjunct
carries a key constraint, which held only because the old detection never
let such a shape through; it now declines instead.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Long IN lists, pair lists on a two-column index, a covered list alongside
uncovered ones, a cross product of two covered lists, and two disjoint pair
lists (empty, asserted on rows). One existing plan changes: a = NULL OR
a = 2 now looks up (2) with the filter kept.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@ggevay

ggevay commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

(Triggered Nightly's optimizer-relevant subset: https://buildkite.com/materialize/nightly/builds/18279)

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants