diff --git a/core/translate/optimizer/access_method.rs b/core/translate/optimizer/access_method.rs index 0da6e2b86..b89cc56f8 100644 --- a/core/translate/optimizer/access_method.rs +++ b/core/translate/optimizer/access_method.rs @@ -20,38 +20,34 @@ pub struct AccessMethod<'a> { /// The estimated number of page fetches. /// We are ignoring CPU cost for now. pub cost: Cost, - pub kind: AccessMethodKind<'a>, + /// The direction of iteration for the access method. + /// Typically this is backwards only if it helps satisfy an [OrderTarget]. + pub iter_dir: IterationDirection, + /// The index that is being used, if any. For rowid based searches (and full table scans), this is None. + pub index: Option>, + /// The constraint references that are being used, if any. + /// An empty list of constraint refs means a scan (full table or index); + /// a non-empty list means a search. + pub constraint_refs: &'a [ConstraintRef], } impl<'a> AccessMethod<'a> { - pub fn index(&self) -> Option<&Index> { - match &self.kind { - AccessMethodKind::Scan { index, .. } => index.as_ref().map(|i| i.as_ref()), - AccessMethodKind::Search { index, .. } => index.as_ref().map(|i| i.as_ref()), - } + pub fn is_scan(&self) -> bool { + self.constraint_refs.is_empty() } - pub fn iter_dir(&self) -> IterationDirection { - match &self.kind { - AccessMethodKind::Scan { iter_dir, .. } => *iter_dir, - AccessMethodKind::Search { iter_dir, .. } => *iter_dir, - } - } -} -#[derive(Debug, Clone)] -/// Represents the kind of access method. -pub enum AccessMethodKind<'a> { - /// A full scan, which can be an index scan or a table scan. - Scan { - index: Option>, - iter_dir: IterationDirection, - }, - /// A search, which can be an index seek or a rowid-based search. - Search { - index: Option>, - iter_dir: IterationDirection, - constraint_refs: &'a [ConstraintRef], - }, + pub fn is_search(&self) -> bool { + !self.constraint_refs.is_empty() + } + + pub fn new_table_scan(input_cardinality: f64, iter_dir: IterationDirection) -> Self { + Self { + cost: estimate_cost_for_scan_or_seek(None, &[], &[], input_cardinality), + iter_dir, + index: None, + constraint_refs: &[], + } + } } /// Return the best [AccessMethod] for a given join order. @@ -63,14 +59,8 @@ pub fn find_best_access_method_for_join_order<'a>( input_cardinality: f64, ) -> Result> { let table_no = join_order.last().unwrap().table_no; - let cost_of_full_table_scan = estimate_cost_for_scan_or_seek(None, &[], &[], input_cardinality); - let mut best_access_method = AccessMethod { - cost: cost_of_full_table_scan, - kind: AccessMethodKind::Scan { - index: None, - iter_dir: IterationDirection::Forwards, - }, - }; + let mut best_access_method = + AccessMethod::new_table_scan(input_cardinality, IterationDirection::Forwards); let rowid_column_idx = rhs_table.columns().iter().position(|c| c.is_rowid_alias); // Estimate cost for each candidate index (including the rowid index) and replace best_access_method if the cost is lower. @@ -151,18 +141,9 @@ pub fn find_best_access_method_for_join_order<'a>( if cost < best_access_method.cost + order_satisfiability_bonus { best_access_method = AccessMethod { cost, - kind: if usable_constraint_refs.is_empty() { - AccessMethodKind::Scan { - index: candidate.index.clone(), - iter_dir, - } - } else { - AccessMethodKind::Search { - index: candidate.index.clone(), - iter_dir, - constraint_refs: &usable_constraint_refs, - } - }, + index: candidate.index.clone(), + iter_dir, + constraint_refs: &usable_constraint_refs, }; } } diff --git a/core/translate/optimizer/join.rs b/core/translate/optimizer/join.rs index 36d11b967..2c9c676b3 100644 --- a/core/translate/optimizer/join.rs +++ b/core/translate/optimizer/join.rs @@ -481,10 +481,7 @@ mod tests { use crate::{ schema::{BTreeTable, Column, Index, IndexColumn, Table, Type}, translate::{ - optimizer::{ - access_method::AccessMethodKind, - constraints::{constraints_from_where_clause, BinaryExprSide}, - }, + optimizer::constraints::{constraints_from_where_clause, BinaryExprSide}, plan::{ColumnUsedMask, IterationDirection, JoinInfo, Operation, WhereTerm}, planner::TableMask, }, @@ -547,11 +544,9 @@ mod tests { .unwrap() .unwrap(); // Should just be a table scan access method - assert!(matches!( - access_methods_arena.borrow()[best_plan.best_access_methods[0]].kind, - AccessMethodKind::Scan { index: None, iter_dir } - if iter_dir == IterationDirection::Forwards - )); + let access_method = &access_methods_arena.borrow()[best_plan.best_access_methods[0]]; + assert!(access_method.is_scan()); + assert!(access_method.iter_dir == IterationDirection::Forwards); } #[test] @@ -584,18 +579,14 @@ mod tests { assert!(result.is_some()); let BestJoinOrderResult { best_plan, .. } = result.unwrap(); assert_eq!(best_plan.table_numbers, vec![0]); + let access_method = &access_methods_arena.borrow()[best_plan.best_access_methods[0]]; + assert!(access_method.is_search()); + assert!(access_method.iter_dir == IterationDirection::Forwards); + assert!(access_method.constraint_refs.len() == 1); assert!( - matches!( - &access_methods_arena.borrow()[best_plan.best_access_methods[0]].kind, - AccessMethodKind::Search { - index: None, - iter_dir, - constraint_refs, - } - if *iter_dir == IterationDirection::Forwards && constraint_refs.len() == 1 && table_constraints[0].constraints[constraint_refs[0].constraint_vec_pos].where_clause_pos == (0, BinaryExprSide::Rhs), - ), - "expected rowid eq access method, got {:?}", - access_methods_arena.borrow()[best_plan.best_access_methods[0]].kind + table_constraints[0].constraints[access_method.constraint_refs[0].constraint_vec_pos] + .where_clause_pos + == (0, BinaryExprSide::Rhs) ); } @@ -645,18 +636,15 @@ mod tests { assert!(result.is_some()); let BestJoinOrderResult { best_plan, .. } = result.unwrap(); assert_eq!(best_plan.table_numbers, vec![0]); + let access_method = &access_methods_arena.borrow()[best_plan.best_access_methods[0]]; + assert!(access_method.is_search()); + assert!(access_method.iter_dir == IterationDirection::Forwards); + assert!(access_method.index.as_ref().unwrap().name == "sqlite_autoindex_test_table_1"); + assert!(access_method.constraint_refs.len() == 1); assert!( - matches!( - &access_methods_arena.borrow()[best_plan.best_access_methods[0]].kind, - AccessMethodKind::Search { - index: Some(index), - iter_dir, - constraint_refs, - } - if *iter_dir == IterationDirection::Forwards && constraint_refs.len() == 1 && table_constraints[0].constraints[constraint_refs[0].constraint_vec_pos].lhs_mask.is_empty() && index.name == "sqlite_autoindex_test_table_1" - ), - "expected index search access method, got {:?}", - access_methods_arena.borrow()[best_plan.best_access_methods[0]].kind + table_constraints[0].constraints[access_method.constraint_refs[0].constraint_vec_pos] + .where_clause_pos + == (0, BinaryExprSide::Rhs) ); } @@ -719,27 +707,19 @@ mod tests { assert!(result.is_some()); let BestJoinOrderResult { best_plan, .. } = result.unwrap(); assert_eq!(best_plan.table_numbers, vec![1, 0]); + let access_method = &access_methods_arena.borrow()[best_plan.best_access_methods[0]]; + assert!(access_method.is_scan()); + assert!(access_method.iter_dir == IterationDirection::Forwards); + let access_method = &access_methods_arena.borrow()[best_plan.best_access_methods[1]]; + assert!(access_method.is_search()); + assert!(access_method.iter_dir == IterationDirection::Forwards); + assert!(access_method.index.as_ref().unwrap().name == "index1"); + assert!(access_method.constraint_refs.len() == 1); assert!( - matches!( - &access_methods_arena.borrow()[best_plan.best_access_methods[0]].kind, - AccessMethodKind::Scan { index: None, iter_dir } - if *iter_dir == IterationDirection::Forwards - ), - "expected TableScan access method, got {:?}", - access_methods_arena.borrow()[best_plan.best_access_methods[0]].kind - ); - assert!( - matches!( - &access_methods_arena.borrow()[best_plan.best_access_methods[1]].kind, - AccessMethodKind::Search { - index: Some(index), - iter_dir, - constraint_refs, - } - if *iter_dir == IterationDirection::Forwards && constraint_refs.len() == 1 && table_constraints[TABLE1].constraints[constraint_refs[0].constraint_vec_pos].where_clause_pos == (0, BinaryExprSide::Rhs) && index.name == "index1", - ), - "expected Search access method, got {:?}", - access_methods_arena.borrow()[best_plan.best_access_methods[1]].kind + table_constraints[TABLE1].constraints + [access_method.constraint_refs[0].constraint_vec_pos] + .where_clause_pos + == (0, BinaryExprSide::Rhs) ); } @@ -892,99 +872,32 @@ mod tests { vec![TABLE_NO_CUSTOMERS, TABLE_NO_ORDERS, TABLE_NO_ORDER_ITEMS] ); - let AccessMethodKind::Search { - index: Some(index), - iter_dir, - constraint_refs, - } = &access_methods_arena.borrow()[best_plan.best_access_methods[0]].kind - else { - panic!("expected Search access method with index for first table"); - }; + let access_method = &access_methods_arena.borrow()[best_plan.best_access_methods[0]]; + assert!(access_method.is_search()); + assert!(access_method.iter_dir == IterationDirection::Forwards); + assert!(access_method.index.as_ref().unwrap().name == "sqlite_autoindex_customers_1"); + assert!(access_method.constraint_refs.len() == 1); + let constraint = &table_constraints[TABLE_NO_CUSTOMERS].constraints + [access_method.constraint_refs[0].constraint_vec_pos]; + assert!(constraint.lhs_mask.is_empty()); - assert_eq!( - index.name, "sqlite_autoindex_customers_1", - "wrong index name" - ); - assert_eq!( - *iter_dir, - IterationDirection::Forwards, - "wrong iteration direction" - ); - assert_eq!( - constraint_refs.len(), - 1, - "wrong number of constraint references" - ); - assert!( - table_constraints[TABLE_NO_CUSTOMERS].constraints - [constraint_refs[0].constraint_vec_pos] - .lhs_mask - .is_empty(), - "wrong lhs mask: {:?}", - table_constraints[TABLE_NO_CUSTOMERS].constraints - [constraint_refs[0].constraint_vec_pos] - .lhs_mask - ); + let access_method = &access_methods_arena.borrow()[best_plan.best_access_methods[1]]; + assert!(access_method.is_search()); + assert!(access_method.iter_dir == IterationDirection::Forwards); + assert!(access_method.index.as_ref().unwrap().name == "orders_customer_id_idx"); + assert!(access_method.constraint_refs.len() == 1); + let constraint = &table_constraints[TABLE_NO_ORDERS].constraints + [access_method.constraint_refs[0].constraint_vec_pos]; + assert!(constraint.lhs_mask.contains_table(TABLE_NO_CUSTOMERS)); - let AccessMethodKind::Search { - index: Some(index), - iter_dir, - constraint_refs, - } = &access_methods_arena.borrow()[best_plan.best_access_methods[1]].kind - else { - panic!("expected Search access method with index for second table"); - }; - - assert_eq!(index.name, "orders_customer_id_idx", "wrong index name"); - assert_eq!( - *iter_dir, - IterationDirection::Forwards, - "wrong iteration direction" - ); - assert_eq!( - constraint_refs.len(), - 1, - "wrong number of constraint references" - ); - assert!( - table_constraints[TABLE_NO_ORDERS].constraints[constraint_refs[0].constraint_vec_pos] - .lhs_mask - .contains_table(TABLE_NO_CUSTOMERS), - "wrong lhs mask: {:?}", - table_constraints[TABLE_NO_ORDERS].constraints[constraint_refs[0].constraint_vec_pos] - .lhs_mask - ); - - let AccessMethodKind::Search { - index: Some(index), - iter_dir, - constraint_refs, - } = &access_methods_arena.borrow()[best_plan.best_access_methods[2]].kind - else { - panic!("expected Search access method with index for third table"); - }; - - assert_eq!(index.name, "order_items_order_id_idx", "wrong index name"); - assert_eq!( - *iter_dir, - IterationDirection::Forwards, - "wrong iteration direction" - ); - assert_eq!( - constraint_refs.len(), - 1, - "wrong number of constraint references" - ); - assert!( - table_constraints[TABLE_NO_ORDER_ITEMS].constraints - [constraint_refs[0].constraint_vec_pos] - .lhs_mask - .contains_table(TABLE_NO_ORDERS), - "wrong lhs mask: {:?}", - table_constraints[TABLE_NO_ORDER_ITEMS].constraints - [constraint_refs[0].constraint_vec_pos] - .lhs_mask - ); + let access_method = &access_methods_arena.borrow()[best_plan.best_access_methods[2]]; + assert!(access_method.is_search()); + assert!(access_method.iter_dir == IterationDirection::Forwards); + assert!(access_method.index.as_ref().unwrap().name == "order_items_order_id_idx"); + assert!(access_method.constraint_refs.len() == 1); + let constraint = &table_constraints[TABLE_NO_ORDER_ITEMS].constraints + [access_method.constraint_refs[0].constraint_vec_pos]; + assert!(constraint.lhs_mask.contains_table(TABLE_NO_ORDERS)); } struct TestColumn { @@ -1060,23 +973,20 @@ mod tests { // Verify that t2 is chosen first due to its equality filter assert_eq!(best_plan.table_numbers[0], 1); // Verify table scan is used since there are no indexes - assert!(matches!( - access_methods_arena.borrow()[best_plan.best_access_methods[0]].kind, - AccessMethodKind::Scan { index: None, iter_dir } - if iter_dir == IterationDirection::Forwards - )); + let access_method = &access_methods_arena.borrow()[best_plan.best_access_methods[0]]; + assert!(access_method.is_scan()); + assert!(access_method.iter_dir == IterationDirection::Forwards); + assert!(access_method.index.is_none()); // Verify that t1 is chosen next due to its inequality filter - assert!(matches!( - access_methods_arena.borrow()[best_plan.best_access_methods[1]].kind, - AccessMethodKind::Scan { index: None, iter_dir } - if iter_dir == IterationDirection::Forwards - )); + let access_method = &access_methods_arena.borrow()[best_plan.best_access_methods[1]]; + assert!(access_method.is_scan()); + assert!(access_method.iter_dir == IterationDirection::Forwards); + assert!(access_method.index.is_none()); // Verify that t3 is chosen last due to no filters - assert!(matches!( - access_methods_arena.borrow()[best_plan.best_access_methods[2]].kind, - AccessMethodKind::Scan { index: None, iter_dir } - if iter_dir == IterationDirection::Forwards - )); + let access_method = &access_methods_arena.borrow()[best_plan.best_access_methods[2]]; + assert!(access_method.is_scan()); + assert!(access_method.iter_dir == IterationDirection::Forwards); + assert!(access_method.index.is_none()); } #[test] @@ -1166,43 +1076,22 @@ mod tests { ); // Verify access methods - assert!( - matches!( - &access_methods_arena.borrow()[best_plan.best_access_methods[0]].kind, - AccessMethodKind::Scan { index: None, iter_dir } - if *iter_dir == IterationDirection::Forwards - ), - "First table (fact) should use table scan due to column filter" - ); + let access_method = &access_methods_arena.borrow()[best_plan.best_access_methods[0]]; + assert!(access_method.is_scan()); + assert!(access_method.iter_dir == IterationDirection::Forwards); + assert!(access_method.index.is_none()); + assert!(access_method.constraint_refs.is_empty()); for (i, table_number) in best_plan.table_numbers.iter().enumerate().skip(1) { - let AccessMethodKind::Search { - index: None, - iter_dir, - constraint_refs, - } = &access_methods_arena.borrow()[best_plan.best_access_methods[i]].kind - else { - panic!("expected Search access method for table {}", table_number); - }; - - assert_eq!( - *iter_dir, - IterationDirection::Forwards, - "wrong iteration direction" - ); - assert_eq!( - constraint_refs.len(), - 1, - "wrong number of constraint references" - ); - assert!( - table_constraints[*table_number].constraints[constraint_refs[0].constraint_vec_pos] - .lhs_mask - .contains_table(FACT_TABLE_IDX), - "wrong lhs mask: {:?}", - table_constraints[*table_number].constraints[constraint_refs[0].constraint_vec_pos] - .lhs_mask - ); + let access_method = &access_methods_arena.borrow()[best_plan.best_access_methods[i]]; + assert!(access_method.is_search()); + assert!(access_method.iter_dir == IterationDirection::Forwards); + assert!(access_method.index.is_none()); + assert!(access_method.constraint_refs.len() == 1); + let constraint = &table_constraints[*table_number].constraints + [access_method.constraint_refs[0].constraint_vec_pos]; + assert!(constraint.lhs_mask.contains_table(FACT_TABLE_IDX)); + assert!(constraint.operator == ast::Operator::Equals); } } @@ -1267,32 +1156,23 @@ mod tests { // Verify access methods: // - First table should use Table scan - assert!( - matches!( - &access_methods_arena.borrow()[best_plan.best_access_methods[0]].kind, - AccessMethodKind::Scan { index: None, iter_dir } - if *iter_dir == IterationDirection::Forwards - ), - "First table should use Table scan" - ); + let access_method = &access_methods_arena.borrow()[best_plan.best_access_methods[0]]; + assert!(access_method.is_scan()); + assert!(access_method.iter_dir == IterationDirection::Forwards); + assert!(access_method.index.is_none()); + assert!(access_method.constraint_refs.is_empty()); // all of the rest should use rowid equality for i in 1..NUM_TABLES { - let method = &access_methods_arena.borrow()[best_plan.best_access_methods[i]].kind; - assert!( - matches!( - method, - AccessMethodKind::Search { - index: None, - iter_dir, - constraint_refs, - } - if *iter_dir == IterationDirection::Forwards && constraint_refs.len() == 1 && table_constraints[i].constraints[constraint_refs[0].constraint_vec_pos].lhs_mask.contains_table(i-1) - ), - "Table {} should use Search access method, got {:?}", - i + 1, - method - ); + let access_method = &access_methods_arena.borrow()[best_plan.best_access_methods[i]]; + assert!(access_method.is_search()); + assert!(access_method.iter_dir == IterationDirection::Forwards); + assert!(access_method.index.is_none()); + assert!(access_method.constraint_refs.len() == 1); + let constraint = &table_constraints[i].constraints + [access_method.constraint_refs[0].constraint_vec_pos]; + assert!(constraint.lhs_mask.contains_table(i - 1)); + assert!(constraint.operator == ast::Operator::Equals); } } diff --git a/core/translate/optimizer/mod.rs b/core/translate/optimizer/mod.rs index 1999c9c29..639ce12ac 100644 --- a/core/translate/optimizer/mod.rs +++ b/core/translate/optimizer/mod.rs @@ -1,6 +1,5 @@ use std::{cell::RefCell, cmp::Ordering, collections::HashMap, sync::Arc}; -use access_method::AccessMethodKind; use constraints::{ constraints_from_where_clause, usable_constraints_for_join_order, BinaryExprSide, Constraint, ConstraintRef, @@ -215,131 +214,132 @@ fn optimize_table_access( // Mutate the Operations in `table_references` to use the selected access methods. for (i, join_order_member) in best_join_order.iter().enumerate() { let table_number = join_order_member.table_no; - let access_method_kind = access_methods_arena.borrow()[best_access_methods[i]] - .kind - .clone(); + let access_method = &access_methods_arena.borrow()[best_access_methods[i]]; if matches!( table_references[table_number].op, Operation::Subquery { .. } ) { // FIXME: Operation::Subquery shouldn't exist. It's not an operation, it's a kind of temporary table. assert!( - matches!(access_method_kind, AccessMethodKind::Scan { index: None, .. }), + access_method.is_scan(), "nothing in the current optimizer should be able to optimize subqueries, but got {:?} for table {}", - access_method_kind, + access_method, table_references[table_number].table.get_name() ); continue; } - table_references[table_number].op = match access_method_kind { - AccessMethodKind::Scan { iter_dir, index } => { - if index.is_some() || i == 0 { - Operation::Scan { iter_dir, index } - } else { - // This branch means we have a full table scan for a non-outermost table. - // Try to construct an ephemeral index since it's going to be better than a scan. - let table_constraints = constraints_per_table - .iter() - .find(|c| c.table_no == table_number); - if let Some(table_constraints) = table_constraints { - let temp_constraint_refs = (0..table_constraints.constraints.len()) - .map(|i| ConstraintRef { - constraint_vec_pos: i, - index_col_pos: table_constraints.constraints[i].table_col_pos, - sort_order: SortOrder::Asc, - }) - .collect::>(); - let usable_constraint_refs = usable_constraints_for_join_order( - &table_constraints.constraints, - &temp_constraint_refs, - &best_join_order[..=i], - ); - if usable_constraint_refs.is_empty() { - Operation::Scan { iter_dir, index } - } else { - let ephemeral_index = ephemeral_index_build( - &table_references[table_number], - table_number, - &table_constraints.constraints, - &usable_constraint_refs, - ); - let ephemeral_index = Arc::new(ephemeral_index); - Operation::Search(Search::Seek { - index: Some(ephemeral_index), - seek_def: build_seek_def_from_constraints( - &table_constraints.constraints, - &usable_constraint_refs, - iter_dir, - where_clause, - )?, - }) - } - } else { - Operation::Scan { iter_dir, index } - } - } + if access_method.is_scan() { + if access_method.index.is_some() || i == 0 { + table_references[table_number].op = Operation::Scan { + iter_dir: access_method.iter_dir, + index: access_method.index.clone(), + }; + continue; } - AccessMethodKind::Search { - index, - constraint_refs, - iter_dir, - } => { - assert!(!constraint_refs.is_empty()); - for cref in constraint_refs.iter() { - let constraint = - &constraints_per_table[table_number].constraints[cref.constraint_vec_pos]; - to_remove_from_where_clause.push(constraint.where_clause_pos.0); - } - if let Some(index) = index { - Operation::Search(Search::Seek { - index: Some(index), - seek_def: build_seek_def_from_constraints( - &constraints_per_table[table_number].constraints, - &constraint_refs, - iter_dir, - where_clause, - )?, - }) - } else { - assert!( - constraint_refs.len() == 1, - "expected exactly one constraint for rowid seek, got {:?}", - constraint_refs - ); - let constraint = &constraints_per_table[table_number].constraints - [constraint_refs[0].constraint_vec_pos]; - match constraint.operator { - ast::Operator::Equals => Operation::Search(Search::RowidEq { - cmp_expr: { - let (idx, side) = constraint.where_clause_pos; - let ast::Expr::Binary(lhs, _, rhs) = - unwrap_parens(&where_clause[idx].expr)? - else { - panic!("Expected a binary expression"); - }; - let where_term = WhereTerm { - expr: match side { - BinaryExprSide::Lhs => lhs.as_ref().clone(), - BinaryExprSide::Rhs => rhs.as_ref().clone(), - }, - from_outer_join: where_clause[idx].from_outer_join.clone(), - }; - where_term + // This branch means we have a full table scan for a non-outermost table. + // Try to construct an ephemeral index since it's going to be better than a scan. + let table_constraints = constraints_per_table + .iter() + .find(|c| c.table_no == table_number); + let Some(table_constraints) = table_constraints else { + table_references[table_number].op = Operation::Scan { + iter_dir: access_method.iter_dir, + index: access_method.index.clone(), + }; + continue; + }; + let temp_constraint_refs = (0..table_constraints.constraints.len()) + .map(|i| ConstraintRef { + constraint_vec_pos: i, + index_col_pos: table_constraints.constraints[i].table_col_pos, + sort_order: SortOrder::Asc, + }) + .collect::>(); + let usable_constraint_refs = usable_constraints_for_join_order( + &table_constraints.constraints, + &temp_constraint_refs, + &best_join_order[..=i], + ); + if usable_constraint_refs.is_empty() { + table_references[table_number].op = Operation::Scan { + iter_dir: access_method.iter_dir, + index: access_method.index.clone(), + }; + continue; + } + let ephemeral_index = ephemeral_index_build( + &table_references[table_number], + table_number, + &table_constraints.constraints, + &usable_constraint_refs, + ); + let ephemeral_index = Arc::new(ephemeral_index); + table_references[table_number].op = Operation::Search(Search::Seek { + index: Some(ephemeral_index), + seek_def: build_seek_def_from_constraints( + &table_constraints.constraints, + &usable_constraint_refs, + access_method.iter_dir, + where_clause, + )?, + }); + } else { + let constraint_refs = access_method.constraint_refs; + assert!(!constraint_refs.is_empty()); + for cref in constraint_refs.iter() { + let constraint = + &constraints_per_table[table_number].constraints[cref.constraint_vec_pos]; + to_remove_from_where_clause.push(constraint.where_clause_pos.0); + } + if let Some(index) = &access_method.index { + table_references[table_number].op = Operation::Search(Search::Seek { + index: Some(index.clone()), + seek_def: build_seek_def_from_constraints( + &constraints_per_table[table_number].constraints, + &constraint_refs, + access_method.iter_dir, + where_clause, + )?, + }); + continue; + } + assert!( + constraint_refs.len() == 1, + "expected exactly one constraint for rowid seek, got {:?}", + constraint_refs + ); + let constraint = &constraints_per_table[table_number].constraints + [constraint_refs[0].constraint_vec_pos]; + table_references[table_number].op = match constraint.operator { + ast::Operator::Equals => Operation::Search(Search::RowidEq { + cmp_expr: { + let (idx, side) = constraint.where_clause_pos; + let ast::Expr::Binary(lhs, _, rhs) = + unwrap_parens(&where_clause[idx].expr)? + else { + panic!("Expected a binary expression"); + }; + let where_term = WhereTerm { + expr: match side { + BinaryExprSide::Lhs => lhs.as_ref().clone(), + BinaryExprSide::Rhs => rhs.as_ref().clone(), }, - }), - _ => Operation::Search(Search::Seek { - index: None, - seek_def: build_seek_def_from_constraints( - &constraints_per_table[table_number].constraints, - &constraint_refs, - iter_dir, - where_clause, - )?, - }), - } - } - } - }; + from_outer_join: where_clause[idx].from_outer_join.clone(), + }; + where_term + }, + }), + _ => Operation::Search(Search::Seek { + index: None, + seek_def: build_seek_def_from_constraints( + &constraints_per_table[table_number].constraints, + &constraint_refs, + access_method.iter_dir, + where_clause, + )?, + }), + }; + } } to_remove_from_where_clause.sort_by_key(|c| *c); for position in to_remove_from_where_clause.iter().rev() { diff --git a/core/translate/optimizer/order.rs b/core/translate/optimizer/order.rs index b6c359915..e79c798e3 100644 --- a/core/translate/optimizer/order.rs +++ b/core/translate/optimizer/order.rs @@ -155,8 +155,8 @@ pub fn plan_satisfies_order_target( // Check if this table has an access method that provides the right ordering. let access_method = &access_methods_arena.borrow()[plan.best_access_methods[i]]; - let iter_dir = access_method.iter_dir(); - let index = access_method.index(); + let iter_dir = access_method.iter_dir; + let index = access_method.index.as_ref(); match index { None => { // No index, so the next required column must be the rowid alias column.