1use 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 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 {
125 let (index_lookup, lookup_cost) = self.gen_index_lookup(logical_scan, index);
127 if lookup_cost.le(&min_cost) {
128 min_cost = lookup_cost;
129 final_plan = index_lookup;
130 }
131 }
132 }
133
134 if let Some((merge_index, merge_index_cost)) = self.index_merge_selection(logical_scan)
135 && merge_index_cost.le(&min_cost)
136 {
137 min_cost = merge_index_cost;
138 final_plan = merge_index;
139 }
140
141 if min_cost == primary_cost {
142 None
143 } else {
144 Some(final_plan)
145 }
146 }
147}
148
149struct IndexPredicateRewriter<'a> {
150 p2s_mapping: &'a BTreeMap<usize, usize>,
151 function_mapping: &'a HashMap<FunctionCall, usize>,
152 offset: usize,
153 covered_by_index: bool,
154}
155
156impl<'a> IndexPredicateRewriter<'a> {
157 fn new(
158 p2s_mapping: &'a BTreeMap<usize, usize>,
159 function_mapping: &'a HashMap<FunctionCall, usize>,
160 offset: usize,
161 ) -> Self {
162 Self {
163 p2s_mapping,
164 function_mapping,
165 offset,
166 covered_by_index: true,
167 }
168 }
169
170 fn covered_by_index(&self) -> bool {
171 self.covered_by_index
172 }
173}
174
175impl ExprRewriter for IndexPredicateRewriter<'_> {
176 fn rewrite_input_ref(&mut self, input_ref: InputRef) -> ExprImpl {
177 if self.p2s_mapping.contains_key(&input_ref.index) {
179 InputRef::new(
180 *self.p2s_mapping.get(&input_ref.index()).unwrap(),
181 input_ref.return_type(),
182 )
183 .into()
184 } else {
185 self.covered_by_index = false;
186 InputRef::new(input_ref.index() + self.offset, input_ref.return_type()).into()
187 }
188 }
189
190 fn rewrite_function_call(&mut self, func_call: FunctionCall) -> ExprImpl {
191 if let Some(index) = self.function_mapping.get(&func_call) {
192 return InputRef::new(*index, func_call.return_type()).into();
193 }
194
195 let (func_type, inputs, ret) = func_call.decompose();
196 let inputs = inputs
197 .into_iter()
198 .map(|expr| self.rewrite_expr(expr))
199 .collect();
200 FunctionCall::new_unchecked(func_type, inputs, ret).into()
201 }
202}
203
204impl IndexSelectionRule {
205 fn gen_index_lookup(
206 &self,
207 logical_scan: &LogicalScan,
208 index: &TableIndex,
209 ) -> (PlanRef, IndexCost) {
210 let index_scan = LogicalScan::create(
214 index.index_table.clone(),
215 logical_scan.ctx(),
216 logical_scan.as_of(),
217 );
218 let offset = index_scan.table().columns().len();
221
222 let primary_table_scan = LogicalScan::create(
223 index.primary_table.clone(),
224 logical_scan.ctx(),
225 logical_scan.as_of(),
226 );
227
228 let predicate = logical_scan.predicate().clone();
229 let mut rewriter = IndexPredicateRewriter::new(
230 index.primary_to_secondary_mapping(),
231 index.function_mapping(),
232 offset,
233 );
234 let new_predicate = predicate.rewrite_expr(&mut rewriter);
235
236 let conjunctions = index
237 .primary_table_pk_ref_to_index_table()
238 .iter()
239 .zip_eq_fast(index.primary_table.pk.iter())
240 .map(|(x, y)| {
241 Self::create_null_safe_equal_expr(
242 x.column_index,
243 index.index_table.columns[x.column_index]
244 .data_type()
245 .clone(),
246 y.column_index + offset,
247 index.primary_table.columns[y.column_index]
248 .data_type()
249 .clone(),
250 )
251 })
252 .chain(new_predicate)
253 .collect_vec();
254 let on = Condition { conjunctions };
255 let join: PlanRef = LogicalJoin::new(
256 index_scan.into(),
257 primary_table_scan.into(),
258 JoinType::Inner,
259 on,
260 )
261 .into();
262
263 let join_ref = join.predicate_pushdown(
265 Condition::true_cond(),
266 &mut PredicatePushdownContext::new(join.clone()),
267 );
268
269 let join_with_predicate_push_down =
270 join_ref.as_logical_join().expect("must be a logical join");
271 let new_join_left = join_with_predicate_push_down.left();
272 let index_scan_with_predicate: &LogicalScan = new_join_left
273 .as_logical_scan()
274 .expect("must be a logical scan");
275
276 let index_cost = self.estimate_table_scan_cost(
278 index_scan_with_predicate,
279 TableScanIoEstimator::estimate_row_size(logical_scan),
280 );
281 let lookup_cost = index_cost.mul(&IndexCost::new(LOOKUP_COST_CONST, false));
283
284 let scan_output_col_idx = logical_scan.output_col_idx();
286 let lookup_join = join_ref.prune_col(
287 &scan_output_col_idx
288 .iter()
289 .map(|&col_idx| col_idx + offset)
290 .collect_vec(),
291 &mut ColumnPruningContext::new(join_ref.clone()),
292 );
293
294 (lookup_join, lookup_cost)
295 }
296
297 fn index_merge_selection(&self, logical_scan: &LogicalScan) -> Option<(PlanRef, IndexCost)> {
301 let predicate = logical_scan.predicate().clone();
302 let primary_table_row_size = TableScanIoEstimator::estimate_row_size(logical_scan);
305 let paths = self.gen_paths(
307 &predicate.conjunctions,
308 logical_scan,
309 primary_table_row_size,
310 );
311 let (index_access, index_access_cost) =
312 self.choose_min_cost_path(&paths, primary_table_row_size)?;
313
314 let schema: &Schema = index_access.schema();
317 let index_access_len = schema.len();
318
319 let mut shift_input_ref_rewriter = ShiftInputRefRewriter {
320 offset: index_access_len,
321 };
322 let new_predicate = predicate.rewrite_expr(&mut shift_input_ref_rewriter);
323
324 let primary_table = logical_scan.table();
325
326 let primary_table_scan = LogicalScan::create(
327 logical_scan.table().clone(),
328 logical_scan.ctx(),
329 logical_scan.as_of(),
330 );
331
332 let conjunctions = primary_table
333 .pk
334 .iter()
335 .enumerate()
336 .map(|(x, y)| {
337 Self::create_null_safe_equal_expr(
338 x,
339 schema.fields[x].data_type.clone(),
340 y.column_index + index_access_len,
341 primary_table.columns[y.column_index].data_type.clone(),
342 )
343 })
344 .chain(new_predicate)
345 .collect_vec();
346
347 let on = Condition { conjunctions };
348 let join: PlanRef =
349 LogicalJoin::new(index_access, primary_table_scan.into(), JoinType::Inner, on).into();
350
351 let join_ref = join.predicate_pushdown(
353 Condition::true_cond(),
354 &mut PredicatePushdownContext::new(join.clone()),
355 );
356
357 let scan_output_col_idx = logical_scan.output_col_idx();
359 let lookup_join = join_ref.prune_col(
360 &scan_output_col_idx
361 .iter()
362 .map(|&col_idx| col_idx + index_access_len)
363 .collect_vec(),
364 &mut ColumnPruningContext::new(join_ref.clone()),
365 );
366
367 Some((
368 lookup_join,
369 index_access_cost.mul(&IndexCost::new(LOOKUP_COST_CONST, false)),
370 ))
371 }
372
373 fn gen_paths(
380 &self,
381 conjunctions: &[ExprImpl],
382 logical_scan: &LogicalScan,
383 primary_table_row_size: usize,
384 ) -> Vec<PlanRef> {
385 let mut result = vec![];
386
387 let (or_conjunctions, not_or_conjunctions): (Vec<ExprImpl>, Vec<ExprImpl>) =
389 conjunctions.iter().cloned().partition(|expr| {
390 if let ExprImpl::FunctionCall(function_call) = expr
391 && function_call.func_type() == ExprType::Or
392 {
393 true
394 } else {
395 false
396 }
397 });
398 let interest_conjunctions: Vec<ExprImpl> = not_or_conjunctions
400 .into_iter()
401 .filter(|expr| {
402 expr.as_eq_const().is_some()
403 || expr
404 .as_in_const_list()
405 .or_else(|| expr.as_some_eq_const_list())
406 .is_some()
407 || expr.as_comparison_const().is_some()
408 })
409 .collect();
410
411 for expr in or_conjunctions {
412 let mut index_to_be_merged = vec![];
414
415 let disjunctions = to_disjunctions(expr.clone());
416
417 let extended_disjunctions = disjunctions
418 .into_iter()
419 .map(|expr| {
420 if interest_conjunctions.is_empty() {
421 expr
422 } else {
423 ExprImpl::FunctionCall(
424 FunctionCall::new_unchecked(
425 ExprType::And,
426 vec![expr]
427 .into_iter()
428 .chain(interest_conjunctions.iter().cloned())
429 .collect(),
430 DataType::Boolean,
431 )
432 .into(),
433 )
434 }
435 })
436 .collect_vec();
437
438 let (map, others) = self.clustering_disjunction(extended_disjunctions);
439 let iter = map
440 .into_iter()
441 .map(|(column_index, expr)| (Some(column_index), expr))
442 .chain(others.into_iter().map(|expr| (None, expr)));
443 for (column_index, expr) in iter {
444 let mut index_paths = vec![];
445 let conjunctions = to_conjunctions(expr);
446 index_paths.extend(self.gen_index_path(column_index, &conjunctions, logical_scan));
447 if conjunctions.len() > 1 {
449 index_paths.extend(self.gen_paths(
450 &conjunctions,
451 logical_scan,
452 primary_table_row_size,
453 ));
454 }
455
456 match self.choose_min_cost_path(&index_paths, primary_table_row_size) {
457 None => {
458 index_to_be_merged.clear();
460 break;
461 }
462 Some((path, _)) => index_to_be_merged.push(path),
463 }
464 }
465
466 if let Some(path) = self.merge(index_to_be_merged) {
467 result.push(path)
468 }
469 }
470
471 result
472 }
473
474 fn clustering_disjunction(
483 &self,
484 disjunctions: Vec<ExprImpl>,
485 ) -> (HashMap<usize, ExprImpl>, Vec<ExprImpl>) {
486 let mut map: HashMap<usize, ExprImpl> = HashMap::new();
487 let mut others = vec![];
488 for expr in disjunctions {
489 let idx = {
490 if let Some((input_ref, _const_expr)) = expr.as_eq_const() {
491 Some(input_ref.index)
492 } else if let Some((input_ref, _in_const_list)) = expr
493 .as_in_const_list()
494 .or_else(|| expr.as_some_eq_const_list())
495 {
496 Some(input_ref.index)
497 } else if let Some((input_ref, _op, _const_expr)) = expr.as_comparison_const() {
498 Some(input_ref.index)
499 } else {
500 None
501 }
502 };
503
504 if let Some(idx) = idx {
505 match map.entry(idx) {
506 Occupied(mut entry) => {
507 let expr2: ExprImpl = entry.get().to_owned();
508 let or_expr = ExprImpl::FunctionCall(
509 FunctionCall::new_unchecked(
510 ExprType::Or,
511 vec![expr, expr2],
512 DataType::Boolean,
513 )
514 .into(),
515 );
516 entry.insert(or_expr);
517 }
518 Vacant(entry) => {
519 entry.insert(expr);
520 }
521 };
522 } else {
523 others.push(expr);
524 continue;
525 }
526 }
527
528 (map, others)
529 }
530
531 fn gen_index_path(
538 &self,
539 column_index: Option<usize>,
540 conjunctions: &[ExprImpl],
541 logical_scan: &LogicalScan,
542 ) -> Vec<PlanRef> {
543 let combinations = conjunctions
546 .iter()
547 .take(min(conjunctions.len(), MAX_CONJUNCTION_SIZE))
548 .combinations(min(conjunctions.len(), MAX_COMBINATION_SIZE))
549 .collect_vec();
550
551 let mut result = vec![];
552
553 for index in logical_scan.table_indexes() {
554 if let Some(column_index) = column_index {
555 assert_eq!(conjunctions.len(), 1);
556 let p2s_mapping = index.primary_to_secondary_mapping();
557 match p2s_mapping.get(&column_index) {
558 None => continue, Some(&idx) => {
560 if index.index_table.pk()[0].column_index != idx {
561 continue;
563 }
564 }
565 }
566 }
567
568 for conj in &combinations {
570 let condition = Condition {
571 conjunctions: conj.iter().map(|&x| x.to_owned()).collect(),
572 };
573 if let Some(index_access) = self.build_index_access(
574 index.clone(),
575 condition,
576 logical_scan.ctx().clone(),
577 logical_scan.as_of().clone(),
578 ) {
579 result.push(index_access);
580 }
581 }
582 }
583
584 let primary_table = logical_scan.table();
586 if let Some(idx) = column_index {
587 assert_eq!(conjunctions.len(), 1);
588 if primary_table.pk[0].column_index != idx {
589 return result;
590 }
591 }
592
593 let primary_access = generic::TableScan::new(
594 primary_table
595 .pk
596 .iter()
597 .map(|x| x.column_index)
598 .collect_vec(),
599 logical_scan.table().clone(),
600 vec![],
601 vec![],
602 logical_scan.ctx(),
603 Condition {
604 conjunctions: conjunctions.to_vec(),
605 },
606 logical_scan.as_of(),
607 );
608
609 result.push(primary_access.into());
610
611 result
612 }
613
614 fn build_index_access(
616 &self,
617 index: Arc<TableIndex>,
618 predicate: Condition,
619 ctx: OptimizerContextRef,
620 as_of: Option<AsOf>,
621 ) -> Option<PlanRef> {
622 let mut rewriter = IndexPredicateRewriter::new(
623 index.primary_to_secondary_mapping(),
624 index.function_mapping(),
625 0,
626 );
627 let new_predicate = predicate.rewrite_expr(&mut rewriter);
628
629 if !rewriter.covered_by_index() {
631 return None;
632 }
633
634 Some(
635 generic::TableScan::new(
636 index
637 .primary_table_pk_ref_to_index_table()
638 .iter()
639 .map(|x| x.column_index)
640 .collect_vec(),
641 index.index_table.clone(),
642 vec![],
643 vec![],
644 ctx,
645 new_predicate,
646 as_of,
647 )
648 .into(),
649 )
650 }
651
652 fn merge(&self, paths: Vec<PlanRef>) -> Option<PlanRef> {
653 if paths.is_empty() {
654 return None;
655 }
656
657 let new_paths = paths
658 .iter()
659 .flat_map(|path| {
660 if let Some(union) = path.as_logical_union() {
661 union.inputs().to_vec()
662 } else if let Some(_scan) = path.as_logical_scan() {
663 vec![path.clone()]
664 } else {
665 unreachable!();
666 }
667 })
668 .sorted_by(|a, b| {
669 a.as_logical_scan()
671 .expect("expect to be a logical scan")
672 .table_name()
673 .cmp(
674 b.as_logical_scan()
675 .expect("expect to be a logical scan")
676 .table_name(),
677 )
678 })
679 .collect_vec();
680
681 Some(LogicalUnion::create(false, new_paths))
682 }
683
684 fn choose_min_cost_path(
685 &self,
686 paths: &[PlanRef],
687 primary_table_row_size: usize,
688 ) -> Option<(PlanRef, IndexCost)> {
689 paths
690 .iter()
691 .map(|path| {
692 if let Some(scan) = path.as_logical_scan() {
693 let cost = self.estimate_table_scan_cost(scan, primary_table_row_size);
694 (scan.clone().into(), cost)
695 } else if let Some(union) = path.as_logical_union() {
696 let cost = union
697 .inputs()
698 .iter()
699 .map(|input| {
700 self.estimate_table_scan_cost(
701 input.as_logical_scan().expect("expect to be a scan"),
702 primary_table_row_size,
703 )
704 })
705 .reduce(|a, b| a.add(&b))
706 .unwrap();
707 (union.clone().into(), cost)
708 } else {
709 unreachable!()
710 }
711 })
712 .min_by(|(_, cost1), (_, cost2)| Ord::cmp(cost1, cost2))
713 }
714
715 pub(crate) fn estimate_table_scan_cost(
716 &self,
717 scan: &LogicalScan,
718 row_size: usize,
719 ) -> IndexCost {
720 let mut table_scan_io_estimator = TableScanIoEstimator::new(scan, row_size);
721 table_scan_io_estimator.estimate(scan.predicate())
722 }
723
724 pub(crate) fn estimate_full_table_scan_cost(
725 &self,
726 scan: &LogicalScan,
727 row_size: usize,
728 ) -> IndexCost {
729 let mut table_scan_io_estimator = TableScanIoEstimator::new(scan, row_size);
730 table_scan_io_estimator.estimate(&Condition::true_cond())
731 }
732
733 pub fn create_null_safe_equal_expr(
734 left: usize,
735 left_data_type: DataType,
736 right: usize,
737 right_data_type: DataType,
738 ) -> ExprImpl {
739 ExprImpl::FunctionCall(Box::new(FunctionCall::new_unchecked(
740 ExprType::IsNotDistinctFrom,
741 vec![
742 ExprImpl::InputRef(Box::new(InputRef::new(left, left_data_type))),
743 ExprImpl::InputRef(Box::new(InputRef::new(right, right_data_type))),
744 ],
745 DataType::Boolean,
746 )))
747 }
748}
749
750pub(crate) struct TableScanIoEstimator<'a> {
751 table_scan: &'a LogicalScan,
752 row_size: usize,
753 cost: Option<IndexCost>,
754}
755
756impl<'a> TableScanIoEstimator<'a> {
757 pub fn new(table_scan: &'a LogicalScan, row_size: usize) -> Self {
758 Self {
759 table_scan,
760 row_size,
761 cost: None,
762 }
763 }
764
765 pub fn estimate_row_size(table_scan: &LogicalScan) -> usize {
766 let row_meta_field_estimate_size = 14_usize;
768 let table = table_scan.table();
769 row_meta_field_estimate_size
770 + table
771 .columns
772 .iter()
773 .chain(table.pk.iter().map(|x| &table.columns[x.column_index]))
775 .map(|x| TableScanIoEstimator::estimate_data_type_size(&x.data_type))
776 .sum::<usize>()
777 }
778
779 fn estimate_data_type_size(data_type: &DataType) -> usize {
780 use std::mem::size_of;
781
782 match data_type {
783 DataType::Boolean => size_of::<bool>(),
784 DataType::Int16 => size_of::<i16>(),
785 DataType::Int32 => size_of::<i32>(),
786 DataType::Int64 => size_of::<i64>(),
787 DataType::Serial => size_of::<Serial>(),
788 DataType::Float32 => size_of::<f32>(),
789 DataType::Float64 => size_of::<f64>(),
790 DataType::Decimal => size_of::<Decimal>(),
791 DataType::Date => size_of::<Date>(),
792 DataType::Time => size_of::<Time>(),
793 DataType::Timestamp => size_of::<Timestamp>(),
794 DataType::Timestamptz => size_of::<Timestamptz>(),
795 DataType::Interval => size_of::<Interval>(),
796 DataType::Int256 => Int256::size(),
797 DataType::Varchar => 20,
798 DataType::Bytea => 20,
799 DataType::Jsonb => 20,
800 DataType::Variant => 20,
801 DataType::Struct { .. } => 20,
802 DataType::List { .. } => 20,
803 DataType::Map(_) => 20,
804 DataType::Vector(d) => d * size_of::<VectorDistanceType>(),
805 }
806 }
807
808 pub fn estimate(&mut self, predicate: &Condition) -> IndexCost {
809 if predicate.conjunctions.len() == 1 {
811 self.visit_expr(&predicate.conjunctions[0]);
812 self.cost.take().unwrap_or_default()
813 } else {
814 self.estimate_conjunctions(&predicate.conjunctions)
815 }
816 }
817
818 fn estimate_conjunctions(&mut self, conjunctions: &[ExprImpl]) -> IndexCost {
819 let mut new_conjunctions = conjunctions.to_owned();
820
821 let mut match_item_vec = vec![];
822
823 for column_idx in self.table_scan.table().order_column_indices() {
824 let match_item = self.match_index_column(column_idx, &mut new_conjunctions);
825 let should_break = match match_item {
827 MatchItem::Equal | MatchItem::In(_) => false,
828 MatchItem::RangeOneSideBound | MatchItem::RangeTwoSideBound | MatchItem::All => {
829 true
830 }
831 };
832 match_item_vec.push(match_item);
833 if should_break {
834 break;
835 }
836 }
837
838 let index_cost = match_item_vec
839 .iter()
840 .enumerate()
841 .take(INDEX_MAX_LEN)
842 .map(|(i, match_item)| match match_item {
843 MatchItem::Equal => INDEX_COST_MATRIX[0][i],
844 MatchItem::In(num) => min(INDEX_COST_MATRIX[1][i], *num),
845 MatchItem::RangeTwoSideBound => INDEX_COST_MATRIX[2][i],
846 MatchItem::RangeOneSideBound => INDEX_COST_MATRIX[3][i],
847 MatchItem::All => INDEX_COST_MATRIX[4][i],
848 })
849 .reduce(|x, y| x * y)
850 .unwrap();
851
852 let primary_lookup = index_cost == 1;
854
855 IndexCost::new(index_cost, primary_lookup)
856 .mul(&IndexCost::new(self.row_size, primary_lookup))
857 }
858
859 fn match_index_column(
860 &mut self,
861 column_idx: usize,
862 conjunctions: &mut Vec<ExprImpl>,
863 ) -> MatchItem {
864 for (i, expr) in conjunctions.iter().enumerate() {
866 if let Some((input_ref, _const_expr)) = expr.as_eq_const()
867 && input_ref.index == column_idx
868 {
869 conjunctions.remove(i);
870 return MatchItem::Equal;
871 }
872 }
873
874 for (i, expr) in conjunctions.iter().enumerate() {
876 if let Some((input_ref, in_const_list)) = expr
877 .as_in_const_list()
878 .or_else(|| expr.as_some_eq_const_list())
879 && input_ref.index == column_idx
880 {
881 conjunctions.remove(i);
882 return MatchItem::In(in_const_list.len());
883 }
884 }
885
886 let mut left_side_bound = false;
888 let mut right_side_bound = false;
889 let mut i = 0;
890 while i < conjunctions.len() {
891 let expr = &conjunctions[i];
892 if let Some((input_ref, op, _const_expr)) = expr.as_comparison_const()
893 && input_ref.index == column_idx
894 {
895 conjunctions.remove(i);
896 match op {
897 ExprType::LessThan | ExprType::LessThanOrEqual => right_side_bound = true,
898 ExprType::GreaterThan | ExprType::GreaterThanOrEqual => left_side_bound = true,
899 _ => unreachable!(),
900 };
901 } else {
902 i += 1;
903 }
904 }
905
906 if left_side_bound && right_side_bound {
907 MatchItem::RangeTwoSideBound
908 } else if left_side_bound || right_side_bound {
909 MatchItem::RangeOneSideBound
910 } else {
911 MatchItem::All
912 }
913 }
914}
915
916enum MatchItem {
917 Equal,
918 In(usize),
919 RangeTwoSideBound,
920 RangeOneSideBound,
921 All,
922}
923
924#[derive(PartialEq, Eq, Hash, Clone, Debug, PartialOrd, Ord)]
925pub(crate) struct IndexCost {
926 cost: usize,
927 pub(crate) primary_lookup: bool,
928}
929
930impl Default for IndexCost {
931 fn default() -> Self {
932 Self {
933 cost: IndexCost::maximum(),
934 primary_lookup: false,
935 }
936 }
937}
938
939impl IndexCost {
940 fn new(cost: usize, primary_lookup: bool) -> IndexCost {
941 Self {
942 cost: min(cost, IndexCost::maximum()),
943 primary_lookup,
944 }
945 }
946
947 fn maximum() -> usize {
948 10000000
949 }
950
951 fn add(&self, other: &IndexCost) -> IndexCost {
952 IndexCost::new(
953 self.cost
954 .checked_add(other.cost)
955 .unwrap_or_else(IndexCost::maximum),
956 self.primary_lookup && other.primary_lookup,
957 )
958 }
959
960 fn mul(&self, other: &IndexCost) -> IndexCost {
961 IndexCost::new(
962 self.cost
963 .checked_mul(other.cost)
964 .unwrap_or_else(IndexCost::maximum),
965 self.primary_lookup && other.primary_lookup,
966 )
967 }
968
969 pub(crate) fn le(&self, other: &IndexCost) -> bool {
970 self.cost < other.cost
971 }
972}
973
974impl ExprVisitor for TableScanIoEstimator<'_> {
975 fn visit_function_call(&mut self, func_call: &FunctionCall) {
976 let cost = match func_call.func_type() {
977 ExprType::Or => func_call
978 .inputs()
979 .iter()
980 .map(|x| {
981 let mut estimator = TableScanIoEstimator::new(self.table_scan, self.row_size);
982 estimator.visit_expr(x);
983 estimator.cost.take().unwrap_or_default()
984 })
985 .reduce(|x, y| x.add(&y))
986 .unwrap(),
987 ExprType::And => self.estimate_conjunctions(func_call.inputs()),
988 _ => {
989 let single = vec![ExprImpl::FunctionCall(func_call.clone().into())];
990 self.estimate_conjunctions(&single)
991 }
992 };
993 self.cost = Some(cost);
994 }
995}
996
997struct ShiftInputRefRewriter {
998 offset: usize,
999}
1000impl ExprRewriter for ShiftInputRefRewriter {
1001 fn rewrite_input_ref(&mut self, input_ref: InputRef) -> ExprImpl {
1002 InputRef::new(input_ref.index() + self.offset, input_ref.return_type()).into()
1003 }
1004}
1005
1006impl IndexSelectionRule {
1007 pub fn create() -> BoxedRule {
1008 Box::new(IndexSelectionRule {})
1009 }
1010}