Skip to main content

risingwave_frontend/optimizer/rule/
index_selection_rule.rs

1// Copyright 2022 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! # Index selection cost matrix
16//!
17//! |`column_idx`| 0   |  1 | 2  | 3  | 4  | remark |
18//! |-----------|-----|----|----|----|----|---|
19//! |Equal      | 1   | 1  | 1  | 1  | 1  | |
20//! |In         | 10  | 8  | 5  | 5  | 5  | take the minimum value with actual in number |
21//! |Range(Two) | 600 | 50 | 20 | 10 | 10 | `RangeTwoSideBound` like a between 1 and 2 |
22//! |Range(One) | 1400| 70 | 25 | 15 | 10 | `RangeOneSideBound` like a > 1, a >= 1, a < 1|
23//! |All        | 4000| 100| 30 | 20 | 10 | |
24//!
25//! ```text
26//! index cost = cost(match type of 0 idx)
27//! * cost(match type of 1 idx)
28//! * ... cost(match type of the last idx)
29//! ```
30//!
31//! ## Example
32//!
33//! Given index order key (a, b, c)
34//!
35//! - For `a = 1 and b = 1 and c = 1`, its cost is 1 = Equal0 * Equal1 * Equal2 = 1
36//! - For `a in (xxx) and b = 1 and c = 1`, its cost is In0 * Equal1 * Equal2 = 10
37//! - For `a = 1 and b in (xxx)`, its cost is Equal0 * In1 * All2 = 1 * 8 * 50 = 400
38//! - For `a between xxx and yyy`, its cost is Range(Two)0 = 600
39//! - For `a = 1 and b between xxx and yyy`, its cost is Equal0 * Range(Two)1 = 50
40//! - For `a = 1 and b > 1`, its cost is Equal0 * Range(One)1 = 70
41//! - For `a = 1`, its cost is 100 = Equal0 * All1 = 100
42//! - For no condition, its cost is All0 = 4000
43//!
44//! With the assumption that the most effective part of a index is its prefix,
45//! cost decreases as `column_idx` increasing.
46//!
47//! For index order key length > 5, we just ignore the rest.
48
49use std::cmp::min;
50use std::collections::hash_map::Entry::{Occupied, Vacant};
51use std::collections::{BTreeMap, HashMap};
52use std::sync::Arc;
53
54use itertools::Itertools;
55use risingwave_common::array::VectorDistanceType;
56use risingwave_common::catalog::Schema;
57use risingwave_common::types::{
58    DataType, Date, Decimal, Int256, Interval, Serial, Time, Timestamp, Timestamptz,
59};
60use risingwave_common::util::iter_util::ZipEqFast;
61use risingwave_pb::plan_common::JoinType;
62use risingwave_sqlparser::ast::AsOf;
63
64use super::prelude::{PlanRef, *};
65use crate::catalog::index_catalog::TableIndex;
66use crate::expr::{
67    Expr, ExprImpl, ExprRewriter, ExprType, ExprVisitor, FunctionCall, InputRef, to_conjunctions,
68    to_disjunctions,
69};
70use crate::optimizer::optimizer_context::OptimizerContextRef;
71use crate::optimizer::plan_node::generic::GenericPlanRef;
72use crate::optimizer::plan_node::{
73    ColumnPruningContext, LogicalJoin, LogicalScan, LogicalUnion, PlanTreeNode, PlanTreeNodeBinary,
74    PredicatePushdown, PredicatePushdownContext, generic,
75};
76use crate::utils::Condition;
77
78const INDEX_MAX_LEN: usize = 5;
79const INDEX_COST_MATRIX: [[usize; INDEX_MAX_LEN]; 5] = [
80    [1, 1, 1, 1, 1],
81    [10, 8, 5, 5, 5],
82    [600, 50, 20, 10, 10],
83    [1400, 70, 25, 15, 10],
84    [4000, 100, 30, 20, 20],
85];
86const LOOKUP_COST_CONST: usize = 3;
87const MAX_COMBINATION_SIZE: usize = 4;
88const MAX_CONJUNCTION_SIZE: usize = 8;
89
90pub struct IndexSelectionRule {}
91
92impl Rule<Logical> for IndexSelectionRule {
93    fn apply(&self, plan: PlanRef) -> Option<PlanRef> {
94        let logical_scan: &LogicalScan = plan.as_logical_scan()?;
95        let indexes = logical_scan.table_indexes();
96        if indexes.is_empty() {
97            return None;
98        }
99        let primary_table_row_size = TableScanIoEstimator::estimate_row_size(logical_scan);
100        let primary_cost = min(
101            self.estimate_table_scan_cost(logical_scan, primary_table_row_size),
102            self.estimate_full_table_scan_cost(logical_scan, primary_table_row_size),
103        );
104
105        // If it is a primary lookup plan, avoid checking other indexes.
106        if primary_cost.primary_lookup {
107            return None;
108        }
109
110        let mut final_plan: PlanRef = logical_scan.clone().into();
111        let mut min_cost = primary_cost.clone();
112
113        for index in indexes {
114            if let Some(index_scan) = logical_scan.to_index_scan_if_index_covered(index) {
115                let index_cost = self.estimate_table_scan_cost(
116                    &index_scan,
117                    TableScanIoEstimator::estimate_row_size(&index_scan),
118                );
119
120                if index_cost.le(&min_cost) {
121                    min_cost = index_cost;
122                    final_plan = index_scan.into();
123                }
124            } else if let Some((index_lookup, lookup_cost)) =
125                self.gen_index_lookup(logical_scan, index)
126            {
127                // non-covering index selection
128                if lookup_cost.le(&min_cost) {
129                    min_cost = lookup_cost;
130                    final_plan = index_lookup;
131                }
132            }
133        }
134
135        if let Some((merge_index, merge_index_cost)) = self.index_merge_selection(logical_scan)
136            && merge_index_cost.le(&min_cost)
137        {
138            min_cost = merge_index_cost;
139            final_plan = merge_index;
140        }
141
142        if min_cost == primary_cost {
143            None
144        } else {
145            Some(final_plan)
146        }
147    }
148}
149
150struct IndexPredicateRewriter<'a> {
151    p2s_mapping: &'a BTreeMap<usize, usize>,
152    function_mapping: &'a HashMap<FunctionCall, usize>,
153    offset: usize,
154    covered_by_index: bool,
155}
156
157impl<'a> IndexPredicateRewriter<'a> {
158    fn new(
159        p2s_mapping: &'a BTreeMap<usize, usize>,
160        function_mapping: &'a HashMap<FunctionCall, usize>,
161        offset: usize,
162    ) -> Self {
163        Self {
164            p2s_mapping,
165            function_mapping,
166            offset,
167            covered_by_index: true,
168        }
169    }
170
171    fn covered_by_index(&self) -> bool {
172        self.covered_by_index
173    }
174}
175
176impl ExprRewriter for IndexPredicateRewriter<'_> {
177    fn rewrite_input_ref(&mut self, input_ref: InputRef) -> ExprImpl {
178        // transform primary predicate to index predicate if it can
179        if self.p2s_mapping.contains_key(&input_ref.index) {
180            InputRef::new(
181                *self.p2s_mapping.get(&input_ref.index()).unwrap(),
182                input_ref.return_type(),
183            )
184            .into()
185        } else {
186            self.covered_by_index = false;
187            InputRef::new(input_ref.index() + self.offset, input_ref.return_type()).into()
188        }
189    }
190
191    fn rewrite_function_call(&mut self, func_call: FunctionCall) -> ExprImpl {
192        if let Some(index) = self.function_mapping.get(&func_call) {
193            return InputRef::new(*index, func_call.return_type()).into();
194        }
195
196        let (func_type, inputs, ret) = func_call.decompose();
197        let inputs = inputs
198            .into_iter()
199            .map(|expr| self.rewrite_expr(expr))
200            .collect();
201        FunctionCall::new_unchecked(func_type, inputs, ret).into()
202    }
203}
204
205impl IndexSelectionRule {
206    /// Returns `None` if the index does not cover the primary key of the primary table, in which
207    /// case it cannot be used to look up the primary table.
208    fn gen_index_lookup(
209        &self,
210        logical_scan: &LogicalScan,
211        index: &TableIndex,
212    ) -> Option<(PlanRef, IndexCost)> {
213        let primary_table_pk_ref = index.primary_table_pk_ref_to_index_table()?;
214
215        // 1. logical_scan ->  logical_join
216        //                      /        \
217        //                index_scan   primary_table_scan
218        let index_scan = LogicalScan::create(
219            index.index_table.clone(),
220            logical_scan.ctx(),
221            logical_scan.as_of(),
222        );
223        // We use `schema.len` instead of `index_item.len` here,
224        // because schema contains system columns like `_rw_timestamp` column which is not represented in the index item.
225        let offset = index_scan.table().columns().len();
226
227        let primary_table_scan = LogicalScan::create(
228            index.primary_table.clone(),
229            logical_scan.ctx(),
230            logical_scan.as_of(),
231        );
232
233        let predicate = logical_scan.predicate().clone();
234        let mut rewriter = IndexPredicateRewriter::new(
235            index.primary_to_secondary_mapping(),
236            index.function_mapping(),
237            offset,
238        );
239        let new_predicate = predicate.rewrite_expr(&mut rewriter);
240
241        let conjunctions = primary_table_pk_ref
242            .iter()
243            .zip_eq_fast(index.primary_table.pk.iter())
244            .map(|(x, y)| {
245                Self::create_null_safe_equal_expr(
246                    x.column_index,
247                    index.index_table.columns[x.column_index]
248                        .data_type()
249                        .clone(),
250                    y.column_index + offset,
251                    index.primary_table.columns[y.column_index]
252                        .data_type()
253                        .clone(),
254                )
255            })
256            .chain(new_predicate)
257            .collect_vec();
258        let on = Condition { conjunctions };
259        let join: PlanRef = LogicalJoin::new(
260            index_scan.into(),
261            primary_table_scan.into(),
262            JoinType::Inner,
263            on,
264        )
265        .into();
266
267        // 2. push down predicate, so we can calculate the cost of index lookup
268        let join_ref = join.predicate_pushdown(
269            Condition::true_cond(),
270            &mut PredicatePushdownContext::new(join.clone()),
271        );
272
273        let join_with_predicate_push_down =
274            join_ref.as_logical_join().expect("must be a logical join");
275        let new_join_left = join_with_predicate_push_down.left();
276        let index_scan_with_predicate: &LogicalScan = new_join_left
277            .as_logical_scan()
278            .expect("must be a logical scan");
279
280        // 3. calculate index cost, index lookup use primary table to estimate row size.
281        let index_cost = self.estimate_table_scan_cost(
282            index_scan_with_predicate,
283            TableScanIoEstimator::estimate_row_size(logical_scan),
284        );
285        // lookup cost = index cost * LOOKUP_COST_CONST
286        let lookup_cost = index_cost.mul(&IndexCost::new(LOOKUP_COST_CONST, false));
287
288        // 4. keep the same schema with original logical_scan
289        let scan_output_col_idx = logical_scan.output_col_idx();
290        let lookup_join = join_ref.prune_col(
291            &scan_output_col_idx
292                .iter()
293                .map(|&col_idx| col_idx + offset)
294                .collect_vec(),
295            &mut ColumnPruningContext::new(join_ref.clone()),
296        );
297
298        Some((lookup_join, lookup_cost))
299    }
300
301    /// Index Merge Selection
302    /// Deal with predicate like a = 1 or b = 1
303    /// Merge index scans from a table, currently merge is union semantic.
304    fn index_merge_selection(&self, logical_scan: &LogicalScan) -> Option<(PlanRef, IndexCost)> {
305        let predicate = logical_scan.predicate().clone();
306        // Index merge is kind of index lookup join so use primary table row size to estimate index
307        // cost.
308        let primary_table_row_size = TableScanIoEstimator::estimate_row_size(logical_scan);
309        // 1. choose lowest cost index merge path
310        let paths = self.gen_paths(
311            &predicate.conjunctions,
312            logical_scan,
313            primary_table_row_size,
314        );
315        let (index_access, index_access_cost) =
316            self.choose_min_cost_path(&paths, primary_table_row_size)?;
317
318        // 2. lookup primary table
319        // the schema of index_access is the order key of primary table .
320        let schema: &Schema = index_access.schema();
321        let index_access_len = schema.len();
322
323        let mut shift_input_ref_rewriter = ShiftInputRefRewriter {
324            offset: index_access_len,
325        };
326        let new_predicate = predicate.rewrite_expr(&mut shift_input_ref_rewriter);
327
328        let primary_table = logical_scan.table();
329
330        let primary_table_scan = LogicalScan::create(
331            logical_scan.table().clone(),
332            logical_scan.ctx(),
333            logical_scan.as_of(),
334        );
335
336        let conjunctions = primary_table
337            .pk
338            .iter()
339            .enumerate()
340            .map(|(x, y)| {
341                Self::create_null_safe_equal_expr(
342                    x,
343                    schema.fields[x].data_type.clone(),
344                    y.column_index + index_access_len,
345                    primary_table.columns[y.column_index].data_type.clone(),
346                )
347            })
348            .chain(new_predicate)
349            .collect_vec();
350
351        let on = Condition { conjunctions };
352        let join: PlanRef =
353            LogicalJoin::new(index_access, primary_table_scan.into(), JoinType::Inner, on).into();
354
355        // 3 push down predicate
356        let join_ref = join.predicate_pushdown(
357            Condition::true_cond(),
358            &mut PredicatePushdownContext::new(join.clone()),
359        );
360
361        // 4. keep the same schema with original logical_scan
362        let scan_output_col_idx = logical_scan.output_col_idx();
363        let lookup_join = join_ref.prune_col(
364            &scan_output_col_idx
365                .iter()
366                .map(|&col_idx| col_idx + index_access_len)
367                .collect_vec(),
368            &mut ColumnPruningContext::new(join_ref.clone()),
369        );
370
371        Some((
372            lookup_join,
373            index_access_cost.mul(&IndexCost::new(LOOKUP_COST_CONST, false)),
374        ))
375    }
376
377    /// Generate possible paths that can be used to access.
378    /// The schema of output is the order key of primary table, so it can be used to lookup primary
379    /// table later.
380    /// Method `gen_paths` handles the complex condition recursively which may contains nested `AND`
381    /// and `OR`. However, Method `gen_index_path` handles one arm of an OR clause which is a
382    /// basic unit for index selection.
383    fn gen_paths(
384        &self,
385        conjunctions: &[ExprImpl],
386        logical_scan: &LogicalScan,
387        primary_table_row_size: usize,
388    ) -> Vec<PlanRef> {
389        let mut result = vec![];
390
391        // split by OR clause, the not_or_conjunctions could be used to generate index path by combining with each arm of OR clause.
392        let (or_conjunctions, not_or_conjunctions): (Vec<ExprImpl>, Vec<ExprImpl>) =
393            conjunctions.iter().cloned().partition(|expr| {
394                if let ExprImpl::FunctionCall(function_call) = expr
395                    && function_call.func_type() == ExprType::Or
396                {
397                    true
398                } else {
399                    false
400                }
401            });
402        // Only consider eq ,in and cmp condition for not_or_conjunctions
403        let interest_conjunctions: Vec<ExprImpl> = not_or_conjunctions
404            .into_iter()
405            .filter(|expr| {
406                expr.as_eq_const().is_some()
407                    || expr
408                        .as_in_const_list()
409                        .or_else(|| expr.as_some_eq_const_list())
410                        .is_some()
411                    || expr.as_comparison_const().is_some()
412            })
413            .collect();
414
415        for expr in or_conjunctions {
416            // it must be OR clause!
417            let mut index_to_be_merged = vec![];
418
419            let disjunctions = to_disjunctions(expr.clone());
420
421            let extended_disjunctions = disjunctions
422                .into_iter()
423                .map(|expr| {
424                    if interest_conjunctions.is_empty() {
425                        expr
426                    } else {
427                        ExprImpl::FunctionCall(
428                            FunctionCall::new_unchecked(
429                                ExprType::And,
430                                vec![expr]
431                                    .into_iter()
432                                    .chain(interest_conjunctions.iter().cloned())
433                                    .collect(),
434                                DataType::Boolean,
435                            )
436                            .into(),
437                        )
438                    }
439                })
440                .collect_vec();
441
442            let (map, others) = self.clustering_disjunction(extended_disjunctions);
443            let iter = map
444                .into_iter()
445                .map(|(column_index, expr)| (Some(column_index), expr))
446                .chain(others.into_iter().map(|expr| (None, expr)));
447            for (column_index, expr) in iter {
448                let mut index_paths = vec![];
449                let conjunctions = to_conjunctions(expr);
450                index_paths.extend(self.gen_index_path(column_index, &conjunctions, logical_scan));
451                // complex condition, recursively gen paths
452                if conjunctions.len() > 1 {
453                    index_paths.extend(self.gen_paths(
454                        &conjunctions,
455                        logical_scan,
456                        primary_table_row_size,
457                    ));
458                }
459
460                match self.choose_min_cost_path(&index_paths, primary_table_row_size) {
461                    None => {
462                        // One arm of OR clause can't use index, bail out
463                        index_to_be_merged.clear();
464                        break;
465                    }
466                    Some((path, _)) => index_to_be_merged.push(path),
467                }
468            }
469
470            if let Some(path) = self.merge(index_to_be_merged) {
471                result.push(path)
472            }
473        }
474
475        result
476    }
477
478    /// Clustering disjunction or expr by column index. If expr is complex, classify them as others.
479    ///
480    /// a = 1, b = 2, b = 3 -> map: [a, (a = 1)], [b, (b = 2 or b = 3)], others: []
481    ///
482    /// a = 1, (b = 2 and c = 3) -> map: [a, (a = 1)], others:
483    ///
484    /// (a > 1 and a < 8) or (c > 1 and c < 8)
485    /// -> map: [], others: [(a > 1 and a < 8), (c > 1 and c < 8)]
486    fn clustering_disjunction(
487        &self,
488        disjunctions: Vec<ExprImpl>,
489    ) -> (HashMap<usize, ExprImpl>, Vec<ExprImpl>) {
490        let mut map: HashMap<usize, ExprImpl> = HashMap::new();
491        let mut others = vec![];
492        for expr in disjunctions {
493            let idx = {
494                if let Some((input_ref, _const_expr)) = expr.as_eq_const() {
495                    Some(input_ref.index)
496                } else if let Some((input_ref, _in_const_list)) = expr
497                    .as_in_const_list()
498                    .or_else(|| expr.as_some_eq_const_list())
499                {
500                    Some(input_ref.index)
501                } else if let Some((input_ref, _op, _const_expr)) = expr.as_comparison_const() {
502                    Some(input_ref.index)
503                } else {
504                    None
505                }
506            };
507
508            if let Some(idx) = idx {
509                match map.entry(idx) {
510                    Occupied(mut entry) => {
511                        let expr2: ExprImpl = entry.get().to_owned();
512                        let or_expr = ExprImpl::FunctionCall(
513                            FunctionCall::new_unchecked(
514                                ExprType::Or,
515                                vec![expr, expr2],
516                                DataType::Boolean,
517                            )
518                            .into(),
519                        );
520                        entry.insert(or_expr);
521                    }
522                    Vacant(entry) => {
523                        entry.insert(expr);
524                    }
525                };
526            } else {
527                others.push(expr);
528                continue;
529            }
530        }
531
532        (map, others)
533    }
534
535    /// Given a conjunctions from one arm of an OR clause (basic unit to index selection), generate
536    /// all matching index path (including primary index) for the relation.
537    /// `column_index` (refers to primary table) is a hint can be used to prune index.
538    /// Steps:
539    /// 1. Take the combination of `conjunctions` to extract the potential clauses.
540    /// 2. For each potential clauses, generate index path if it can.
541    fn gen_index_path(
542        &self,
543        column_index: Option<usize>,
544        conjunctions: &[ExprImpl],
545        logical_scan: &LogicalScan,
546    ) -> Vec<PlanRef> {
547        // Assumption: use at most `MAX_COMBINATION_SIZE` clauses, we can determine which is the
548        // best index.
549        let combinations = conjunctions
550            .iter()
551            .take(min(conjunctions.len(), MAX_CONJUNCTION_SIZE))
552            .combinations(min(conjunctions.len(), MAX_COMBINATION_SIZE))
553            .collect_vec();
554
555        let mut result = vec![];
556
557        for index in logical_scan.table_indexes() {
558            if let Some(column_index) = column_index {
559                assert_eq!(conjunctions.len(), 1);
560                let p2s_mapping = index.primary_to_secondary_mapping();
561                match p2s_mapping.get(&column_index) {
562                    None => continue, // not found, prune this index
563                    Some(&idx) => {
564                        if index.index_table.pk()[0].column_index != idx {
565                            // not match, prune this index
566                            continue;
567                        }
568                    }
569                }
570            }
571
572            // try secondary index
573            for conj in &combinations {
574                let condition = Condition {
575                    conjunctions: conj.iter().map(|&x| x.to_owned()).collect(),
576                };
577                if let Some(index_access) = self.build_index_access(
578                    index.clone(),
579                    condition,
580                    logical_scan.ctx().clone(),
581                    logical_scan.as_of().clone(),
582                ) {
583                    result.push(index_access);
584                }
585            }
586        }
587
588        // try primary index
589        let primary_table = logical_scan.table();
590        if let Some(idx) = column_index {
591            assert_eq!(conjunctions.len(), 1);
592            if primary_table.pk[0].column_index != idx {
593                return result;
594            }
595        }
596
597        let primary_access = generic::TableScan::new(
598            primary_table
599                .pk
600                .iter()
601                .map(|x| x.column_index)
602                .collect_vec(),
603            logical_scan.table().clone(),
604            vec![],
605            vec![],
606            logical_scan.ctx(),
607            Condition {
608                conjunctions: conjunctions.to_vec(),
609            },
610            logical_scan.as_of(),
611        );
612
613        result.push(primary_access.into());
614
615        result
616    }
617
618    /// build index access if predicate (refers to primary table) is covered by index
619    fn build_index_access(
620        &self,
621        index: Arc<TableIndex>,
622        predicate: Condition,
623        ctx: OptimizerContextRef,
624        as_of: Option<AsOf>,
625    ) -> Option<PlanRef> {
626        let mut rewriter = IndexPredicateRewriter::new(
627            index.primary_to_secondary_mapping(),
628            index.function_mapping(),
629            0,
630        );
631        let new_predicate = predicate.rewrite_expr(&mut rewriter);
632
633        // check condition is covered by index.
634        if !rewriter.covered_by_index() {
635            return None;
636        }
637
638        // The index access must output the primary key of the primary table for the lookup.
639        let primary_table_pk_ref = index.primary_table_pk_ref_to_index_table()?;
640
641        Some(
642            generic::TableScan::new(
643                primary_table_pk_ref
644                    .iter()
645                    .map(|x| x.column_index)
646                    .collect_vec(),
647                index.index_table.clone(),
648                vec![],
649                vec![],
650                ctx,
651                new_predicate,
652                as_of,
653            )
654            .into(),
655        )
656    }
657
658    fn merge(&self, paths: Vec<PlanRef>) -> Option<PlanRef> {
659        if paths.is_empty() {
660            return None;
661        }
662
663        let new_paths = paths
664            .iter()
665            .flat_map(|path| {
666                if let Some(union) = path.as_logical_union() {
667                    union.inputs().to_vec()
668                } else if let Some(_scan) = path.as_logical_scan() {
669                    vec![path.clone()]
670                } else {
671                    unreachable!();
672                }
673            })
674            .sorted_by(|a, b| {
675                // sort inputs to make plan deterministic
676                a.as_logical_scan()
677                    .expect("expect to be a logical scan")
678                    .table_name()
679                    .cmp(
680                        b.as_logical_scan()
681                            .expect("expect to be a logical scan")
682                            .table_name(),
683                    )
684            })
685            .collect_vec();
686
687        Some(LogicalUnion::create(false, new_paths))
688    }
689
690    fn choose_min_cost_path(
691        &self,
692        paths: &[PlanRef],
693        primary_table_row_size: usize,
694    ) -> Option<(PlanRef, IndexCost)> {
695        paths
696            .iter()
697            .map(|path| {
698                if let Some(scan) = path.as_logical_scan() {
699                    let cost = self.estimate_table_scan_cost(scan, primary_table_row_size);
700                    (scan.clone().into(), cost)
701                } else if let Some(union) = path.as_logical_union() {
702                    let cost = union
703                        .inputs()
704                        .iter()
705                        .map(|input| {
706                            self.estimate_table_scan_cost(
707                                input.as_logical_scan().expect("expect to be a scan"),
708                                primary_table_row_size,
709                            )
710                        })
711                        .reduce(|a, b| a.add(&b))
712                        .unwrap();
713                    (union.clone().into(), cost)
714                } else {
715                    unreachable!()
716                }
717            })
718            .min_by(|(_, cost1), (_, cost2)| Ord::cmp(cost1, cost2))
719    }
720
721    pub(crate) fn estimate_table_scan_cost(
722        &self,
723        scan: &LogicalScan,
724        row_size: usize,
725    ) -> IndexCost {
726        let mut table_scan_io_estimator = TableScanIoEstimator::new(scan, row_size);
727        table_scan_io_estimator.estimate(scan.predicate())
728    }
729
730    pub(crate) fn estimate_full_table_scan_cost(
731        &self,
732        scan: &LogicalScan,
733        row_size: usize,
734    ) -> IndexCost {
735        let mut table_scan_io_estimator = TableScanIoEstimator::new(scan, row_size);
736        table_scan_io_estimator.estimate(&Condition::true_cond())
737    }
738
739    pub fn create_null_safe_equal_expr(
740        left: usize,
741        left_data_type: DataType,
742        right: usize,
743        right_data_type: DataType,
744    ) -> ExprImpl {
745        ExprImpl::FunctionCall(Box::new(FunctionCall::new_unchecked(
746            ExprType::IsNotDistinctFrom,
747            vec![
748                ExprImpl::InputRef(Box::new(InputRef::new(left, left_data_type))),
749                ExprImpl::InputRef(Box::new(InputRef::new(right, right_data_type))),
750            ],
751            DataType::Boolean,
752        )))
753    }
754}
755
756pub(crate) struct TableScanIoEstimator<'a> {
757    table_scan: &'a LogicalScan,
758    row_size: usize,
759    cost: Option<IndexCost>,
760}
761
762impl<'a> TableScanIoEstimator<'a> {
763    pub fn new(table_scan: &'a LogicalScan, row_size: usize) -> Self {
764        Self {
765            table_scan,
766            row_size,
767            cost: None,
768        }
769    }
770
771    pub fn estimate_row_size(table_scan: &LogicalScan) -> usize {
772        // 5 for table_id + 1 for vnode + 8 for epoch
773        let row_meta_field_estimate_size = 14_usize;
774        let table = table_scan.table();
775        row_meta_field_estimate_size
776            + table
777                .columns
778                .iter()
779                // add order key twice for its appearance both in key and value
780                .chain(table.pk.iter().map(|x| &table.columns[x.column_index]))
781                .map(|x| TableScanIoEstimator::estimate_data_type_size(&x.data_type))
782                .sum::<usize>()
783    }
784
785    fn estimate_data_type_size(data_type: &DataType) -> usize {
786        use std::mem::size_of;
787
788        match data_type {
789            DataType::Boolean => size_of::<bool>(),
790            DataType::Int16 => size_of::<i16>(),
791            DataType::Int32 => size_of::<i32>(),
792            DataType::Int64 => size_of::<i64>(),
793            DataType::Serial => size_of::<Serial>(),
794            DataType::Float32 => size_of::<f32>(),
795            DataType::Float64 => size_of::<f64>(),
796            DataType::Decimal => size_of::<Decimal>(),
797            DataType::Date => size_of::<Date>(),
798            DataType::Time => size_of::<Time>(),
799            DataType::Timestamp => size_of::<Timestamp>(),
800            DataType::Timestamptz => size_of::<Timestamptz>(),
801            DataType::Interval => size_of::<Interval>(),
802            DataType::Int256 => Int256::size(),
803            DataType::Varchar => 20,
804            DataType::Bytea => 20,
805            DataType::Jsonb => 20,
806            DataType::Variant => 20,
807            DataType::Struct { .. } => 20,
808            DataType::List { .. } => 20,
809            DataType::Map(_) => 20,
810            DataType::Vector(d) => d * size_of::<VectorDistanceType>(),
811        }
812    }
813
814    pub fn estimate(&mut self, predicate: &Condition) -> IndexCost {
815        // try to deal with OR condition
816        if predicate.conjunctions.len() == 1 {
817            self.visit_expr(&predicate.conjunctions[0]);
818            self.cost.take().unwrap_or_default()
819        } else {
820            self.estimate_conjunctions(&predicate.conjunctions)
821        }
822    }
823
824    fn estimate_conjunctions(&mut self, conjunctions: &[ExprImpl]) -> IndexCost {
825        let mut new_conjunctions = conjunctions.to_owned();
826
827        let mut match_item_vec = vec![];
828
829        for column_idx in self.table_scan.table().order_column_indices() {
830            let match_item = self.match_index_column(column_idx, &mut new_conjunctions);
831            // seeing range, we don't need to match anymore.
832            let should_break = match match_item {
833                MatchItem::Equal | MatchItem::In(_) => false,
834                MatchItem::RangeOneSideBound | MatchItem::RangeTwoSideBound | MatchItem::All => {
835                    true
836                }
837            };
838            match_item_vec.push(match_item);
839            if should_break {
840                break;
841            }
842        }
843
844        // The product of an empty iterator is 1: a table without any order column holds at most
845        // one row, so scanning it is as cheap as a primary lookup.
846        let index_cost: usize = match_item_vec
847            .iter()
848            .enumerate()
849            .take(INDEX_MAX_LEN)
850            .map(|(i, match_item)| match match_item {
851                MatchItem::Equal => INDEX_COST_MATRIX[0][i],
852                MatchItem::In(num) => min(INDEX_COST_MATRIX[1][i], *num),
853                MatchItem::RangeTwoSideBound => INDEX_COST_MATRIX[2][i],
854                MatchItem::RangeOneSideBound => INDEX_COST_MATRIX[3][i],
855                MatchItem::All => INDEX_COST_MATRIX[4][i],
856            })
857            .product();
858
859        // If `index_cost` equals 1, it is a primary lookup
860        let primary_lookup = index_cost == 1;
861
862        IndexCost::new(index_cost, primary_lookup)
863            .mul(&IndexCost::new(self.row_size, primary_lookup))
864    }
865
866    fn match_index_column(
867        &mut self,
868        column_idx: usize,
869        conjunctions: &mut Vec<ExprImpl>,
870    ) -> MatchItem {
871        // Equal
872        for (i, expr) in conjunctions.iter().enumerate() {
873            if let Some((input_ref, _const_expr)) = expr.as_eq_const()
874                && input_ref.index == column_idx
875            {
876                conjunctions.remove(i);
877                return MatchItem::Equal;
878            }
879        }
880
881        // In
882        for (i, expr) in conjunctions.iter().enumerate() {
883            if let Some((input_ref, in_const_list)) = expr
884                .as_in_const_list()
885                .or_else(|| expr.as_some_eq_const_list())
886                && input_ref.index == column_idx
887            {
888                conjunctions.remove(i);
889                return MatchItem::In(in_const_list.len());
890            }
891        }
892
893        // Range
894        let mut left_side_bound = false;
895        let mut right_side_bound = false;
896        let mut i = 0;
897        while i < conjunctions.len() {
898            let expr = &conjunctions[i];
899            if let Some((input_ref, op, _const_expr)) = expr.as_comparison_const()
900                && input_ref.index == column_idx
901            {
902                conjunctions.remove(i);
903                match op {
904                    ExprType::LessThan | ExprType::LessThanOrEqual => right_side_bound = true,
905                    ExprType::GreaterThan | ExprType::GreaterThanOrEqual => left_side_bound = true,
906                    _ => unreachable!(),
907                };
908            } else {
909                i += 1;
910            }
911        }
912
913        if left_side_bound && right_side_bound {
914            MatchItem::RangeTwoSideBound
915        } else if left_side_bound || right_side_bound {
916            MatchItem::RangeOneSideBound
917        } else {
918            MatchItem::All
919        }
920    }
921}
922
923enum MatchItem {
924    Equal,
925    In(usize),
926    RangeTwoSideBound,
927    RangeOneSideBound,
928    All,
929}
930
931#[derive(PartialEq, Eq, Hash, Clone, Debug, PartialOrd, Ord)]
932pub(crate) struct IndexCost {
933    cost: usize,
934    pub(crate) primary_lookup: bool,
935}
936
937impl Default for IndexCost {
938    fn default() -> Self {
939        Self {
940            cost: IndexCost::maximum(),
941            primary_lookup: false,
942        }
943    }
944}
945
946impl IndexCost {
947    fn new(cost: usize, primary_lookup: bool) -> IndexCost {
948        Self {
949            cost: min(cost, IndexCost::maximum()),
950            primary_lookup,
951        }
952    }
953
954    fn maximum() -> usize {
955        10000000
956    }
957
958    fn add(&self, other: &IndexCost) -> IndexCost {
959        IndexCost::new(
960            self.cost
961                .checked_add(other.cost)
962                .unwrap_or_else(IndexCost::maximum),
963            self.primary_lookup && other.primary_lookup,
964        )
965    }
966
967    fn mul(&self, other: &IndexCost) -> IndexCost {
968        IndexCost::new(
969            self.cost
970                .checked_mul(other.cost)
971                .unwrap_or_else(IndexCost::maximum),
972            self.primary_lookup && other.primary_lookup,
973        )
974    }
975
976    pub(crate) fn le(&self, other: &IndexCost) -> bool {
977        self.cost < other.cost
978    }
979}
980
981impl ExprVisitor for TableScanIoEstimator<'_> {
982    fn visit_function_call(&mut self, func_call: &FunctionCall) {
983        let cost = match func_call.func_type() {
984            ExprType::Or => func_call
985                .inputs()
986                .iter()
987                .map(|x| {
988                    let mut estimator = TableScanIoEstimator::new(self.table_scan, self.row_size);
989                    estimator.visit_expr(x);
990                    estimator.cost.take().unwrap_or_default()
991                })
992                .reduce(|x, y| x.add(&y))
993                .unwrap(),
994            ExprType::And => self.estimate_conjunctions(func_call.inputs()),
995            _ => {
996                let single = vec![ExprImpl::FunctionCall(func_call.clone().into())];
997                self.estimate_conjunctions(&single)
998            }
999        };
1000        self.cost = Some(cost);
1001    }
1002}
1003
1004struct ShiftInputRefRewriter {
1005    offset: usize,
1006}
1007impl ExprRewriter for ShiftInputRefRewriter {
1008    fn rewrite_input_ref(&mut self, input_ref: InputRef) -> ExprImpl {
1009        InputRef::new(input_ref.index() + self.offset, input_ref.return_type()).into()
1010    }
1011}
1012
1013impl IndexSelectionRule {
1014    pub fn create() -> BoxedRule {
1015        Box::new(IndexSelectionRule {})
1016    }
1017}