mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-09 10:14:21 +01:00
Get rid of Seekrowid operator in favor of a unified Search operator
This commit is contained in:
@@ -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!()
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)?;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user