diff --git a/src/transform/src/lib.rs b/src/transform/src/lib.rs index 712913a035ce8..95d6ab794fe68 100644 --- a/src/transform/src/lib.rs +++ b/src/transform/src/lib.rs @@ -134,7 +134,7 @@ pub struct TransformCtx<'a> { pub last_hash: BTreeMap, } -const FOLD_CONSTANTS_LIMIT: usize = 10000; +pub(crate) const FOLD_CONSTANTS_LIMIT: usize = 10000; impl<'a> TransformCtx<'a> { /// Generates a [`TransformCtx`] instance for the local MIR optimization diff --git a/src/transform/src/literal_constraints.rs b/src/transform/src/literal_constraints.rs index 003bc005ea772..fb66af6e0684d 100644 --- a/src/transform/src/literal_constraints.rs +++ b/src/transform/src/literal_constraints.rs @@ -12,6 +12,13 @@ //! the Get has a matching index. Convert these to `IndexedFilter` joins, which is a semi-join with //! a constant collection. //! +//! Detecting which index to use, and with which values, reads the filter as written (see +//! [`key_bounds`]). Removing the constraints the lookup enforces from the filter, and dropping +//! contradictory disjuncts, work on a disjunctive normal form of the filter that is prepared +//! first and undone afterwards. The two therefore have different reach: a filter can yield a +//! lookup whose constraints removal cannot take out, in which case the filter stays whole above +//! the lookup. +//! //! E.g.: Logically, we go from something like //! `SELECT f1, f2, f3 FROM t WHERE t.f1 = lit1 AND t.f2 = lit2` //! to @@ -36,6 +43,10 @@ use crate::TransformCtx; use crate::canonicalize_mfp::CanonicalizeMfp; use crate::notice::IndexTooWideForLiteralConstraints; +mod key_bounds; + +use key_bounds::KeyBounds; + /// Convert literal constraints into `IndexedFilter` joins. #[derive(Debug)] pub struct LiteralConstraints; @@ -78,7 +89,9 @@ impl LiteralConstraints { { let orig_mfp = mfp.clone(); - // Preparation for the literal constraints detection. + // Preparation for removing literal constraints and contradictory disjuncts, which + // work on a disjunctive normal form. Detection reads the prepared MFP too, but does + // not depend on the form. Self::inline_literal_constraints(&mut mfp); Self::list_of_predicates_to_and_of_predicates(&mut mfp); Self::distribute_and_over_or(&mut mfp)?; @@ -219,8 +232,9 @@ impl LiteralConstraints { /// For example, if there is an index on `(f1, f2)`, and the Filter is /// `(f1 = 3 AND f2 = 5) OR (f1 = 7 AND f2 = 9)`, it returns `Some([f1, f2], [[3,5], [7,9]])`. /// - /// We can use an index if each argument of the OR includes a literal constraint on each of the - /// key fields of the index. Extra predicates inside the OR arguments are ok. + /// An index is usable when the predicates, read as written, pin every field of its key to + /// literals; the values to look up are exactly the key values the predicates admit. Predicates + /// that say nothing about the key are fine and stay in the filter. /// /// Returns (idx_id, idx_key, values to lookup in the index). fn detect_literal_constraints( @@ -229,66 +243,36 @@ impl LiteralConstraints { transform_ctx: &mut TransformCtx, ) -> Option<(GlobalId, Vec, Vec)> { // Checks whether an index with the specified key can be used to speed up the given filter. - // See comment of `IndexMatch`. - fn match_index(key: &[MirScalarExpr], or_args: &Vec) -> IndexMatch { + // See comment of `IndexMatch`. Reads the predicates as they are, so a shape the DNF + // preparation could not fully distribute is still examined: the key is usable when the + // predicates pin every one of its fields, and the lookup values are exactly the key + // values they admit. + fn match_index(key: &[MirScalarExpr], mfp: &MapFilterProject) -> IndexMatch { if key.is_empty() { // Nothing to do with an index that has an empty key. return IndexMatch::UnusableNoSubset; } if !key.iter().all_unique() { - // This is a weird index. Why does it have duplicate key expressions? + // We could handle this, but it would need some care, and such indexes are odd. return IndexMatch::UnusableNoSubset; } - let mut literal_values = Vec::new(); - let mut inv_cast_any = false; - // This starts with all key fields of the index. - // At the end, it will contain a subset S of index key fields such that if the index had - // only S as its key, then the index would be usable. - let mut usable_key_fields = key.iter().collect::>(); - let mut usable = true; - for or_arg in or_args { - let mut row = Row::default(); - let mut packer = row.packer(); - for key_field in key { - let and_args = or_arg.and_or_args(And.into()); - // Let's find a constraint for this key field - if let Some((literal, inv_cast)) = and_args - .iter() - .find_map(|and_arg| and_arg.expr_eq_literal(key_field)) - { - // (Note that the above find_map can find only 0 or 1 result, because - // of `remove_impossible_or_args`.) - packer.push(literal.unpack_first()); - inv_cast_any |= inv_cast; - } else { - // There is an `or_arg` where we didn't find a constraint for a key field, - // so the index is unusable. Throw out the field from the usable fields. - usable = false; - usable_key_fields.remove(key_field); - if usable_key_fields.is_empty() { - return IndexMatch::UnusableNoSubset; - } - } + let bounds = KeyBounds::conjunction(mfp.predicates.iter().map(|(_, p)| p), key); + if bounds.bounds_every_field() { + match bounds.lookup_values() { + Some(values) => IndexMatch::Usable(values, bounds.inv_cast), + // Too many values to look up; a scan is the better plan. + None => IndexMatch::UnusableNoSubset, } - literal_values.push(row); - } - if usable { - // We should deduplicate, because a constraint can be duplicated by - // `distribute_and_over_or`. For example: `IN ('l1', 'l2') AND (a > 0 OR a < 5)`: - // the 2 args of the OR will cause the IN constraints to be duplicated. This doesn't - // alter the meaning of the expression when evaluated as a filter, but if we extract - // those literals 2 times into `literal_values` then the Peek code will look up - // those keys from the index 2 times, leading to duplicate results. - literal_values.sort(); - literal_values.dedup(); - IndexMatch::Usable(literal_values, inv_cast_any) } else { - if usable_key_fields.is_empty() { + let subset = bounds + .bounded_fields() + .into_iter() + .map(|i| key[i].clone()) + .collect_vec(); + if subset.is_empty() { IndexMatch::UnusableNoSubset } else { - IndexMatch::UnusableTooWide( - usable_key_fields.into_iter().cloned().collect_vec(), - ) + IndexMatch::UnusableTooWide(subset) } } } @@ -298,7 +282,7 @@ impl LiteralConstraints { let index_matches = transform_ctx .indexes .indexes_on(get_id) - .map(|(index_id, key)| (index_id, key.to_owned(), match_index(key, &or_args))) + .map(|(index_id, key)| (index_id, key.to_owned(), match_index(key, mfp))) .collect_vec(); let result = index_matches @@ -325,9 +309,11 @@ impl LiteralConstraints { assert!(!usable_subset.is_empty()); // Determine literal values that we would get if the index was on // `usable_subset`. - let literal_values = match match_index(&usable_subset, &or_args) { + let literal_values = match match_index(&usable_subset, mfp) { IndexMatch::Usable(literal_vals, _) => literal_vals, - _ => unreachable!(), // `usable_subset` would make the index usable. + // The subset is bounded, so this is the value count exceeding + // the lookup limit: nothing to recommend looking up. + _ => return, }; // Let's come up with a recommendation for what columns to index: @@ -403,16 +389,20 @@ impl LiteralConstraints { // After removing the literal constraints we have // `c OR (d AND e)` let mut constraints_to_residual_sets = BTreeMap::new(); - or_args.iter().for_each(|or_arg| { + for or_arg in or_args.iter() { let and_args = or_arg.and_or_args(And.into()); let (mut constraints, mut residual): (Vec<_>, Vec<_>) = and_args.iter().cloned().partition(|and_arg| { key.iter() .any(|key_field| matches!(and_arg.expr_eq_literal(key_field), Some(..))) }); - // In every or_arg there has to be some literal constraints, otherwise - // `detect_literal_constraints` would have returned None. - assert!(constraints.len() >= 1); + // Detection reads the predicates as a whole, so it can find a usable key while an + // individual disjunct pins nothing, for example a disjunct that is literally + // `null`. This removal reasons disjunct by disjunct and has nothing to say about + // such a shape, so the filter stays as it is; the lookup is still taken. + if constraints.is_empty() { + return false; + } // `remove_impossible_or_args` made sure that inside each or_arg, each // expression can be literal constrained only once. So if we find one of the // key fields being literal constrained, then it's definitely that literal @@ -428,7 +418,7 @@ impl LiteralConstraints { .entry(constraints) .or_insert_with(BTreeSet::new); entry.insert(residual); - }); + } let residual_sets = constraints_to_residual_sets .into_iter() .map(|(_constraints, residual_set)| residual_set) @@ -750,10 +740,10 @@ impl LiteralConstraints { /// Whether an index is usable to speed up a Filter with literal constraints. #[derive(Clone)] enum IndexMatch { - /// The index is usable, that is, each OR argument constrains each key field. + /// The index is usable, that is, the predicates pin every key field. /// - /// The `Vec` has the constraining literal values, where each Row corresponds to one OR - /// argument, and each value in the Row corresponds to one key field. + /// The `Vec` has the key values the predicates admit, deduplicated, each value in a Row + /// corresponding to one key field. /// /// The `bool` indicates whether we needed to inverse cast equalities to match them up with key /// fields. The inverse cast enables index usage when an implicit cast is wrapping a key field. diff --git a/src/transform/src/literal_constraints/key_bounds.rs b/src/transform/src/literal_constraints/key_bounds.rs new file mode 100644 index 0000000000000..6710efc82ebc9 --- /dev/null +++ b/src/transform/src/literal_constraints/key_bounds.rs @@ -0,0 +1,427 @@ +// Copyright Materialize, Inc. and contributors. All rights reserved. +// +// Use of this software is governed by the Business Source License +// included in the LICENSE file. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0. + +//! What a filter predicate implies about the values of an index key. +//! +//! The question this module answers is always asked about a specific list of key +//! expressions: "given that this predicate holds, which values can these expressions +//! take?" Everything in the predicate that says nothing about those expressions is +//! invisible to the answer, and costs a single visit of the node. +//! +//! The answer is a [`KeyBounds`]: a disjunction of conjunctive boxes, where a box bounds +//! each key field independently. Two shapes motivate the representation. +//! +//! * `a IN (1, 2) AND b IN (3, 4)` is one box, `{a: {1,2}, b: {3,4}}`. The four key values +//! are the cross product, formed over datums rather than over expression nodes. +//! * `(a, b) IN ((1, 3), (2, 4))` is two boxes, `{a: {1}, b: {3}}` and `{a: {2}, b: {4}}`. +//! Collapsing it to one box would admit `(1, 4)`, which the predicate rejects. +//! +//! `AND` intersects boxes pairwise and `OR` concatenates them, so the box count is bounded +//! by the number of distinct key tuples the predicate admits. It does not grow with +//! disjunctions over columns that the index does not cover. +//! +//! The analysis is exact: it never approximates, and a predicate it cannot read leaves the +//! key unbounded rather than guessed at. Its one limit is on the number of key values it +//! will enumerate for a lookup, which is a limit on the size of the constant collection a +//! plan may carry, the same one constant folding observes. +//! +//! NOTE: Literal values are compared as `Row`s, and `RowRef`'s `Ord` orders by the packed +//! byte representation rather than by `Datum::cmp`. Packing canonicalizes numerics, but +//! writes a float's raw bits, so `f = 0.0 AND f = '-0'::float8` intersects to the empty set +//! even though the two literals are equal in SQL (SQL-452). Byte identity is therefore the +//! definition of literal equality for everything in this module. + +use std::collections::btree_map::Entry; +use std::collections::{BTreeMap, BTreeSet}; + +use itertools::Itertools; +use mz_expr::MirScalarExpr; +use mz_expr::VariadicFunc; +use mz_expr::func::variadic::{And, Or}; +use mz_repr::Row; + +use crate::FOLD_CONSTANTS_LIMIT; + +/// The values a single key field may take. `None` means the predicate does not bound it. +/// +/// `Some` is never empty: a field bounded to no values makes its whole box unsatisfiable, +/// and such boxes are dropped rather than stored. +type FieldBound = Option>; + +/// One conjunctive bound on all key fields, entry `i` bounding key field `i`. +type KeyBox = Vec; + +/// What a predicate implies about a list of key expressions. +#[derive(Clone, Debug)] +pub struct KeyBounds { + /// The key can only take a value that falls inside at least one of these boxes. + /// + /// An empty list means the predicate is never satisfied. + boxes: Vec, + /// Whether matching a key field required inverting a cast on it. Reported so that the + /// caller can prefer an index whose key needs no inversion. + pub inv_cast: bool, + /// The number of key fields, which is the width of every box. + arity: usize, +} + +impl KeyBounds { + /// The bound of a predicate that says nothing about the key: every key value is + /// admissible. It is the identity of `and`. + fn top(arity: usize) -> Self { + KeyBounds { + boxes: vec![vec![None; arity]], + inv_cast: false, + arity, + } + } + + /// The bound of a predicate that is never satisfied. + fn bottom(arity: usize) -> Self { + KeyBounds { + boxes: Vec::new(), + inv_cast: false, + arity, + } + } + + /// Extracts what a conjunction of predicates jointly says about `key`. + pub fn conjunction<'a>( + predicates: impl IntoIterator, + key: &[MirScalarExpr], + ) -> Self { + predicates + .into_iter() + .map(|p| Self::extract(p, key)) + .fold(Self::top(key.len()), Self::and) + } + + /// Extracts what `predicate` says about `key`. + /// + /// Linear in the size of `predicate`, apart from the box arithmetic, which is bounded by + /// the number of distinct key tuples the predicate admits. + pub fn extract(predicate: &MirScalarExpr, key: &[MirScalarExpr]) -> Self { + mz_ore::stack::maybe_grow(|| match predicate { + MirScalarExpr::CallVariadic { + func: VariadicFunc::And(And), + exprs, + } => exprs + .iter() + .map(|e| Self::extract(e, key)) + .fold(Self::top(key.len()), Self::and), + MirScalarExpr::CallVariadic { + func: VariadicFunc::Or(Or), + exprs, + } => Self::disjunction(exprs.iter().map(|e| Self::extract(e, key)), key.len()), + _ => Self::leaf(predicate, key), + }) + } + + /// Extracts what a predicate with no `AND`/`OR` at its root says about `key`. + fn leaf(predicate: &MirScalarExpr, key: &[MirScalarExpr]) -> Self { + // NOTE: `null` counts as never satisfied because these are filter predicates, where + // a row that evaluates to `null` is dropped just as a `false` one is. A literal + // *error* is not: that row errors out rather than being filtered away, so it stays + // opaque. + if predicate.is_literal_false() || predicate.is_literal_null() { + return Self::bottom(key.len()); + } + // A literal equality whose cast cannot be inverted without erroring is never true. + if predicate.impossible_literal_equality_because_types() { + return Self::bottom(key.len()); + } + let mut result = Self::top(key.len()); + // A single leaf can pin more than one key field, if the key holds both an + // expression and a cast of it. Recording all of them is sound and no less precise. + for (i, key_field) in key.iter().enumerate() { + if let Some((literal, inv_cast)) = predicate.expr_eq_literal(key_field) { + result.boxes[0][i] = Some(BTreeSet::from([literal])); + result.inv_cast |= inv_cast; + } + } + result + } + + /// The bound implied by both `self` and `other` holding: the pairwise intersection of + /// their boxes. + fn and(self, other: Self) -> Self { + debug_assert_eq!(self.arity, other.arity); + Self { + boxes: Self::product(&self.boxes, &other.boxes, self.arity), + inv_cast: self.inv_cast || other.inv_cast, + arity: self.arity, + } + } + + /// The bound implied by any one of `args` holding: the union of their boxes. + /// + /// NOTE: Taken n-ary rather than folded pairwise. Folding would normalize the + /// accumulator once per argument, which is quadratic in the width of an `IN` list, and + /// an `IN` list is the case that matters most here. + fn disjunction(args: impl IntoIterator, arity: usize) -> Self { + let mut boxes = Vec::new(); + // A disjunction with no arguments is `false`, which `bottom` already describes. + let mut result = Self::bottom(arity); + for arg in args { + debug_assert_eq!(arg.arity, arity); + result.inv_cast |= arg.inv_cast; + boxes.extend(arg.boxes); + } + result.boxes = Self::normalize(boxes, arity); + result + } + + /// Pairwise intersection of two box lists, dropping boxes that come out unsatisfiable. + fn product(left: &[KeyBox], right: &[KeyBox], arity: usize) -> Vec { + let mut out = Vec::new(); + for l in left { + for r in right { + if let Some(b) = Self::intersect(l, r) { + out.push(b); + } + } + } + Self::normalize(out, arity) + } + + /// Deduplicates a disjunction of boxes, and merges any two that differ in a single + /// field by unioning that field. + /// + /// The merge is what keeps `a IN ()` to one box instead of `n` of them. + fn normalize(mut boxes: Vec, arity: usize) -> Vec { + boxes.sort(); + boxes.dedup(); + if boxes.len() < 2 { + return boxes; + } + for i in 0..arity { + // Merging on a field that every box agrees on is a no-op: two boxes sharing a + // group would then agree on every field and have been deduplicated already. + // Skipping those keeps the cost proportional to the fields that actually vary, + // which is what makes a wide key affordable when most of it is pinned to single + // values. + if boxes.iter().all(|b| b[i] == boxes[0][i]) { + continue; + } + // Group by every field but `i`, then union field `i` within each group. + let mut groups: BTreeMap = BTreeMap::new(); + for mut b in boxes { + let field = b[i].take(); + match groups.entry(b) { + Entry::Vacant(e) => { + e.insert(field); + } + Entry::Occupied(mut e) => { + // An unbounded field stays unbounded in the union. + let merged = match (e.get_mut().take(), field) { + (Some(mut l), Some(r)) => { + l.extend(r); + Some(l) + } + _ => None, + }; + *e.get_mut() = merged; + } + } + } + boxes = groups + .into_iter() + .map(|(mut b, field)| { + b[i] = field; + b + }) + .collect(); + } + boxes + } + + /// Intersects two boxes, returning `None` if no key value satisfies both. + fn intersect(left: &KeyBox, right: &KeyBox) -> Option { + left.iter() + .zip_eq(right.iter()) + .map(|(l, r)| match (l, r) { + (None, None) => Some(None), + (None, Some(s)) | (Some(s), None) => Some(Some(s.clone())), + (Some(l), Some(r)) => { + let both: BTreeSet = l.intersection(r).cloned().collect(); + // An empty field bound makes the whole box unsatisfiable. + (!both.is_empty()).then_some(Some(both)) + } + }) + .collect() + } + + /// The key values to look up. + /// + /// An empty result means the predicate is never satisfied. `None` means there is nothing + /// to look up: either a key field is unbounded, or enumerating the values would exceed + /// [`FOLD_CONSTANTS_LIMIT`], the size of constant collection a plan may carry. Callers + /// that need to tell those apart should consult [`KeyBounds::bounds_every_field`] first. + pub fn lookup_values(&self) -> Option> { + if !self.bounds_every_field() { + return None; + } + let mut values = BTreeSet::new(); + for b in &self.boxes { + let sets = b.iter().map(|f| f.as_ref()).collect::>>()?; + for combination in sets.into_iter().multi_cartesian_product() { + values.insert(Row::pack(combination.iter().map(|r| r.unpack_first()))); + if values.len() > FOLD_CONSTANTS_LIMIT { + return None; + } + } + } + Some(values.into_iter().collect()) + } + + /// Whether every key field is bounded in every box, which is what makes an index + /// usable at all. + pub fn bounds_every_field(&self) -> bool { + self.arity > 0 && self.boxes.iter().all(|b| b.iter().all(|f| f.is_some())) + } + + /// The key fields that every box bounds. + /// + /// When this is a strict, non-empty subset of the key, an index on just these fields + /// would have been usable, which is what the "index too wide" notice reports. + pub fn bounded_fields(&self) -> Vec { + (0..self.arity) + .filter(|i| self.boxes.iter().all(|b| b[*i].is_some())) + .collect() + } +} + +#[cfg(test)] +mod tests { + use mz_expr::func; + use mz_repr::{Datum, ReprScalarType}; + + use super::*; + + fn lit(v: i32) -> MirScalarExpr { + MirScalarExpr::literal_ok(Datum::Int32(v), ReprScalarType::Int32) + } + + fn col_eq(c: usize, v: i32) -> MirScalarExpr { + MirScalarExpr::column(c).call_binary(lit(v), func::BinaryFunc::Eq(func::Eq)) + } + + fn and(args: Vec) -> MirScalarExpr { + MirScalarExpr::call_variadic(VariadicFunc::And(And), args) + } + + fn or(args: Vec) -> MirScalarExpr { + MirScalarExpr::call_variadic(VariadicFunc::Or(Or), args) + } + + /// `(#0, #1) IN ((f(i), g(i)) for i in 0..n)`. + fn pair_list(n: i32, f: impl Fn(i32) -> i32, g: impl Fn(i32) -> i32) -> MirScalarExpr { + or((0..n) + .map(|i| and(vec![col_eq(0, f(i)), col_eq(1, g(i))])) + .collect()) + } + + fn key() -> Vec { + vec![MirScalarExpr::column(0), MirScalarExpr::column(1)] + } + + fn values(bounds: &KeyBounds) -> Vec<(i32, i32)> { + bounds + .lookup_values() + .expect("every field bounded") + .iter() + .map(|row| { + let mut it = row.iter(); + ( + it.next().unwrap().unwrap_int32(), + it.next().unwrap().unwrap_int32(), + ) + }) + .collect() + } + + #[mz_ore::test] + fn independent_lists_form_one_box_and_a_product_of_values() { + let p = and(vec![ + or(vec![col_eq(0, 1), col_eq(0, 2)]), + or(vec![col_eq(1, 3), col_eq(1, 4)]), + ]); + let bounds = KeyBounds::extract(&p, &key()); + assert_eq!(bounds.boxes.len(), 1); + assert_eq!(values(&bounds), vec![(1, 3), (1, 4), (2, 3), (2, 4)]); + } + + #[mz_ore::test] + fn pair_lists_keep_their_pairs() { + let bounds = KeyBounds::extract(&pair_list(3, |i| i, |i| i + 10), &key()); + assert_eq!(values(&bounds), vec![(0, 10), (1, 11), (2, 12)]); + } + + #[mz_ore::test] + fn conjunctions_of_pair_lists_intersect_exactly() { + // The two lists share exactly the pairs (i, i) for odd i in 0..40. + let evens_and_all = pair_list(40, |i| i, |i| i); + let shifted = pair_list(40, |i| i, |i| if i % 2 == 1 { i } else { i + 1 }); + let bounds = KeyBounds::conjunction([&evens_and_all, &shifted], &key()); + let expected: Vec<(i32, i32)> = (0..40).filter(|i| i % 2 == 1).map(|i| (i, i)).collect(); + assert_eq!(values(&bounds), expected); + } + + #[mz_ore::test] + fn disjoint_pair_lists_are_unsatisfiable() { + let a = pair_list(40, |i| i, |i| i); + let b = pair_list(40, |i| i, |i| i + 1); + let bounds = KeyBounds::conjunction([&a, &b], &key()); + assert!(bounds.bounds_every_field()); + assert_eq!(bounds.lookup_values(), Some(Vec::new())); + } + + #[mz_ore::test] + fn an_unread_predicate_leaves_the_key_unbounded() { + let opaque = MirScalarExpr::column(2).call_is_null(); + let p = and(vec![col_eq(0, 1), opaque]); + let bounds = KeyBounds::extract(&p, &key()); + assert!(!bounds.bounds_every_field()); + assert_eq!(bounds.bounded_fields(), vec![0]); + assert_eq!(bounds.lookup_values(), None); + } + + #[mz_ore::test] + fn a_disjunct_that_says_nothing_about_the_key_unbounds_it() { + let p = or(vec![col_eq(0, 1), MirScalarExpr::column(2).call_is_null()]); + let bounds = KeyBounds::extract(&p, &[MirScalarExpr::column(0)]); + assert!(!bounds.bounds_every_field()); + } + + #[mz_ore::test] + fn null_and_false_disjuncts_contribute_no_values() { + let p = or(vec![ + and(vec![ + MirScalarExpr::literal_null(ReprScalarType::Bool), + col_eq(1, 7), + ]), + and(vec![col_eq(0, 2), col_eq(1, 5)]), + ]); + let bounds = KeyBounds::extract(&p, &key()); + assert_eq!(values(&bounds), vec![(2, 5)]); + } + + #[mz_ore::test] + fn too_many_values_are_not_enumerated() { + let wide = |c: usize| or((0..200).map(|v| col_eq(c, v)).collect()); + let p = and(vec![wide(0), wide(1)]); + let bounds = KeyBounds::extract(&p, &key()); + assert!(bounds.bounds_every_field()); + assert_eq!( + bounds.lookup_values(), + None, + "40,000 values exceed the limit" + ); + } +} diff --git a/test/sqllogictest/transform/literal_constraints.slt b/test/sqllogictest/transform/literal_constraints.slt index 3f6b96eb59daa..4098edc78d797 100644 --- a/test/sqllogictest/transform/literal_constraints.slt +++ b/test/sqllogictest/transform/literal_constraints.slt @@ -211,18 +211,22 @@ Target cluster: quickstart EOF -# `a = NULL` should NOT find the NULL in the table. +# `a = NULL` should NOT find the NULL in the table. Note that `a = NULL` reduces to a `null` +# literal, and a filter drops a `null` row just as it drops a `false` one, so the only key +# value the predicate admits is 2 and the index is used. Removal reasons disjunct by +# disjunct and has nothing to say about a bare `null` disjunct, so the filter stays. query T multiline EXPLAIN OPTIMIZED PLAN WITH(humanized expressions, arity, join implementations) AS VERBOSE TEXT FOR SELECT * FROM t1 WHERE a = NULL OR a = 2 ---- Explained Query (fast path): - Filter (null OR (#0{a} = 2)) - ReadIndex on=materialize.public.t1 idx_t1_a_b=[*** full scan ***] + Project (#0{a}, #1{b}) + Filter (null OR (#0{a} = 2)) + ReadIndex on=materialize.public.t1 idx_t1_a=[lookup value=(2)] Used Indexes: - - materialize.public.idx_t1_a_b (*** full scan ***) + - materialize.public.idx_t1_a (lookup) Target cluster: quickstart @@ -1490,3 +1494,208 @@ SELECT u FROM t_uint_cast WHERE u::int2 = 5::int2 query error "3000000000" uint2 out of range SELECT u FROM t_uint_cast WHERE u::uint2 = 5::uint2 + +# An IN list large enough that a disjunctive-normal-form expansion would be impractical +# still uses the index. The predicate is read once per candidate index, so the conjuncts +# that say nothing about the index key neither cost anything nor stand in the way. Removal +# still works on the DNF, which the size guard stops here, so the list stays in the filter +# above the lookup. +# See https://github.com/MaterializeInc/database-issues/issues/1924 + +statement ok +CREATE TABLE wide (shop_id text, sku_code text, created_at int, rule text, flag bool) + +statement ok +CREATE INDEX wide_idx ON wide(shop_id, sku_code) + +query T multiline +EXPLAIN OPTIMIZED PLAN WITH(humanized expressions, join implementations) AS VERBOSE TEXT FOR +SELECT shop_id FROM wide +WHERE shop_id = 's1' AND sku_code IN ('sku0','sku1','sku2','sku3','sku4','sku5','sku6','sku7','sku8','sku9','sku10','sku11','sku12','sku13','sku14','sku15','sku16','sku17','sku18','sku19','sku20','sku21','sku22','sku23','sku24','sku25','sku26','sku27','sku28','sku29','sku30','sku31','sku32','sku33','sku34','sku35','sku36','sku37','sku38','sku39','sku40','sku41','sku42','sku43','sku44','sku45','sku46','sku47','sku48','sku49','sku50','sku51','sku52','sku53','sku54','sku55','sku56','sku57','sku58','sku59','sku60','sku61','sku62','sku63','sku64','sku65','sku66','sku67','sku68','sku69','sku70','sku71','sku72','sku73','sku74','sku75','sku76','sku77','sku78','sku79','sku80','sku81','sku82','sku83','sku84','sku85','sku86','sku87','sku88','sku89','sku90','sku91','sku92','sku93','sku94','sku95','sku96','sku97','sku98','sku99','sku100','sku101','sku102','sku103','sku104','sku105','sku106','sku107','sku108','sku109','sku110','sku111','sku112','sku113','sku114','sku115','sku116','sku117','sku118','sku119','sku120','sku121','sku122','sku123','sku124','sku125','sku126','sku127','sku128','sku129','sku130','sku131','sku132','sku133','sku134','sku135','sku136','sku137','sku138','sku139','sku140','sku141','sku142','sku143','sku144','sku145','sku146','sku147','sku148','sku149','sku150','sku151','sku152','sku153','sku154','sku155','sku156','sku157','sku158','sku159','sku160','sku161','sku162','sku163','sku164','sku165','sku166','sku167','sku168','sku169','sku170','sku171','sku172','sku173','sku174','sku175','sku176','sku177','sku178','sku179','sku180','sku181','sku182','sku183','sku184','sku185','sku186','sku187','sku188','sku189','sku190','sku191','sku192','sku193','sku194','sku195','sku196','sku197','sku198','sku199','sku200','sku201','sku202','sku203','sku204','sku205','sku206','sku207','sku208','sku209','sku210','sku211','sku212','sku213','sku214','sku215','sku216','sku217','sku218','sku219','sku220','sku221','sku222','sku223','sku224','sku225','sku226','sku227','sku228','sku229','sku230','sku231','sku232','sku233','sku234','sku235','sku236','sku237','sku238','sku239','sku240','sku241','sku242','sku243','sku244','sku245','sku246','sku247','sku248','sku249','sku250','sku251','sku252','sku253','sku254','sku255','sku256','sku257','sku258','sku259','sku260','sku261','sku262','sku263','sku264','sku265','sku266','sku267','sku268','sku269','sku270','sku271','sku272','sku273','sku274','sku275','sku276','sku277','sku278','sku279','sku280','sku281','sku282','sku283','sku284','sku285','sku286','sku287','sku288','sku289','sku290','sku291','sku292','sku293','sku294','sku295','sku296','sku297','sku298','sku299','sku300','sku301','sku302','sku303','sku304','sku305','sku306','sku307','sku308','sku309','sku310','sku311','sku312','sku313','sku314','sku315','sku316','sku317','sku318','sku319','sku320','sku321','sku322','sku323','sku324','sku325','sku326','sku327','sku328','sku329','sku330','sku331','sku332','sku333','sku334','sku335','sku336','sku337','sku338','sku339','sku340','sku341','sku342','sku343','sku344','sku345','sku346','sku347','sku348','sku349','sku350','sku351','sku352','sku353','sku354','sku355','sku356','sku357','sku358','sku359','sku360','sku361','sku362','sku363','sku364','sku365','sku366','sku367','sku368','sku369','sku370','sku371','sku372','sku373','sku374','sku375','sku376','sku377','sku378','sku379','sku380','sku381','sku382','sku383','sku384','sku385','sku386','sku387','sku388','sku389','sku390','sku391','sku392','sku393','sku394','sku395','sku396','sku397','sku398','sku399') + AND created_at < 100 AND (rule = 'median' OR rule = 'same_price') AND flag = false +---- +Explained Query (fast path): + Project (#0{shop_id}) + Filter ((#1{sku_code} = "sku0") OR (#1{sku_code} = "sku1") OR (#1{sku_code} = "sku2") OR (#1{sku_code} = "sku3") OR (#1{sku_code} = "sku4") OR (#1{sku_code} = "sku5") OR (#1{sku_code} = "sku6") OR (#1{sku_code} = "sku7") OR (#1{sku_code} = "sku8") OR (#1{sku_code} = "sku9") OR (#1{sku_code} = "sku10") OR (#1{sku_code} = "sku11") OR (#1{sku_code} = "sku12") OR (#1{sku_code} = "sku13") OR (#1{sku_code} = "sku14") OR (#1{sku_code} = "sku15") OR (#1{sku_code} = "sku16") OR (#1{sku_code} = "sku17") OR (#1{sku_code} = "sku18") OR (#1{sku_code} = "sku19") OR (#1{sku_code} = "sku20") OR (#1{sku_code} = "sku21") OR (#1{sku_code} = "sku22") OR (#1{sku_code} = "sku23") OR (#1{sku_code} = "sku24") OR (#1{sku_code} = "sku25") OR (#1{sku_code} = "sku26") OR (#1{sku_code} = "sku27") OR (#1{sku_code} = "sku28") OR (#1{sku_code} = "sku29") OR (#1{sku_code} = "sku30") OR (#1{sku_code} = "sku31") OR (#1{sku_code} = "sku32") OR (#1{sku_code} = "sku33") OR (#1{sku_code} = "sku34") OR (#1{sku_code} = "sku35") OR (#1{sku_code} = "sku36") OR (#1{sku_code} = "sku37") OR (#1{sku_code} = "sku38") OR (#1{sku_code} = "sku39") OR (#1{sku_code} = "sku40") OR (#1{sku_code} = "sku41") OR (#1{sku_code} = "sku42") OR (#1{sku_code} = "sku43") OR (#1{sku_code} = "sku44") OR (#1{sku_code} = "sku45") OR (#1{sku_code} = "sku46") OR (#1{sku_code} = "sku47") OR (#1{sku_code} = "sku48") OR (#1{sku_code} = "sku49") OR (#1{sku_code} = "sku50") OR (#1{sku_code} = "sku51") OR (#1{sku_code} = "sku52") OR (#1{sku_code} = "sku53") OR (#1{sku_code} = "sku54") OR (#1{sku_code} = "sku55") OR (#1{sku_code} = "sku56") OR (#1{sku_code} = "sku57") OR (#1{sku_code} = "sku58") OR (#1{sku_code} = "sku59") OR (#1{sku_code} = "sku60") OR (#1{sku_code} = "sku61") OR (#1{sku_code} = "sku62") OR (#1{sku_code} = "sku63") OR (#1{sku_code} = "sku64") OR (#1{sku_code} = "sku65") OR (#1{sku_code} = "sku66") OR (#1{sku_code} = "sku67") OR (#1{sku_code} = "sku68") OR (#1{sku_code} = "sku69") OR (#1{sku_code} = "sku70") OR (#1{sku_code} = "sku71") OR (#1{sku_code} = "sku72") OR (#1{sku_code} = "sku73") OR (#1{sku_code} = "sku74") OR (#1{sku_code} = "sku75") OR (#1{sku_code} = "sku76") OR (#1{sku_code} = "sku77") OR (#1{sku_code} = "sku78") OR (#1{sku_code} = "sku79") OR (#1{sku_code} = "sku80") OR (#1{sku_code} = "sku81") OR (#1{sku_code} = "sku82") OR (#1{sku_code} = "sku83") OR (#1{sku_code} = "sku84") OR (#1{sku_code} = "sku85") OR (#1{sku_code} = "sku86") OR (#1{sku_code} = "sku87") OR (#1{sku_code} = "sku88") OR (#1{sku_code} = "sku89") OR (#1{sku_code} = "sku90") OR (#1{sku_code} = "sku91") OR (#1{sku_code} = "sku92") OR (#1{sku_code} = "sku93") OR (#1{sku_code} = "sku94") OR (#1{sku_code} = "sku95") OR (#1{sku_code} = "sku96") OR (#1{sku_code} = "sku97") OR (#1{sku_code} = "sku98") OR (#1{sku_code} = "sku99") OR (#1{sku_code} = "sku100") OR (#1{sku_code} = "sku101") OR (#1{sku_code} = "sku102") OR (#1{sku_code} = "sku103") OR (#1{sku_code} = "sku104") OR (#1{sku_code} = "sku105") OR (#1{sku_code} = "sku106") OR (#1{sku_code} = "sku107") OR (#1{sku_code} = "sku108") OR (#1{sku_code} = "sku109") OR (#1{sku_code} = "sku110") OR (#1{sku_code} = "sku111") OR (#1{sku_code} = "sku112") OR (#1{sku_code} = "sku113") OR (#1{sku_code} = "sku114") OR (#1{sku_code} = "sku115") OR (#1{sku_code} = "sku116") OR (#1{sku_code} = "sku117") OR (#1{sku_code} = "sku118") OR (#1{sku_code} = "sku119") OR (#1{sku_code} = "sku120") OR (#1{sku_code} = "sku121") OR (#1{sku_code} = "sku122") OR (#1{sku_code} = "sku123") OR (#1{sku_code} = "sku124") OR (#1{sku_code} = "sku125") OR (#1{sku_code} = "sku126") OR (#1{sku_code} = "sku127") OR (#1{sku_code} = "sku128") OR (#1{sku_code} = "sku129") OR (#1{sku_code} = "sku130") OR (#1{sku_code} = "sku131") OR (#1{sku_code} = "sku132") OR (#1{sku_code} = "sku133") OR (#1{sku_code} = "sku134") OR (#1{sku_code} = "sku135") OR (#1{sku_code} = "sku136") OR (#1{sku_code} = "sku137") OR (#1{sku_code} = "sku138") OR (#1{sku_code} = "sku139") OR (#1{sku_code} = "sku140") OR (#1{sku_code} = "sku141") OR (#1{sku_code} = "sku142") OR (#1{sku_code} = "sku143") OR (#1{sku_code} = "sku144") OR (#1{sku_code} = "sku145") OR (#1{sku_code} = "sku146") OR (#1{sku_code} = "sku147") OR (#1{sku_code} = "sku148") OR (#1{sku_code} = "sku149") OR (#1{sku_code} = "sku150") OR (#1{sku_code} = "sku151") OR (#1{sku_code} = "sku152") OR (#1{sku_code} = "sku153") OR (#1{sku_code} = "sku154") OR (#1{sku_code} = "sku155") OR (#1{sku_code} = "sku156") OR (#1{sku_code} = "sku157") OR (#1{sku_code} = "sku158") OR (#1{sku_code} = "sku159") OR (#1{sku_code} = "sku160") OR (#1{sku_code} = "sku161") OR (#1{sku_code} = "sku162") OR (#1{sku_code} = "sku163") OR (#1{sku_code} = "sku164") OR (#1{sku_code} = "sku165") OR (#1{sku_code} = "sku166") OR (#1{sku_code} = "sku167") OR (#1{sku_code} = "sku168") OR (#1{sku_code} = "sku169") OR (#1{sku_code} = "sku170") OR (#1{sku_code} = "sku171") OR (#1{sku_code} = "sku172") OR (#1{sku_code} = "sku173") OR (#1{sku_code} = "sku174") OR (#1{sku_code} = "sku175") OR (#1{sku_code} = "sku176") OR (#1{sku_code} = "sku177") OR (#1{sku_code} = "sku178") OR (#1{sku_code} = "sku179") OR (#1{sku_code} = "sku180") OR (#1{sku_code} = "sku181") OR (#1{sku_code} = "sku182") OR (#1{sku_code} = "sku183") OR (#1{sku_code} = "sku184") OR (#1{sku_code} = "sku185") OR (#1{sku_code} = "sku186") OR (#1{sku_code} = "sku187") OR (#1{sku_code} = "sku188") OR (#1{sku_code} = "sku189") OR (#1{sku_code} = "sku190") OR (#1{sku_code} = "sku191") OR (#1{sku_code} = "sku192") OR (#1{sku_code} = "sku193") OR (#1{sku_code} = "sku194") OR (#1{sku_code} = "sku195") OR (#1{sku_code} = "sku196") OR (#1{sku_code} = "sku197") OR (#1{sku_code} = "sku198") OR (#1{sku_code} = "sku199") OR (#1{sku_code} = "sku200") OR (#1{sku_code} = "sku201") OR (#1{sku_code} = "sku202") OR (#1{sku_code} = "sku203") OR (#1{sku_code} = "sku204") OR (#1{sku_code} = "sku205") OR (#1{sku_code} = "sku206") OR (#1{sku_code} = "sku207") OR (#1{sku_code} = "sku208") OR (#1{sku_code} = "sku209") OR (#1{sku_code} = "sku210") OR (#1{sku_code} = "sku211") OR (#1{sku_code} = "sku212") OR (#1{sku_code} = "sku213") OR (#1{sku_code} = "sku214") OR (#1{sku_code} = "sku215") OR (#1{sku_code} = "sku216") OR (#1{sku_code} = "sku217") OR (#1{sku_code} = "sku218") OR (#1{sku_code} = "sku219") OR (#1{sku_code} = "sku220") OR (#1{sku_code} = "sku221") OR (#1{sku_code} = "sku222") OR (#1{sku_code} = "sku223") OR (#1{sku_code} = "sku224") OR (#1{sku_code} = "sku225") OR (#1{sku_code} = "sku226") OR (#1{sku_code} = "sku227") OR (#1{sku_code} = "sku228") OR (#1{sku_code} = "sku229") OR (#1{sku_code} = "sku230") OR (#1{sku_code} = "sku231") OR (#1{sku_code} = "sku232") OR (#1{sku_code} = "sku233") OR (#1{sku_code} = "sku234") OR (#1{sku_code} = "sku235") OR (#1{sku_code} = "sku236") OR (#1{sku_code} = "sku237") OR (#1{sku_code} = "sku238") OR (#1{sku_code} = "sku239") OR (#1{sku_code} = "sku240") OR (#1{sku_code} = "sku241") OR (#1{sku_code} = "sku242") OR (#1{sku_code} = "sku243") OR (#1{sku_code} = "sku244") OR (#1{sku_code} = "sku245") OR (#1{sku_code} = "sku246") OR (#1{sku_code} = "sku247") OR (#1{sku_code} = "sku248") OR (#1{sku_code} = "sku249") OR (#1{sku_code} = "sku250") OR (#1{sku_code} = "sku251") OR (#1{sku_code} = "sku252") OR (#1{sku_code} = "sku253") OR (#1{sku_code} = "sku254") OR (#1{sku_code} = "sku255") OR (#1{sku_code} = "sku256") OR (#1{sku_code} = "sku257") OR (#1{sku_code} = "sku258") OR (#1{sku_code} = "sku259") OR (#1{sku_code} = "sku260") OR (#1{sku_code} = "sku261") OR (#1{sku_code} = "sku262") OR (#1{sku_code} = "sku263") OR (#1{sku_code} = "sku264") OR (#1{sku_code} = "sku265") OR (#1{sku_code} = "sku266") OR (#1{sku_code} = "sku267") OR (#1{sku_code} = "sku268") OR (#1{sku_code} = "sku269") OR (#1{sku_code} = "sku270") OR (#1{sku_code} = "sku271") OR (#1{sku_code} = "sku272") OR (#1{sku_code} = "sku273") OR (#1{sku_code} = "sku274") OR (#1{sku_code} = "sku275") OR (#1{sku_code} = "sku276") OR (#1{sku_code} = "sku277") OR (#1{sku_code} = "sku278") OR (#1{sku_code} = "sku279") OR (#1{sku_code} = "sku280") OR (#1{sku_code} = "sku281") OR (#1{sku_code} = "sku282") OR (#1{sku_code} = "sku283") OR (#1{sku_code} = "sku284") OR (#1{sku_code} = "sku285") OR (#1{sku_code} = "sku286") OR (#1{sku_code} = "sku287") OR (#1{sku_code} = "sku288") OR (#1{sku_code} = "sku289") OR (#1{sku_code} = "sku290") OR (#1{sku_code} = "sku291") OR (#1{sku_code} = "sku292") OR (#1{sku_code} = "sku293") OR (#1{sku_code} = "sku294") OR (#1{sku_code} = "sku295") OR (#1{sku_code} = "sku296") OR (#1{sku_code} = "sku297") OR (#1{sku_code} = "sku298") OR (#1{sku_code} = "sku299") OR (#1{sku_code} = "sku300") OR (#1{sku_code} = "sku301") OR (#1{sku_code} = "sku302") OR (#1{sku_code} = "sku303") OR (#1{sku_code} = "sku304") OR (#1{sku_code} = "sku305") OR (#1{sku_code} = "sku306") OR (#1{sku_code} = "sku307") OR (#1{sku_code} = "sku308") OR (#1{sku_code} = "sku309") OR (#1{sku_code} = "sku310") OR (#1{sku_code} = "sku311") OR (#1{sku_code} = "sku312") OR (#1{sku_code} = "sku313") OR (#1{sku_code} = "sku314") OR (#1{sku_code} = "sku315") OR (#1{sku_code} = "sku316") OR (#1{sku_code} = "sku317") OR (#1{sku_code} = "sku318") OR (#1{sku_code} = "sku319") OR (#1{sku_code} = "sku320") OR (#1{sku_code} = "sku321") OR (#1{sku_code} = "sku322") OR (#1{sku_code} = "sku323") OR (#1{sku_code} = "sku324") OR (#1{sku_code} = "sku325") OR (#1{sku_code} = "sku326") OR (#1{sku_code} = "sku327") OR (#1{sku_code} = "sku328") OR (#1{sku_code} = "sku329") OR (#1{sku_code} = "sku330") OR (#1{sku_code} = "sku331") OR (#1{sku_code} = "sku332") OR (#1{sku_code} = "sku333") OR (#1{sku_code} = "sku334") OR (#1{sku_code} = "sku335") OR (#1{sku_code} = "sku336") OR (#1{sku_code} = "sku337") OR (#1{sku_code} = "sku338") OR (#1{sku_code} = "sku339") OR (#1{sku_code} = "sku340") OR (#1{sku_code} = "sku341") OR (#1{sku_code} = "sku342") OR (#1{sku_code} = "sku343") OR (#1{sku_code} = "sku344") OR (#1{sku_code} = "sku345") OR (#1{sku_code} = "sku346") OR (#1{sku_code} = "sku347") OR (#1{sku_code} = "sku348") OR (#1{sku_code} = "sku349") OR (#1{sku_code} = "sku350") OR (#1{sku_code} = "sku351") OR (#1{sku_code} = "sku352") OR (#1{sku_code} = "sku353") OR (#1{sku_code} = "sku354") OR (#1{sku_code} = "sku355") OR (#1{sku_code} = "sku356") OR (#1{sku_code} = "sku357") OR (#1{sku_code} = "sku358") OR (#1{sku_code} = "sku359") OR (#1{sku_code} = "sku360") OR (#1{sku_code} = "sku361") OR (#1{sku_code} = "sku362") OR (#1{sku_code} = "sku363") OR (#1{sku_code} = "sku364") OR (#1{sku_code} = "sku365") OR (#1{sku_code} = "sku366") OR (#1{sku_code} = "sku367") OR (#1{sku_code} = "sku368") OR (#1{sku_code} = "sku369") OR (#1{sku_code} = "sku370") OR (#1{sku_code} = "sku371") OR (#1{sku_code} = "sku372") OR (#1{sku_code} = "sku373") OR (#1{sku_code} = "sku374") OR (#1{sku_code} = "sku375") OR (#1{sku_code} = "sku376") OR (#1{sku_code} = "sku377") OR (#1{sku_code} = "sku378") OR (#1{sku_code} = "sku379") OR (#1{sku_code} = "sku380") OR (#1{sku_code} = "sku381") OR (#1{sku_code} = "sku382") OR (#1{sku_code} = "sku383") OR (#1{sku_code} = "sku384") OR (#1{sku_code} = "sku385") OR (#1{sku_code} = "sku386") OR (#1{sku_code} = "sku387") OR (#1{sku_code} = "sku388") OR (#1{sku_code} = "sku389") OR (#1{sku_code} = "sku390") OR (#1{sku_code} = "sku391") OR (#1{sku_code} = "sku392") OR (#1{sku_code} = "sku393") OR (#1{sku_code} = "sku394") OR (#1{sku_code} = "sku395") OR (#1{sku_code} = "sku396") OR (#1{sku_code} = "sku397") OR (#1{sku_code} = "sku398") OR (#1{sku_code} = "sku399")) AND (#2{created_at} < 100) AND ((#3{rule} = "median") OR (#3{rule} = "same_price")) AND (#4{flag} = false) + ReadIndex on=materialize.public.wide wide_idx=[lookup values=[("s1", "sku0"); ("s1", "sku1"); ("s1", "sku2"); ("s1", "sku3"); ("s1", "sku4"); ("s1", "sku5"); ("s1", "sku6"); ("s1", "sku7"); ("s1", "sku8"); ("s1", "sku9"); ("s1", "sku10"); ("s1", "sku11"); ("s1", "sku12"); ("s1", "sku13"); ("s1", "sku14"); ("s1", "sku15"); ("s1", "sku16"); ("s1", "sku17"); ("s1", "sku18"); ("s1", "sku19"); ("s1", "sku20"); ("s1", "sku21"); ("s1", "sku22"); ("s1", "sku23"); ("s1", "sku24"); ("s1", "sku25"); ("s1", "sku26"); ("s1", "sku27"); ("s1", "sku28"); ("s1", "sku29"); ("s1", "sku30"); ("s1", "sku31"); ("s1", "sku32"); ("s1", "sku33"); ("s1", "sku34"); ("s1", "sku35"); ("s1", "sku36"); ("s1", "sku37"); ("s1", "sku38"); ("s1", "sku39"); ("s1", "sku40"); ("s1", "sku41"); ("s1", "sku42"); ("s1", "sku43"); ("s1", "sku44"); ("s1", "sku45"); ("s1", "sku46"); ("s1", "sku47"); ("s1", "sku48"); ("s1", "sku49"); ("s1", "sku50"); ("s1", "sku51"); ("s1", "sku52"); ("s1", "sku53"); ("s1", "sku54"); ("s1", "sku55"); ("s1", "sku56"); ("s1", "sku57"); ("s1", "sku58"); ("s1", "sku59"); ("s1", "sku60"); ("s1", "sku61"); ("s1", "sku62"); ("s1", "sku63"); ("s1", "sku64"); ("s1", "sku65"); ("s1", "sku66"); ("s1", "sku67"); ("s1", "sku68"); ("s1", "sku69"); ("s1", "sku70"); ("s1", "sku71"); ("s1", "sku72"); ("s1", "sku73"); ("s1", "sku74"); ("s1", "sku75"); ("s1", "sku76"); ("s1", "sku77"); ("s1", "sku78"); ("s1", "sku79"); ("s1", "sku80"); ("s1", "sku81"); ("s1", "sku82"); ("s1", "sku83"); ("s1", "sku84"); ("s1", "sku85"); ("s1", "sku86"); ("s1", "sku87"); ("s1", "sku88"); ("s1", "sku89"); ("s1", "sku90"); ("s1", "sku91"); ("s1", "sku92"); ("s1", "sku93"); ("s1", "sku94"); ("s1", "sku95"); ("s1", "sku96"); ("s1", "sku97"); ("s1", "sku98"); ("s1", "sku99"); ("s1", "sku100"); ("s1", "sku101"); ("s1", "sku102"); ("s1", "sku103"); ("s1", "sku104"); ("s1", "sku105"); ("s1", "sku106"); ("s1", "sku107"); ("s1", "sku108"); ("s1", "sku109"); ("s1", "sku110"); ("s1", "sku111"); ("s1", "sku112"); ("s1", "sku113"); ("s1", "sku114"); ("s1", "sku115"); ("s1", "sku116"); ("s1", "sku117"); ("s1", "sku118"); ("s1", "sku119"); ("s1", "sku120"); ("s1", "sku121"); ("s1", "sku122"); ("s1", "sku123"); ("s1", "sku124"); ("s1", "sku125"); ("s1", "sku126"); ("s1", "sku127"); ("s1", "sku128"); ("s1", "sku129"); ("s1", "sku130"); ("s1", "sku131"); ("s1", "sku132"); ("s1", "sku133"); ("s1", "sku134"); ("s1", "sku135"); ("s1", "sku136"); ("s1", "sku137"); ("s1", "sku138"); ("s1", "sku139"); ("s1", "sku140"); ("s1", "sku141"); ("s1", "sku142"); ("s1", "sku143"); ("s1", "sku144"); ("s1", "sku145"); ("s1", "sku146"); ("s1", "sku147"); ("s1", "sku148"); ("s1", "sku149"); ("s1", "sku150"); ("s1", "sku151"); ("s1", "sku152"); ("s1", "sku153"); ("s1", "sku154"); ("s1", "sku155"); ("s1", "sku156"); ("s1", "sku157"); ("s1", "sku158"); ("s1", "sku159"); ("s1", "sku160"); ("s1", "sku161"); ("s1", "sku162"); ("s1", "sku163"); ("s1", "sku164"); ("s1", "sku165"); ("s1", "sku166"); ("s1", "sku167"); ("s1", "sku168"); ("s1", "sku169"); ("s1", "sku170"); ("s1", "sku171"); ("s1", "sku172"); ("s1", "sku173"); ("s1", "sku174"); ("s1", "sku175"); ("s1", "sku176"); ("s1", "sku177"); ("s1", "sku178"); ("s1", "sku179"); ("s1", "sku180"); ("s1", "sku181"); ("s1", "sku182"); ("s1", "sku183"); ("s1", "sku184"); ("s1", "sku185"); ("s1", "sku186"); ("s1", "sku187"); ("s1", "sku188"); ("s1", "sku189"); ("s1", "sku190"); ("s1", "sku191"); ("s1", "sku192"); ("s1", "sku193"); ("s1", "sku194"); ("s1", "sku195"); ("s1", "sku196"); ("s1", "sku197"); ("s1", "sku198"); ("s1", "sku199"); ("s1", "sku200"); ("s1", "sku201"); ("s1", "sku202"); ("s1", "sku203"); ("s1", "sku204"); ("s1", "sku205"); ("s1", "sku206"); ("s1", "sku207"); ("s1", "sku208"); ("s1", "sku209"); ("s1", "sku210"); ("s1", "sku211"); ("s1", "sku212"); ("s1", "sku213"); ("s1", "sku214"); ("s1", "sku215"); ("s1", "sku216"); ("s1", "sku217"); ("s1", "sku218"); ("s1", "sku219"); ("s1", "sku220"); ("s1", "sku221"); ("s1", "sku222"); ("s1", "sku223"); ("s1", "sku224"); ("s1", "sku225"); ("s1", "sku226"); ("s1", "sku227"); ("s1", "sku228"); ("s1", "sku229"); ("s1", "sku230"); ("s1", "sku231"); ("s1", "sku232"); ("s1", "sku233"); ("s1", "sku234"); ("s1", "sku235"); ("s1", "sku236"); ("s1", "sku237"); ("s1", "sku238"); ("s1", "sku239"); ("s1", "sku240"); ("s1", "sku241"); ("s1", "sku242"); ("s1", "sku243"); ("s1", "sku244"); ("s1", "sku245"); ("s1", "sku246"); ("s1", "sku247"); ("s1", "sku248"); ("s1", "sku249"); ("s1", "sku250"); ("s1", "sku251"); ("s1", "sku252"); ("s1", "sku253"); ("s1", "sku254"); ("s1", "sku255"); ("s1", "sku256"); ("s1", "sku257"); ("s1", "sku258"); ("s1", "sku259"); ("s1", "sku260"); ("s1", "sku261"); ("s1", "sku262"); ("s1", "sku263"); ("s1", "sku264"); ("s1", "sku265"); ("s1", "sku266"); ("s1", "sku267"); ("s1", "sku268"); ("s1", "sku269"); ("s1", "sku270"); ("s1", "sku271"); ("s1", "sku272"); ("s1", "sku273"); ("s1", "sku274"); ("s1", "sku275"); ("s1", "sku276"); ("s1", "sku277"); ("s1", "sku278"); ("s1", "sku279"); ("s1", "sku280"); ("s1", "sku281"); ("s1", "sku282"); ("s1", "sku283"); ("s1", "sku284"); ("s1", "sku285"); ("s1", "sku286"); ("s1", "sku287"); ("s1", "sku288"); ("s1", "sku289"); ("s1", "sku290"); ("s1", "sku291"); ("s1", "sku292"); ("s1", "sku293"); ("s1", "sku294"); ("s1", "sku295"); ("s1", "sku296"); ("s1", "sku297"); ("s1", "sku298"); ("s1", "sku299"); ("s1", "sku300"); ("s1", "sku301"); ("s1", "sku302"); ("s1", "sku303"); ("s1", "sku304"); ("s1", "sku305"); ("s1", "sku306"); ("s1", "sku307"); ("s1", "sku308"); ("s1", "sku309"); ("s1", "sku310"); ("s1", "sku311"); ("s1", "sku312"); ("s1", "sku313"); ("s1", "sku314"); ("s1", "sku315"); ("s1", "sku316"); ("s1", "sku317"); ("s1", "sku318"); ("s1", "sku319"); ("s1", "sku320"); ("s1", "sku321"); ("s1", "sku322"); ("s1", "sku323"); ("s1", "sku324"); ("s1", "sku325"); ("s1", "sku326"); ("s1", "sku327"); ("s1", "sku328"); ("s1", "sku329"); ("s1", "sku330"); ("s1", "sku331"); ("s1", "sku332"); ("s1", "sku333"); ("s1", "sku334"); ("s1", "sku335"); ("s1", "sku336"); ("s1", "sku337"); ("s1", "sku338"); ("s1", "sku339"); ("s1", "sku340"); ("s1", "sku341"); ("s1", "sku342"); ("s1", "sku343"); ("s1", "sku344"); ("s1", "sku345"); ("s1", "sku346"); ("s1", "sku347"); ("s1", "sku348"); ("s1", "sku349"); ("s1", "sku350"); ("s1", "sku351"); ("s1", "sku352"); ("s1", "sku353"); ("s1", "sku354"); ("s1", "sku355"); ("s1", "sku356"); ("s1", "sku357"); ("s1", "sku358"); ("s1", "sku359"); ("s1", "sku360"); ("s1", "sku361"); ("s1", "sku362"); ("s1", "sku363"); ("s1", "sku364"); ("s1", "sku365"); ("s1", "sku366"); ("s1", "sku367"); ("s1", "sku368"); ("s1", "sku369"); ("s1", "sku370"); ("s1", "sku371"); ("s1", "sku372"); ("s1", "sku373"); ("s1", "sku374"); ("s1", "sku375"); ("s1", "sku376"); ("s1", "sku377"); ("s1", "sku378"); ("s1", "sku379"); ("s1", "sku380"); ("s1", "sku381"); ("s1", "sku382"); ("s1", "sku383"); ("s1", "sku384"); ("s1", "sku385"); ("s1", "sku386"); ("s1", "sku387"); ("s1", "sku388"); ("s1", "sku389"); ("s1", "sku390"); ("s1", "sku391"); ("s1", "sku392"); ("s1", "sku393"); ("s1", "sku394"); ("s1", "sku395"); ("s1", "sku396"); ("s1", "sku397"); ("s1", "sku398"); ("s1", "sku399")]] + +Used Indexes: + - materialize.public.wide_idx (lookup) + +Target cluster: quickstart + +EOF + +# Unrelated disjunctions multiply a disjunctive normal form but say nothing about the key, +# so they are carried through to the residual filter. The tail of that filter has the shape +# the DNF preparation leaves when its size guard stops it partway, which `undo_preparation` +# keeps when it is no larger than the original; that is the transform's existing behaviour, +# reached here because the lookup is now found. + +query T multiline +EXPLAIN OPTIMIZED PLAN WITH(humanized expressions, join implementations) AS VERBOSE TEXT FOR +SELECT shop_id FROM wide +WHERE shop_id = 's1' AND sku_code IN ('sku3','sku4') + AND (created_at > 0 OR rule = 'r0') AND (created_at > 1 OR rule = 'r1') + AND (created_at > 2 OR rule = 'r2') AND (created_at > 3 OR rule = 'r3') + AND (created_at > 4 OR rule = 'r4') AND (created_at > 5 OR rule = 'r5') + AND (created_at > 6 OR rule = 'r6') AND (created_at > 7 OR rule = 'r7') + AND (created_at > 8 OR rule = 'r8') AND (created_at > 9 OR rule = 'r9') +---- +Explained Query (fast path): + Project (#0{shop_id}) + Filter ((#3{rule} = "r0") OR (#2{created_at} > 0)) AND ((#3{rule} = "r1") OR (#2{created_at} > 1)) AND ((#3{rule} = "r2") OR (#2{created_at} > 2)) AND ((#3{rule} = "r3") OR (#2{created_at} > 3)) AND ((#3{rule} = "r4") OR (#2{created_at} > 4)) AND ((#3{rule} = "r5") OR (#2{created_at} > 5)) AND ((#3{rule} = "r6") OR (#2{created_at} > 6)) AND ((#7 AND #8 AND (#3{rule} = "r7")) OR ((#2{created_at} > 7) AND ((#7 AND (#8 OR (#3{rule} = "r9"))) OR (#8 AND (#3{rule} = "r8"))))) + Map ((#2{created_at} > 8), (#2{created_at} > 9)) + ReadIndex on=materialize.public.wide wide_idx=[lookup values=[("s1", "sku3"); ("s1", "sku4")]] + +Used Indexes: + - materialize.public.wide_idx (lookup) + +Target cluster: quickstart + +EOF + +# An expression pinned to two different values makes the relation empty, whether or not the +# expression is part of an index key. + +query T multiline +EXPLAIN OPTIMIZED PLAN WITH(humanized expressions, join implementations) AS VERBOSE TEXT FOR +SELECT shop_id FROM wide +WHERE shop_id = 's1' AND sku_code = 'sku3' + AND (created_at = 1 OR created_at = 2) AND (created_at = 3 OR created_at = 4) +---- +Explained Query (fast path): + Constant + +Target cluster: quickstart + +EOF + +# An IN list on a column the index does not cover must not obstruct the index. Converting to +# disjunctive normal form would multiply the two lists together, so these are the cases that +# used to fall off a cliff. The lookup values come only from the covered column, and the +# uncovered list is carried through to the residual filter. + +statement ok +CREATE TABLE cover (foo int, bar int, qux int) + +statement ok +CREATE INDEX cover_foo ON cover(foo) + +statement ok +INSERT INTO cover VALUES (1, 1, 1), (2, 2, 2), (3, 3, 3) + +# Two IN lists, one covered. A DNF would have 144 disjuncts for 12 lookup values. + +query T multiline +EXPLAIN OPTIMIZED PLAN WITH(humanized expressions, join implementations) AS VERBOSE TEXT FOR +SELECT foo FROM cover WHERE foo IN (0,1,2,3,4,5,6,7,8,9,10,11) AND bar IN (0,1,2,3,4,5,6,7,8,9,10,11) +---- +Explained Query (fast path): + Project (#0{foo}) + Filter ((#1{bar} = 0) OR (#1{bar} = 1) OR (#1{bar} = 2) OR (#1{bar} = 3) OR (#1{bar} = 4) OR (#1{bar} = 5) OR (#1{bar} = 6) OR (#1{bar} = 7) OR (#1{bar} = 8) OR (#1{bar} = 9) OR (#1{bar} = 10) OR (#1{bar} = 11)) + ReadIndex on=materialize.public.cover cover_foo=[lookup values=[(0); (1); (2); (3); (4); (5); (6); (7); (8); (9); (10); (11)]] + +Used Indexes: + - materialize.public.cover_foo (lookup) + +Target cluster: quickstart + +EOF + +# Three IN lists, one covered. A DNF would have 12^3 = 1728 disjuncts for the same 12 values. + +query T multiline +EXPLAIN OPTIMIZED PLAN WITH(humanized expressions, join implementations) AS VERBOSE TEXT FOR +SELECT foo FROM cover WHERE foo IN (0,1,2,3,4,5,6,7,8,9,10,11) AND bar IN (0,1,2,3,4,5,6,7,8,9,10,11) AND qux IN (0,1,2,3,4,5,6,7,8,9,10,11) +---- +Explained Query (fast path): + Project (#0{foo}) + Filter ((#1{bar} = 0) OR (#1{bar} = 1) OR (#1{bar} = 2) OR (#1{bar} = 3) OR (#1{bar} = 4) OR (#1{bar} = 5) OR (#1{bar} = 6) OR (#1{bar} = 7) OR (#1{bar} = 8) OR (#1{bar} = 9) OR (#1{bar} = 10) OR (#1{bar} = 11)) AND ((#2{qux} = 0) OR (#2{qux} = 1) OR (#2{qux} = 2) OR (#2{qux} = 3) OR (#2{qux} = 4) OR (#2{qux} = 5) OR (#2{qux} = 6) OR (#2{qux} = 7) OR (#2{qux} = 8) OR (#2{qux} = 9) OR (#2{qux} = 10) OR (#2{qux} = 11)) + ReadIndex on=materialize.public.cover cover_foo=[lookup values=[(0); (1); (2); (3); (4); (5); (6); (7); (8); (9); (10); (11)]] + +Used Indexes: + - materialize.public.cover_foo (lookup) + +Target cluster: quickstart + +EOF + +# A covered list long enough that the whole predicate is past any workable size guard on a +# DNF, with an uncovered list alongside it. The long list is on the covered column here so +# that the plan prints one line of lookup values rather than a wall of residual predicates. + +query T multiline +EXPLAIN OPTIMIZED PLAN WITH(humanized expressions, join implementations) AS VERBOSE TEXT FOR +SELECT foo FROM cover WHERE foo IN (0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339) AND bar IN (1,2) +---- +Explained Query (fast path): + Project (#0{foo}) + Filter ((#0{foo} = 0) OR (#0{foo} = 1) OR (#0{foo} = 2) OR (#0{foo} = 3) OR (#0{foo} = 4) OR (#0{foo} = 5) OR (#0{foo} = 6) OR (#0{foo} = 7) OR (#0{foo} = 8) OR (#0{foo} = 9) OR (#0{foo} = 10) OR (#0{foo} = 11) OR (#0{foo} = 12) OR (#0{foo} = 13) OR (#0{foo} = 14) OR (#0{foo} = 15) OR (#0{foo} = 16) OR (#0{foo} = 17) OR (#0{foo} = 18) OR (#0{foo} = 19) OR (#0{foo} = 20) OR (#0{foo} = 21) OR (#0{foo} = 22) OR (#0{foo} = 23) OR (#0{foo} = 24) OR (#0{foo} = 25) OR (#0{foo} = 26) OR (#0{foo} = 27) OR (#0{foo} = 28) OR (#0{foo} = 29) OR (#0{foo} = 30) OR (#0{foo} = 31) OR (#0{foo} = 32) OR (#0{foo} = 33) OR (#0{foo} = 34) OR (#0{foo} = 35) OR (#0{foo} = 36) OR (#0{foo} = 37) OR (#0{foo} = 38) OR (#0{foo} = 39) OR (#0{foo} = 40) OR (#0{foo} = 41) OR (#0{foo} = 42) OR (#0{foo} = 43) OR (#0{foo} = 44) OR (#0{foo} = 45) OR (#0{foo} = 46) OR (#0{foo} = 47) OR (#0{foo} = 48) OR (#0{foo} = 49) OR (#0{foo} = 50) OR (#0{foo} = 51) OR (#0{foo} = 52) OR (#0{foo} = 53) OR (#0{foo} = 54) OR (#0{foo} = 55) OR (#0{foo} = 56) OR (#0{foo} = 57) OR (#0{foo} = 58) OR (#0{foo} = 59) OR (#0{foo} = 60) OR (#0{foo} = 61) OR (#0{foo} = 62) OR (#0{foo} = 63) OR (#0{foo} = 64) OR (#0{foo} = 65) OR (#0{foo} = 66) OR (#0{foo} = 67) OR (#0{foo} = 68) OR (#0{foo} = 69) OR (#0{foo} = 70) OR (#0{foo} = 71) OR (#0{foo} = 72) OR (#0{foo} = 73) OR (#0{foo} = 74) OR (#0{foo} = 75) OR (#0{foo} = 76) OR (#0{foo} = 77) OR (#0{foo} = 78) OR (#0{foo} = 79) OR (#0{foo} = 80) OR (#0{foo} = 81) OR (#0{foo} = 82) OR (#0{foo} = 83) OR (#0{foo} = 84) OR (#0{foo} = 85) OR (#0{foo} = 86) OR (#0{foo} = 87) OR (#0{foo} = 88) OR (#0{foo} = 89) OR (#0{foo} = 90) OR (#0{foo} = 91) OR (#0{foo} = 92) OR (#0{foo} = 93) OR (#0{foo} = 94) OR (#0{foo} = 95) OR (#0{foo} = 96) OR (#0{foo} = 97) OR (#0{foo} = 98) OR (#0{foo} = 99) OR (#0{foo} = 100) OR (#0{foo} = 101) OR (#0{foo} = 102) OR (#0{foo} = 103) OR (#0{foo} = 104) OR (#0{foo} = 105) OR (#0{foo} = 106) OR (#0{foo} = 107) OR (#0{foo} = 108) OR (#0{foo} = 109) OR (#0{foo} = 110) OR (#0{foo} = 111) OR (#0{foo} = 112) OR (#0{foo} = 113) OR (#0{foo} = 114) OR (#0{foo} = 115) OR (#0{foo} = 116) OR (#0{foo} = 117) OR (#0{foo} = 118) OR (#0{foo} = 119) OR (#0{foo} = 120) OR (#0{foo} = 121) OR (#0{foo} = 122) OR (#0{foo} = 123) OR (#0{foo} = 124) OR (#0{foo} = 125) OR (#0{foo} = 126) OR (#0{foo} = 127) OR (#0{foo} = 128) OR (#0{foo} = 129) OR (#0{foo} = 130) OR (#0{foo} = 131) OR (#0{foo} = 132) OR (#0{foo} = 133) OR (#0{foo} = 134) OR (#0{foo} = 135) OR (#0{foo} = 136) OR (#0{foo} = 137) OR (#0{foo} = 138) OR (#0{foo} = 139) OR (#0{foo} = 140) OR (#0{foo} = 141) OR (#0{foo} = 142) OR (#0{foo} = 143) OR (#0{foo} = 144) OR (#0{foo} = 145) OR (#0{foo} = 146) OR (#0{foo} = 147) OR (#0{foo} = 148) OR (#0{foo} = 149) OR (#0{foo} = 150) OR (#0{foo} = 151) OR (#0{foo} = 152) OR (#0{foo} = 153) OR (#0{foo} = 154) OR (#0{foo} = 155) OR (#0{foo} = 156) OR (#0{foo} = 157) OR (#0{foo} = 158) OR (#0{foo} = 159) OR (#0{foo} = 160) OR (#0{foo} = 161) OR (#0{foo} = 162) OR (#0{foo} = 163) OR (#0{foo} = 164) OR (#0{foo} = 165) OR (#0{foo} = 166) OR (#0{foo} = 167) OR (#0{foo} = 168) OR (#0{foo} = 169) OR (#0{foo} = 170) OR (#0{foo} = 171) OR (#0{foo} = 172) OR (#0{foo} = 173) OR (#0{foo} = 174) OR (#0{foo} = 175) OR (#0{foo} = 176) OR (#0{foo} = 177) OR (#0{foo} = 178) OR (#0{foo} = 179) OR (#0{foo} = 180) OR (#0{foo} = 181) OR (#0{foo} = 182) OR (#0{foo} = 183) OR (#0{foo} = 184) OR (#0{foo} = 185) OR (#0{foo} = 186) OR (#0{foo} = 187) OR (#0{foo} = 188) OR (#0{foo} = 189) OR (#0{foo} = 190) OR (#0{foo} = 191) OR (#0{foo} = 192) OR (#0{foo} = 193) OR (#0{foo} = 194) OR (#0{foo} = 195) OR (#0{foo} = 196) OR (#0{foo} = 197) OR (#0{foo} = 198) OR (#0{foo} = 199) OR (#0{foo} = 200) OR (#0{foo} = 201) OR (#0{foo} = 202) OR (#0{foo} = 203) OR (#0{foo} = 204) OR (#0{foo} = 205) OR (#0{foo} = 206) OR (#0{foo} = 207) OR (#0{foo} = 208) OR (#0{foo} = 209) OR (#0{foo} = 210) OR (#0{foo} = 211) OR (#0{foo} = 212) OR (#0{foo} = 213) OR (#0{foo} = 214) OR (#0{foo} = 215) OR (#0{foo} = 216) OR (#0{foo} = 217) OR (#0{foo} = 218) OR (#0{foo} = 219) OR (#0{foo} = 220) OR (#0{foo} = 221) OR (#0{foo} = 222) OR (#0{foo} = 223) OR (#0{foo} = 224) OR (#0{foo} = 225) OR (#0{foo} = 226) OR (#0{foo} = 227) OR (#0{foo} = 228) OR (#0{foo} = 229) OR (#0{foo} = 230) OR (#0{foo} = 231) OR (#0{foo} = 232) OR (#0{foo} = 233) OR (#0{foo} = 234) OR (#0{foo} = 235) OR (#0{foo} = 236) OR (#0{foo} = 237) OR (#0{foo} = 238) OR (#0{foo} = 239) OR (#0{foo} = 240) OR (#0{foo} = 241) OR (#0{foo} = 242) OR (#0{foo} = 243) OR (#0{foo} = 244) OR (#0{foo} = 245) OR (#0{foo} = 246) OR (#0{foo} = 247) OR (#0{foo} = 248) OR (#0{foo} = 249) OR (#0{foo} = 250) OR (#0{foo} = 251) OR (#0{foo} = 252) OR (#0{foo} = 253) OR (#0{foo} = 254) OR (#0{foo} = 255) OR (#0{foo} = 256) OR (#0{foo} = 257) OR (#0{foo} = 258) OR (#0{foo} = 259) OR (#0{foo} = 260) OR (#0{foo} = 261) OR (#0{foo} = 262) OR (#0{foo} = 263) OR (#0{foo} = 264) OR (#0{foo} = 265) OR (#0{foo} = 266) OR (#0{foo} = 267) OR (#0{foo} = 268) OR (#0{foo} = 269) OR (#0{foo} = 270) OR (#0{foo} = 271) OR (#0{foo} = 272) OR (#0{foo} = 273) OR (#0{foo} = 274) OR (#0{foo} = 275) OR (#0{foo} = 276) OR (#0{foo} = 277) OR (#0{foo} = 278) OR (#0{foo} = 279) OR (#0{foo} = 280) OR (#0{foo} = 281) OR (#0{foo} = 282) OR (#0{foo} = 283) OR (#0{foo} = 284) OR (#0{foo} = 285) OR (#0{foo} = 286) OR (#0{foo} = 287) OR (#0{foo} = 288) OR (#0{foo} = 289) OR (#0{foo} = 290) OR (#0{foo} = 291) OR (#0{foo} = 292) OR (#0{foo} = 293) OR (#0{foo} = 294) OR (#0{foo} = 295) OR (#0{foo} = 296) OR (#0{foo} = 297) OR (#0{foo} = 298) OR (#0{foo} = 299) OR (#0{foo} = 300) OR (#0{foo} = 301) OR (#0{foo} = 302) OR (#0{foo} = 303) OR (#0{foo} = 304) OR (#0{foo} = 305) OR (#0{foo} = 306) OR (#0{foo} = 307) OR (#0{foo} = 308) OR (#0{foo} = 309) OR (#0{foo} = 310) OR (#0{foo} = 311) OR (#0{foo} = 312) OR (#0{foo} = 313) OR (#0{foo} = 314) OR (#0{foo} = 315) OR (#0{foo} = 316) OR (#0{foo} = 317) OR (#0{foo} = 318) OR (#0{foo} = 319) OR (#0{foo} = 320) OR (#0{foo} = 321) OR (#0{foo} = 322) OR (#0{foo} = 323) OR (#0{foo} = 324) OR (#0{foo} = 325) OR (#0{foo} = 326) OR (#0{foo} = 327) OR (#0{foo} = 328) OR (#0{foo} = 329) OR (#0{foo} = 330) OR (#0{foo} = 331) OR (#0{foo} = 332) OR (#0{foo} = 333) OR (#0{foo} = 334) OR (#0{foo} = 335) OR (#0{foo} = 336) OR (#0{foo} = 337) OR (#0{foo} = 338) OR (#0{foo} = 339)) AND ((#1{bar} = 1) OR (#1{bar} = 2)) + ReadIndex on=materialize.public.cover cover_foo=[lookup values=[(0); (1); (2); (3); (4); (5); (6); (7); (8); (9); (10); (11); (12); (13); (14); (15); (16); (17); (18); (19); (20); (21); (22); (23); (24); (25); (26); (27); (28); (29); (30); (31); (32); (33); (34); (35); (36); (37); (38); (39); (40); (41); (42); (43); (44); (45); (46); (47); (48); (49); (50); (51); (52); (53); (54); (55); (56); (57); (58); (59); (60); (61); (62); (63); (64); (65); (66); (67); (68); (69); (70); (71); (72); (73); (74); (75); (76); (77); (78); (79); (80); (81); (82); (83); (84); (85); (86); (87); (88); (89); (90); (91); (92); (93); (94); (95); (96); (97); (98); (99); (100); (101); (102); (103); (104); (105); (106); (107); (108); (109); (110); (111); (112); (113); (114); (115); (116); (117); (118); (119); (120); (121); (122); (123); (124); (125); (126); (127); (128); (129); (130); (131); (132); (133); (134); (135); (136); (137); (138); (139); (140); (141); (142); (143); (144); (145); (146); (147); (148); (149); (150); (151); (152); (153); (154); (155); (156); (157); (158); (159); (160); (161); (162); (163); (164); (165); (166); (167); (168); (169); (170); (171); (172); (173); (174); (175); (176); (177); (178); (179); (180); (181); (182); (183); (184); (185); (186); (187); (188); (189); (190); (191); (192); (193); (194); (195); (196); (197); (198); (199); (200); (201); (202); (203); (204); (205); (206); (207); (208); (209); (210); (211); (212); (213); (214); (215); (216); (217); (218); (219); (220); (221); (222); (223); (224); (225); (226); (227); (228); (229); (230); (231); (232); (233); (234); (235); (236); (237); (238); (239); (240); (241); (242); (243); (244); (245); (246); (247); (248); (249); (250); (251); (252); (253); (254); (255); (256); (257); (258); (259); (260); (261); (262); (263); (264); (265); (266); (267); (268); (269); (270); (271); (272); (273); (274); (275); (276); (277); (278); (279); (280); (281); (282); (283); (284); (285); (286); (287); (288); (289); (290); (291); (292); (293); (294); (295); (296); (297); (298); (299); (300); (301); (302); (303); (304); (305); (306); (307); (308); (309); (310); (311); (312); (313); (314); (315); (316); (317); (318); (319); (320); (321); (322); (323); (324); (325); (326); (327); (328); (329); (330); (331); (332); (333); (334); (335); (336); (337); (338); (339)]] + +Used Indexes: + - materialize.public.cover_foo (lookup) + +Target cluster: quickstart + +EOF + +query III rowsort +SELECT * FROM cover WHERE foo IN (0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339) AND bar IN (1,2) +---- +1 1 1 +2 2 2 + +# With both `foo` and `qux` covered, the cross product of their two lists is the real answer, +# so the nine lookup values here are inherent rather than incidental. `bar` still contributes +# nothing but a residual predicate. + +statement ok +CREATE INDEX cover_foo_qux ON cover(foo, qux) + +query T multiline +EXPLAIN OPTIMIZED PLAN WITH(humanized expressions, join implementations) AS VERBOSE TEXT FOR +SELECT foo FROM cover WHERE foo IN (1,2,3) AND bar IN (0,1,2,3,4,5,6,7,8,9,10,11) AND qux IN (1,2,3) +---- +Explained Query (fast path): + Project (#0{foo}) + Filter ((#2{bar} = 0) OR (#2{bar} = 1) OR (#2{bar} = 2) OR (#2{bar} = 3) OR (#2{bar} = 4) OR (#2{bar} = 5) OR (#2{bar} = 6) OR (#2{bar} = 7) OR (#2{bar} = 8) OR (#2{bar} = 9) OR (#2{bar} = 10) OR (#2{bar} = 11)) + ReadIndex on=materialize.public.cover cover_foo_qux=[lookup values=[(1, 1); (1, 2); (1, 3); (2, 1); (2, 2); (2, 3); (3, 1); (3, 2); (3, 3)]] + +Used Indexes: + - materialize.public.cover_foo_qux (lookup) + +Target cluster: quickstart + +EOF + +query III rowsort +SELECT * FROM cover WHERE foo IN (1,2,3) AND bar IN (0,1,2,3,4,5,6,7,8,9,10,11) AND qux IN (1,2,3) +---- +1 1 1 +2 2 2 +3 3 3 + +# Two disjoint pair lists, each far past what a disjunctive normal form could hold. Their +# intersection is empty, so no key value satisfies the predicate. Asserted on rows rather +# than on the plan: returning a row here would be a wrong result. + +statement ok +CREATE TABLE pairs (a int, b int) + +statement ok +CREATE INDEX pairs_idx ON pairs(a, b) + +statement ok +INSERT INTO pairs VALUES (5, 5), (5, 6), (7, 8) + +query II rowsort +SELECT * FROM pairs WHERE (a,b) IN ((0,0),(1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(9,9),(10,10),(11,11),(12,12),(13,13),(14,14),(15,15),(16,16),(17,17),(18,18),(19,19),(20,20),(21,21),(22,22),(23,23),(24,24),(25,25),(26,26),(27,27),(28,28),(29,29),(30,30),(31,31),(32,32),(33,33),(34,34),(35,35),(36,36),(37,37),(38,38),(39,39)) AND (a,b) IN ((0,1),(1,2),(2,3),(3,4),(4,5),(5,6),(6,7),(7,8),(8,9),(9,10),(10,11),(11,12),(12,13),(13,14),(14,15),(15,16),(16,17),(17,18),(18,19),(19,20),(20,21),(21,22),(22,23),(23,24),(24,25),(25,26),(26,27),(27,28),(28,29),(29,30),(30,31),(31,32),(32,33),(33,34),(34,35),(35,36),(36,37),(37,38),(38,39),(39,40)) +---- + +# The same shape with short lists; the intersection is still empty. + +query II rowsort +SELECT * FROM pairs WHERE (a,b) IN ((0,0),(5,5)) AND (a,b) IN ((0,1),(5,6)) +----