mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-07 17:24:24 +01:00
AccessMethod: simplify - get rid of AccessMethodKind as it can be derived
This commit is contained in:
@@ -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<Arc<Index>>,
|
||||
/// 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<Arc<Index>>,
|
||||
iter_dir: IterationDirection,
|
||||
},
|
||||
/// A search, which can be an index seek or a rowid-based search.
|
||||
Search {
|
||||
index: Option<Arc<Index>>,
|
||||
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<AccessMethod<'a>> {
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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::<Vec<_>>();
|
||||
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::<Vec<_>>();
|
||||
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() {
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user