Get rid of Seekrowid operator in favor of a unified Search operator

This commit is contained in:
jussisaurio
2024-10-06 00:11:38 +03:00
parent d3e797f59e
commit 47534cb8df
3 changed files with 268 additions and 430 deletions

View File

@@ -7,6 +7,7 @@ use sqlite3_parser::ast;
use crate::schema::{BTreeTable, Column, PseudoTable, Table};
use crate::storage::sqlite3_ondisk::DatabaseHeader;
use crate::translate::expr::resolve_ident_pseudo_table;
use crate::translate::plan::Search;
use crate::types::{OwnedRecord, OwnedValue};
use crate::vdbe::builder::ProgramBuilder;
use crate::vdbe::{BranchOffset, Insn, Program};
@@ -176,7 +177,7 @@ impl Emitter for Operator {
} => {
*step += 1;
const SCAN_OPEN_READ: usize = 1;
const SCAN_REWIND_AND_CONDITIONS: usize = 2;
const SCAN_BODY: usize = 2;
const SCAN_NEXT: usize = 3;
match *step {
SCAN_OPEN_READ => {
@@ -195,7 +196,7 @@ impl Emitter for Operator {
Ok(OpStepResult::Continue)
}
SCAN_REWIND_AND_CONDITIONS => {
SCAN_BODY => {
let cursor_id = program.resolve_cursor_id(table_identifier, None);
program.emit_insn(Insn::RewindAsync { cursor_id });
let scan_loop_body_label = program.allocate_label();
@@ -256,9 +257,7 @@ impl Emitter for Operator {
Operator::Search {
table,
table_identifier,
index,
seek_cmp,
seek_expr,
search,
predicates,
step,
id,
@@ -266,7 +265,7 @@ impl Emitter for Operator {
} => {
*step += 1;
const SEARCH_OPEN_READ: usize = 1;
const SEARCH_SEEK_AND_CONDITIONS: usize = 2;
const SEARCH_BODY: usize = 2;
const SEARCH_NEXT: usize = 3;
match *step {
SEARCH_OPEN_READ => {
@@ -275,17 +274,13 @@ impl Emitter for Operator {
Some(Table::BTree(table.clone())),
);
let index_cursor_id = if let Some(index) = index {
program.alloc_cursor_id(
Some(index.name.clone()),
Some(Table::Index(index.clone())),
)
} else {
table_cursor_id
};
let next_row_label = program.allocate_label();
m.next_row_labels.insert(*id, next_row_label);
if !matches!(search, Search::PrimaryKeyEq { .. }) {
// Primary key equality search is handled with a SeekRowid instruction which does not loop, since it is a single row lookup.
m.next_row_labels.insert(*id, next_row_label);
}
let scan_loop_body_label = program.allocate_label();
m.scan_loop_body_labels.push(scan_loop_body_label);
program.emit_insn(Insn::OpenReadAsync {
@@ -294,7 +289,11 @@ impl Emitter for Operator {
});
program.emit_insn(Insn::OpenReadAwait);
if let Some(index) = index {
if let Search::IndexSearch { index, .. } = search {
let index_cursor_id = program.alloc_cursor_id(
Some(index.name.clone()),
Some(Table::Index(index.clone())),
);
program.emit_insn(Insn::OpenReadAsync {
cursor_id: index_cursor_id,
root_page: index.root_page,
@@ -303,162 +302,194 @@ impl Emitter for Operator {
}
Ok(OpStepResult::Continue)
}
SEARCH_SEEK_AND_CONDITIONS => {
SEARCH_BODY => {
let table_cursor_id = program.resolve_cursor_id(table_identifier, None);
let index_cursor_id = if let Some(index) = index {
program.resolve_cursor_id(&index.name, None)
} else {
table_cursor_id
};
let scan_loop_body_label = *m.scan_loop_body_labels.last().unwrap();
let cmp_reg = program.alloc_register();
// TODO this only handles ascending indexes
match seek_cmp {
ast::Operator::Equals
| ast::Operator::Greater
| ast::Operator::GreaterEquals => {
// Open the loop for the index search.
// Primary key equality search is handled with a SeekRowid instruction which does not loop, since it is a single row lookup.
if !matches!(search, Search::PrimaryKeyEq { .. }) {
let index_cursor_id = if let Search::IndexSearch { index, .. } = search
{
Some(program.resolve_cursor_id(&index.name, None))
} else {
None
};
let scan_loop_body_label = *m.scan_loop_body_labels.last().unwrap();
let cmp_reg = program.alloc_register();
let (cmp_expr, cmp_op) = match search {
Search::IndexSearch {
cmp_expr, cmp_op, ..
} => (cmp_expr, cmp_op),
Search::PrimaryKeySearch { cmp_expr, cmp_op } => (cmp_expr, cmp_op),
Search::PrimaryKeyEq { .. } => unreachable!(),
};
// TODO this only handles ascending indexes
match cmp_op {
ast::Operator::Equals
| ast::Operator::Greater
| ast::Operator::GreaterEquals => {
translate_expr(
program,
Some(referenced_tables),
cmp_expr,
cmp_reg,
None,
None,
)?;
}
ast::Operator::Less | ast::Operator::LessEquals => {
program.emit_insn(Insn::Null {
dest: cmp_reg,
dest_end: None,
});
}
_ => unreachable!(),
}
program.emit_insn_with_label_dependency(
match cmp_op {
ast::Operator::Equals | ast::Operator::GreaterEquals => {
Insn::SeekGE {
is_index: index_cursor_id.is_some(),
cursor_id: index_cursor_id.unwrap_or(table_cursor_id),
start_reg: cmp_reg,
num_regs: 1,
target_pc: *m.termination_label_stack.last().unwrap(),
}
}
ast::Operator::Greater
| ast::Operator::Less
| ast::Operator::LessEquals => Insn::SeekGT {
is_index: index_cursor_id.is_some(),
cursor_id: index_cursor_id.unwrap_or(table_cursor_id),
start_reg: cmp_reg,
num_regs: 1,
target_pc: *m.termination_label_stack.last().unwrap(),
},
_ => unreachable!(),
},
*m.termination_label_stack.last().unwrap(),
);
if *cmp_op == ast::Operator::Less
|| *cmp_op == ast::Operator::LessEquals
{
translate_expr(
program,
Some(referenced_tables),
seek_expr,
cmp_expr,
cmp_reg,
None,
None,
)?;
}
ast::Operator::Less | ast::Operator::LessEquals => {
program.emit_insn(Insn::Null {
dest: cmp_reg,
dest_end: None,
});
}
_ => unreachable!(),
}
program.emit_insn_with_label_dependency(
match seek_cmp {
ast::Operator::Equals | ast::Operator::GreaterEquals => {
Insn::SeekGE {
is_index: index.is_some(),
cursor_id: index_cursor_id,
start_reg: cmp_reg,
num_regs: 1,
target_pc: *m.termination_label_stack.last().unwrap(),
program.defer_label_resolution(
scan_loop_body_label,
program.offset() as usize,
);
// TODO: We are currently only handling ascending indexes.
// For conditions like index_key > 10, we have already seeked to the first key greater than 10, and can just scan forward.
// For conditions like index_key < 10, we are at the beginning of the index, and will scan forward and emit IdxGE(10) with a conditional jump to the end.
// For conditions like index_key = 10, we have already seeked to the first key greater than or equal to 10, and can just scan forward and emit IdxGT(10) with a conditional jump to the end.
// For conditions like index_key >= 10, we have already seeked to the first key greater than or equal to 10, and can just scan forward.
// For conditions like index_key <= 10, we are at the beginning of the index, and will scan forward and emit IdxGT(10) with a conditional jump to the end.
// For conditions like index_key != 10, TODO. probably the optimal way is not to use an index at all.
//
// For primary key searches we emit RowId and then compare it to the seek value.
let abort_jump_target = *m
.next_row_labels
.get(id)
.unwrap_or(m.termination_label_stack.last().unwrap());
match cmp_op {
ast::Operator::Equals | ast::Operator::LessEquals => {
if index_cursor_id.is_some() {
program.emit_insn_with_label_dependency(
Insn::IdxGT {
cursor_id: index_cursor_id.unwrap(),
start_reg: cmp_reg,
num_regs: 1,
target_pc: abort_jump_target,
},
abort_jump_target,
);
} else {
let rowid_reg = program.alloc_register();
program.emit_insn(Insn::RowId {
cursor_id: table_cursor_id,
dest: rowid_reg,
});
program.emit_insn_with_label_dependency(
Insn::Gt {
lhs: rowid_reg,
rhs: cmp_reg,
target_pc: abort_jump_target,
},
abort_jump_target,
);
}
}
ast::Operator::Greater
| ast::Operator::Less
| ast::Operator::LessEquals => Insn::SeekGT {
is_index: index.is_some(),
cursor_id: index_cursor_id,
start_reg: cmp_reg,
num_regs: 1,
target_pc: *m.termination_label_stack.last().unwrap(),
},
_ => unreachable!(),
},
*m.termination_label_stack.last().unwrap(),
);
if *seek_cmp == ast::Operator::Less
|| *seek_cmp == ast::Operator::LessEquals
{
translate_expr(
program,
Some(referenced_tables),
seek_expr,
cmp_reg,
None,
None,
)?;
}
program.defer_label_resolution(
scan_loop_body_label,
program.offset() as usize,
);
// TODO: We are currently only handling ascending indexes.
// For conditions like index_key > 10, we have already seeked to the first key greater than 10, and can just scan forward.
// For conditions like index_key < 10, we are at the beginning of the index, and will scan forward and emit IdxGE(10) with a conditional jump to the end.
// For conditions like index_key = 10, we have already seeked to the first key greater than or equal to 10, and can just scan forward and emit IdxGT(10) with a conditional jump to the end.
// For conditions like index_key >= 10, we have already seeked to the first key greater than or equal to 10, and can just scan forward.
// For conditions like index_key <= 10, we are at the beginning of the index, and will scan forward and emit IdxGT(10) with a conditional jump to the end.
// For conditions like index_key != 10, TODO. probably the optimal way is not to use an index at all.
//
// For primary key searches we emit RowId and then compare it to the seek value.
let abort_jump_target = *m
.next_row_labels
.get(id)
.unwrap_or(m.termination_label_stack.last().unwrap());
match seek_cmp {
ast::Operator::Equals | ast::Operator::LessEquals => {
if index.is_some() {
program.emit_insn_with_label_dependency(
Insn::IdxGT {
cursor_id: index_cursor_id,
start_reg: cmp_reg,
num_regs: 1,
target_pc: abort_jump_target,
},
abort_jump_target,
);
} else {
let rowid_reg = program.alloc_register();
program.emit_insn(Insn::RowId {
cursor_id: table_cursor_id,
dest: rowid_reg,
});
program.emit_insn_with_label_dependency(
Insn::Gt {
lhs: rowid_reg,
rhs: cmp_reg,
target_pc: abort_jump_target,
},
abort_jump_target,
);
ast::Operator::Less => {
if index_cursor_id.is_some() {
program.emit_insn_with_label_dependency(
Insn::IdxGE {
cursor_id: index_cursor_id.unwrap(),
start_reg: cmp_reg,
num_regs: 1,
target_pc: abort_jump_target,
},
abort_jump_target,
);
} else {
let rowid_reg = program.alloc_register();
program.emit_insn(Insn::RowId {
cursor_id: table_cursor_id,
dest: rowid_reg,
});
program.emit_insn_with_label_dependency(
Insn::Ge {
lhs: rowid_reg,
rhs: cmp_reg,
target_pc: abort_jump_target,
},
abort_jump_target,
);
}
}
_ => {}
}
ast::Operator::Less => {
if index.is_some() {
program.emit_insn_with_label_dependency(
Insn::IdxGE {
cursor_id: index_cursor_id,
start_reg: cmp_reg,
num_regs: 1,
target_pc: abort_jump_target,
},
abort_jump_target,
);
} else {
let rowid_reg = program.alloc_register();
program.emit_insn(Insn::RowId {
cursor_id: table_cursor_id,
dest: rowid_reg,
});
program.emit_insn_with_label_dependency(
Insn::Ge {
lhs: rowid_reg,
rhs: cmp_reg,
target_pc: abort_jump_target,
},
abort_jump_target,
);
}
}
_ => {}
}
if index.is_some() {
program.emit_insn(Insn::DeferredSeek {
index_cursor_id,
table_cursor_id,
});
if index_cursor_id.is_some() {
program.emit_insn(Insn::DeferredSeek {
index_cursor_id: index_cursor_id.unwrap(),
table_cursor_id,
});
}
}
let jump_label = m
.next_row_labels
.get(id)
.unwrap_or(m.termination_label_stack.last().unwrap());
if let Search::PrimaryKeyEq { cmp_expr } = search {
let src_reg = program.alloc_register();
translate_expr(
program,
Some(referenced_tables),
cmp_expr,
src_reg,
None,
None,
)?;
program.emit_insn_with_label_dependency(
Insn::SeekRowid {
cursor_id: table_cursor_id,
src_reg,
target_pc: *jump_label,
},
*jump_label,
);
}
if let Some(predicates) = predicates {
for predicate in predicates.iter() {
let jump_target_when_true = program.allocate_label();
@@ -481,10 +512,18 @@ impl Emitter for Operator {
Ok(OpStepResult::ReadyToEmit)
}
SEARCH_NEXT => {
let cursor_id = if let Some(index) = index {
program.resolve_cursor_id(&index.name, None)
} else {
program.resolve_cursor_id(table_identifier, None)
if matches!(search, Search::PrimaryKeyEq { .. }) {
// Primary key equality search is handled with a SeekRowid instruction which does not loop, so there is no need to emit a NextAsync instruction.
return Ok(OpStepResult::Done);
}
let cursor_id = match search {
Search::IndexSearch { index, .. } => {
program.resolve_cursor_id(&index.name, None)
}
Search::PrimaryKeySearch { .. } => {
program.resolve_cursor_id(table_identifier, None)
}
Search::PrimaryKeyEq { .. } => unreachable!(),
};
program
.resolve_label(*m.next_row_labels.get(id).unwrap(), program.offset());
@@ -502,80 +541,6 @@ impl Emitter for Operator {
_ => Ok(OpStepResult::Done),
}
}
Operator::SeekRowid {
table,
table_identifier,
rowid_predicate,
predicates,
step,
id,
..
} => {
*step += 1;
const SEEKROWID_OPEN_READ: usize = 1;
const SEEKROWID_SEEK_AND_CONDITIONS: usize = 2;
match *step {
SEEKROWID_OPEN_READ => {
let cursor_id = program.alloc_cursor_id(
Some(table_identifier.clone()),
Some(Table::BTree(table.clone())),
);
let root_page = table.root_page;
program.emit_insn(Insn::OpenReadAsync {
cursor_id,
root_page,
});
program.emit_insn(Insn::OpenReadAwait);
Ok(OpStepResult::Continue)
}
SEEKROWID_SEEK_AND_CONDITIONS => {
let cursor_id = program.resolve_cursor_id(table_identifier, None);
let rowid_reg = program.alloc_register();
translate_expr(
program,
Some(referenced_tables),
rowid_predicate,
rowid_reg,
None,
None,
)?;
let jump_label = m
.next_row_labels
.get(id)
.unwrap_or(m.termination_label_stack.last().unwrap());
program.emit_insn_with_label_dependency(
Insn::SeekRowid {
cursor_id,
src_reg: rowid_reg,
target_pc: *jump_label,
},
*jump_label,
);
if let Some(predicates) = predicates {
for predicate in predicates.iter() {
let jump_target_when_true = program.allocate_label();
let condition_metadata = ConditionMetadata {
jump_if_condition_is_true: false,
jump_target_when_true,
jump_target_when_false: *jump_label,
};
translate_condition_expr(
program,
referenced_tables,
predicate,
None,
condition_metadata,
)?;
program.resolve_label(jump_target_when_true, program.offset());
}
}
Ok(OpStepResult::ReadyToEmit)
}
_ => Ok(OpStepResult::Done),
}
}
Operator::Join {
left,
right,
@@ -679,7 +644,7 @@ impl Emitter for Operator {
Operator::Scan {
table_identifier, ..
} => program.resolve_cursor_id(table_identifier, None),
Operator::SeekRowid {
Operator::Search {
table_identifier, ..
} => program.resolve_cursor_id(table_identifier, None),
_ => unreachable!(),
@@ -694,9 +659,14 @@ impl Emitter for Operator {
},
lj_meta.set_match_flag_true_label,
);
// This points to the NextAsync instruction of the left table
program.resolve_label(lj_meta.on_match_jump_to_label, program.offset());
}
let next_row_label = if *outer {
m.left_joins.get(id).unwrap().on_match_jump_to_label
} else {
*m.next_row_labels.get(&right.id()).unwrap()
};
// This points to the NextAsync instruction of the left table
program.resolve_label(next_row_label, program.offset());
left.step(program, m, referenced_tables)?;
Ok(OpStepResult::Done)
@@ -1524,23 +1494,6 @@ impl Emitter for Operator {
}
}
Operator::Filter { .. } => unreachable!("predicates have been pushed down"),
Operator::SeekRowid {
table_identifier,
table,
..
} => {
let start_reg = program.alloc_registers(col_count);
let table = cursor_override
.map(|c| c.pseudo_table.clone())
.unwrap_or_else(|| Table::BTree(table.clone()));
let cursor_id = cursor_override
.map(|c| c.cursor_id)
.unwrap_or_else(|| program.resolve_cursor_id(table_identifier, None));
let start_column_offset = cursor_override.map(|c| c.sort_key_len).unwrap_or(0);
translate_table_columns(program, cursor_id, &table, start_column_offset, start_reg);
Ok(start_reg)
}
Operator::Limit { .. } => {
unimplemented!()
}

View File

@@ -4,14 +4,13 @@ use sqlite3_parser::ast;
use crate::{
schema::{BTreeTable, Index},
types::OwnedValue,
util::normalize_ident,
Result,
};
use super::plan::{
get_table_ref_bitmask_for_ast_expr, get_table_ref_bitmask_for_operator, Operator, Plan,
ProjectionColumn,
ProjectionColumn, Search,
};
/**
@@ -72,60 +71,23 @@ fn use_indexes(
.iter()
.find(|(t, t_id)| Rc::ptr_eq(t, table) && t_id == table_identifier)
.unwrap();
match try_extract_expr_that_utilizes_index(f, table, available_indexes)? {
match try_extract_index_search_expression(f, table, available_indexes)? {
Either::Left(non_index_using_expr) => {
fs[i] = non_index_using_expr;
}
Either::Right(index_using_expr) => match index_using_expr {
SearchableExpr::IndexSearch {
index,
cmp_op,
cmp_expr,
} => {
fs.remove(i);
*operator = Operator::Search {
table: table.0.clone(),
index: Some(index.clone()),
predicates: Some(std::mem::take(fs)),
seek_cmp: cmp_op,
seek_expr: cmp_expr,
table_identifier: table_identifier.clone(),
id: *id,
step: 0,
};
return Ok(());
}
SearchableExpr::PrimaryKeySearch {
table,
cmp_op,
cmp_expr,
} => {
fs.remove(i);
*operator = Operator::Search {
table,
index: None,
predicates: Some(std::mem::take(fs)),
seek_cmp: cmp_op,
seek_expr: cmp_expr,
table_identifier: table_identifier.clone(),
id: *id,
step: 0,
};
return Ok(());
}
SearchableExpr::PrimaryKeyEq { cmp_expr, table } => {
fs.remove(i);
*operator = Operator::SeekRowid {
table,
table_identifier: table_identifier.clone(),
rowid_predicate: cmp_expr,
predicates: Some(std::mem::take(fs)),
id: *id,
step: 0,
};
return Ok(());
}
},
Either::Right(index_search) => {
fs.remove(i);
*operator = Operator::Search {
id: *id,
table: table.0.clone(),
table_identifier: table.1.clone(),
predicates: Some(fs.clone()),
search: index_search,
step: 0,
};
return Ok(());
}
}
}
@@ -139,7 +101,6 @@ fn use_indexes(
use_indexes(source, referenced_tables, available_indexes)?;
Ok(())
}
Operator::SeekRowid { .. } => Ok(()),
Operator::Limit { source, .. } => {
use_indexes(source, referenced_tables, available_indexes)?;
Ok(())
@@ -242,31 +203,6 @@ fn eliminate_constants(operator: &mut Operator) -> Result<ConstantConditionElimi
// Aggregation operator can return a row even if the source is empty e.g. count(1) from users where 0
Ok(ConstantConditionEliminationResult::Continue)
}
Operator::SeekRowid {
rowid_predicate,
predicates,
..
} => {
if let Some(predicates) = predicates {
let mut i = 0;
while i < predicates.len() {
let predicate = &predicates[i];
if predicate.is_always_true()? {
predicates.remove(i);
} else if predicate.is_always_false()? {
return Ok(ConstantConditionEliminationResult::ImpossibleCondition);
} else {
i += 1;
}
}
}
if rowid_predicate.is_always_false()? {
return Ok(ConstantConditionEliminationResult::ImpossibleCondition);
}
Ok(ConstantConditionEliminationResult::Continue)
}
Operator::Limit { source, .. } => {
let constant_elimination_result = eliminate_constants(source)?;
if constant_elimination_result
@@ -315,7 +251,23 @@ fn eliminate_constants(operator: &mut Operator) -> Result<ConstantConditionElimi
}
Ok(ConstantConditionEliminationResult::Continue)
}
Operator::Search { .. } => Ok(ConstantConditionEliminationResult::Continue),
Operator::Search { predicates, .. } => {
if let Some(predicates) = predicates {
let mut i = 0;
while i < predicates.len() {
let predicate = &predicates[i];
if predicate.is_always_true()? {
predicates.remove(i);
} else if predicate.is_always_false()? {
return Ok(ConstantConditionEliminationResult::ImpossibleCondition);
} else {
i += 1;
}
}
}
Ok(ConstantConditionEliminationResult::Continue)
}
Operator::Nothing => Ok(ConstantConditionEliminationResult::Continue),
}
}
@@ -402,7 +354,6 @@ fn push_predicates(
Ok(())
}
Operator::SeekRowid { .. } => Ok(()),
Operator::Limit { source, .. } => {
push_predicates(source, referenced_tables)?;
Ok(())
@@ -525,7 +476,6 @@ fn push_predicate(
Ok(Some(push_result.unwrap()))
}
Operator::SeekRowid { .. } => Ok(Some(predicate)),
Operator::Limit { source, .. } => {
let push_result = push_predicate(source, predicate, referenced_tables)?;
if push_result.is_none() {
@@ -702,7 +652,6 @@ fn find_indexes_of_all_result_columns_in_operator_that_match_expr_either_fully_o
mask
}
Operator::Filter { .. } => 0,
Operator::SeekRowid { .. } => 0,
Operator::Limit { .. } => 0,
Operator::Join { .. } => 0,
Operator::Order { .. } => 0,
@@ -887,7 +836,6 @@ fn find_shared_expressions_in_child_operators_and_mark_them_so_that_the_parent_o
)
}
Operator::Filter { .. } => unreachable!(),
Operator::SeekRowid { .. } => {}
Operator::Limit { source, .. } => {
find_shared_expressions_in_child_operators_and_mark_them_so_that_the_parent_operator_doesnt_recompute_them(source, expr_result_cache)
}
@@ -1160,51 +1108,23 @@ pub enum Either<T, U> {
Right(U),
}
/// An expression that can be used to search for a row in a table using an index
/// (i.e. a primary key or a secondary index)
///
pub enum SearchableExpr {
/// A primary key equality search. This is a special case of the primary key search
/// that uses the SeekRowid operator and bytecode instruction.
PrimaryKeyEq {
table: Rc<BTreeTable>,
cmp_expr: ast::Expr,
},
/// A primary key search. This uses the Search operator and uses bytecode instructions like SeekGT, SeekGE etc.
PrimaryKeySearch {
table: Rc<BTreeTable>,
cmp_op: ast::Operator,
cmp_expr: ast::Expr,
},
/// A secondary index search. This uses the Search operator and uses bytecode instructions like SeekGE, SeekGT etc.
IndexSearch {
index: Rc<Index>,
cmp_op: ast::Operator,
cmp_expr: ast::Expr,
},
}
pub fn try_extract_expr_that_utilizes_index(
pub fn try_extract_index_search_expression(
expr: ast::Expr,
table: &(Rc<BTreeTable>, String),
available_indexes: &[Rc<Index>],
) -> Result<Either<ast::Expr, SearchableExpr>> {
) -> Result<Either<ast::Expr, Search>> {
match expr {
ast::Expr::Binary(mut lhs, operator, mut rhs) => {
if lhs.is_primary_key_of(table) {
match operator {
ast::Operator::Equals => {
return Ok(Either::Right(SearchableExpr::PrimaryKeyEq {
table: table.0.clone(),
cmp_expr: *rhs,
}));
return Ok(Either::Right(Search::PrimaryKeyEq { cmp_expr: *rhs }));
}
ast::Operator::Greater
| ast::Operator::GreaterEquals
| ast::Operator::Less
| ast::Operator::LessEquals => {
return Ok(Either::Right(SearchableExpr::PrimaryKeySearch {
table: table.0.clone(),
return Ok(Either::Right(Search::PrimaryKeySearch {
cmp_op: operator,
cmp_expr: *rhs,
}));
@@ -1216,17 +1136,13 @@ pub fn try_extract_expr_that_utilizes_index(
if rhs.is_primary_key_of(table) {
match operator {
ast::Operator::Equals => {
return Ok(Either::Right(SearchableExpr::PrimaryKeyEq {
table: table.0.clone(),
cmp_expr: *lhs,
}));
return Ok(Either::Right(Search::PrimaryKeyEq { cmp_expr: *lhs }));
}
ast::Operator::Greater
| ast::Operator::GreaterEquals
| ast::Operator::Less
| ast::Operator::LessEquals => {
return Ok(Either::Right(SearchableExpr::PrimaryKeySearch {
table: table.0.clone(),
return Ok(Either::Right(Search::PrimaryKeySearch {
cmp_op: operator,
cmp_expr: *lhs,
}));
@@ -1242,7 +1158,7 @@ pub fn try_extract_expr_that_utilizes_index(
| ast::Operator::GreaterEquals
| ast::Operator::Less
| ast::Operator::LessEquals => {
return Ok(Either::Right(SearchableExpr::IndexSearch {
return Ok(Either::Right(Search::IndexSearch {
index: available_indexes[index_index].clone(),
cmp_op: operator,
cmp_expr: *rhs,
@@ -1259,7 +1175,7 @@ pub fn try_extract_expr_that_utilizes_index(
| ast::Operator::GreaterEquals
| ast::Operator::Less
| ast::Operator::LessEquals => {
return Ok(Either::Right(SearchableExpr::IndexSearch {
return Ok(Either::Right(Search::IndexSearch {
index: available_indexes[index_index].clone(),
cmp_op: operator,
cmp_expr: *lhs,

View File

@@ -64,19 +64,6 @@ pub enum Operator {
source: Box<Operator>,
predicates: Vec<ast::Expr>,
},
// SeekRowid operator
// This operator is used to retrieve a single row from a table by its rowid.
// rowid_predicate is an expression that produces the comparison value for the rowid.
// e.g. rowid = 5, or rowid = other_table.foo
// predicates is an optional list of additional predicates to evaluate.
SeekRowid {
id: usize,
table: Rc<BTreeTable>,
table_identifier: String,
rowid_predicate: ast::Expr,
predicates: Option<Vec<ast::Expr>>,
step: usize,
},
// Limit operator
// This operator is used to limit the number of rows returned by the source operator.
Limit {
@@ -129,13 +116,14 @@ pub enum Operator {
predicates: Option<Vec<ast::Expr>>,
step: usize,
},
// Search operator
// This operator is used to search for a row in a table using an index
// (i.e. a primary key or a secondary index)
Search {
id: usize,
index: Option<Rc<Index>>,
seek_cmp: ast::Operator,
seek_expr: ast::Expr,
table: Rc<BTreeTable>,
table_identifier: String,
search: Search,
predicates: Option<Vec<ast::Expr>>,
step: usize,
},
@@ -145,6 +133,26 @@ pub enum Operator {
Nothing,
}
/// An enum that represents a search operation that can be used to search for a row in a table using an index
/// (i.e. a primary key or a secondary index)
#[derive(Clone, Debug)]
pub enum Search {
/// A primary key equality search. This is a special case of the primary key search
/// that uses the SeekRowid bytecode instruction.
PrimaryKeyEq { cmp_expr: ast::Expr },
/// A primary key search. Uses bytecode instructions like SeekGT, SeekGE etc.
PrimaryKeySearch {
cmp_op: ast::Operator,
cmp_expr: ast::Expr,
},
/// A secondary index search. Uses bytecode instructions like SeekGE, SeekGT etc.
IndexSearch {
index: Rc<Index>,
cmp_op: ast::Operator,
cmp_expr: ast::Expr,
},
}
#[derive(Clone, Debug)]
pub enum ProjectionColumn {
Column(ast::Expr),
@@ -177,7 +185,6 @@ impl Operator {
..
} => aggregates.len() + group_by.as_ref().map_or(0, |g| g.len()),
Operator::Filter { source, .. } => source.column_count(referenced_tables),
Operator::SeekRowid { table, .. } => table.columns.len(),
Operator::Limit { source, .. } => source.column_count(referenced_tables),
Operator::Join { left, right, .. } => {
left.column_count(referenced_tables) + right.column_count(referenced_tables)
@@ -220,9 +227,6 @@ impl Operator {
names
}
Operator::Filter { source, .. } => source.column_names(),
Operator::SeekRowid { table, .. } => {
table.columns.iter().map(|c| c.name.clone()).collect()
}
Operator::Limit { source, .. } => source.column_names(),
Operator::Join { left, right, .. } => {
let mut names = left.column_names();
@@ -254,7 +258,6 @@ impl Operator {
match self {
Operator::Aggregate { id, .. } => *id,
Operator::Filter { id, .. } => *id,
Operator::SeekRowid { id, .. } => *id,
Operator::Limit { id, .. } => *id,
Operator::Join { id, .. } => *id,
Operator::Order { id, .. } => *id,
@@ -343,33 +346,6 @@ impl Display for Operator {
writeln!(f, "{}FILTER {}", indent, predicates_string)?;
fmt_operator(source, f, level + 1, true)
}
Operator::SeekRowid {
table,
rowid_predicate,
predicates,
..
} => {
match predicates {
Some(ps) => {
let predicates_string = ps
.iter()
.map(|p| p.to_string())
.collect::<Vec<String>>()
.join(" AND ");
writeln!(
f,
"{}SEEK {}.rowid ON rowid={} FILTER {}",
indent, &table.name, rowid_predicate, predicates_string
)?;
}
None => writeln!(
f,
"{}SEEK {}.rowid ON rowid={}",
indent, &table.name, rowid_predicate
)?,
}
Ok(())
}
Operator::Limit { source, limit, .. } => {
writeln!(f, "{}TAKE {}", indent, limit)?;
fmt_operator(source, f, level + 1, true)
@@ -487,13 +463,6 @@ pub fn get_table_ref_bitmask_for_operator<'a>(
table_refs_mask |= get_table_ref_bitmask_for_ast_expr(tables, predicate)?;
}
}
Operator::SeekRowid { table, .. } => {
table_refs_mask |= 1
<< tables
.iter()
.position(|(t, _)| Rc::ptr_eq(t, table))
.unwrap();
}
Operator::Limit { source, .. } => {
table_refs_mask |= get_table_ref_bitmask_for_operator(tables, source)?;
}