AccessMethod: simplify - get rid of AccessMethodKind as it can be derived

This commit is contained in:
Jussi Saurio
2025-05-12 12:43:40 +03:00
parent 12a2c2b9ad
commit 9d50446ffb
4 changed files with 238 additions and 377 deletions

View File

@@ -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,
};
}
}

View File

@@ -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);
}
}

View File

@@ -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() {

View File

@@ -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.