diff --git a/core/translate/emitter.rs b/core/translate/emitter.rs index 99c56304e..cb0ed95c4 100644 --- a/core/translate/emitter.rs +++ b/core/translate/emitter.rs @@ -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!() } diff --git a/core/translate/optimizer.rs b/core/translate/optimizer.rs index da08bab1d..07975b020 100644 --- a/core/translate/optimizer.rs +++ b/core/translate/optimizer.rs @@ -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 { - 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 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 { 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, - cmp_expr: ast::Expr, - }, - /// A primary key search. This uses the Search operator and uses bytecode instructions like SeekGT, SeekGE etc. - PrimaryKeySearch { - table: Rc, - 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, - 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, String), available_indexes: &[Rc], -) -> Result> { +) -> Result> { 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, diff --git a/core/translate/plan.rs b/core/translate/plan.rs index 24a3b6513..174ea2d3b 100644 --- a/core/translate/plan.rs +++ b/core/translate/plan.rs @@ -64,19 +64,6 @@ pub enum Operator { source: Box, predicates: Vec, }, - // 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, - table_identifier: String, - rowid_predicate: ast::Expr, - predicates: Option>, - 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>, 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>, - seek_cmp: ast::Operator, - seek_expr: ast::Expr, table: Rc, table_identifier: String, + search: Search, predicates: Option>, 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, + 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::>() - .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)?; }