diff --git a/core/translate/emitter.rs b/core/translate/emitter.rs index b392da5ad..bb6892334 100644 --- a/core/translate/emitter.rs +++ b/core/translate/emitter.rs @@ -27,7 +27,7 @@ use crate::schema::{Index, IndexColumn, Schema}; use crate::translate::plan::{DeletePlan, Plan, Search}; use crate::translate::values::emit_values; use crate::util::exprs_are_equivalent; -use crate::vdbe::builder::{CursorType, ProgramBuilder}; +use crate::vdbe::builder::{CursorKey, CursorType, ProgramBuilder}; use crate::vdbe::insn::{CmpInsFlags, IdxInsertFlags, RegisterOrLiteral}; use crate::vdbe::{insn::Insn, BranchOffset}; use crate::{Result, SymbolTable}; @@ -212,7 +212,7 @@ fn emit_program_for_compound_select( if limit == 0 { program.epilogue(TransactionMode::Read); program.result_columns = first.result_columns; - program.table_references = first.table_references; + program.table_references.extend(first.table_references); return Ok(()); } } @@ -370,7 +370,7 @@ fn emit_program_for_compound_select( program.epilogue(TransactionMode::Read); program.result_columns = first.result_columns; - program.table_references = first.table_references; + program.table_references.extend(first.table_references); Ok(()) } @@ -402,10 +402,7 @@ fn get_union_dedupe_index( unique: true, has_rowid: false, }); - let cursor_id = program.alloc_cursor_id( - Some(dedupe_index.name.clone()), - CursorType::BTreeIndex(dedupe_index.clone()), - ); + let cursor_id = program.alloc_cursor_id(CursorType::BTreeIndex(dedupe_index.clone())); program.emit_insn(Insn::OpenEphemeral { cursor_id, is_table: false, @@ -487,7 +484,7 @@ fn emit_program_for_select( if limit == 0 { program.epilogue(TransactionMode::Read); program.result_columns = plan.result_columns; - program.table_references = plan.table_references; + program.table_references.extend(plan.table_references); return Ok(()); } } @@ -502,7 +499,7 @@ fn emit_program_for_select( } program.result_columns = plan.result_columns; - program.table_references = plan.table_references; + program.table_references.extend(plan.table_references); Ok(()) } @@ -658,7 +655,7 @@ fn emit_program_for_delete( if let Some(0) = plan.limit { program.epilogue(TransactionMode::Write); program.result_columns = plan.result_columns; - program.table_references = plan.table_references; + program.table_references.extend(plan.table_references); return Ok(()); } @@ -705,7 +702,7 @@ fn emit_program_for_delete( // Finalize program program.epilogue(TransactionMode::Write); program.result_columns = plan.result_columns; - program.table_references = plan.table_references; + program.table_references.extend(plan.table_references); Ok(()) } @@ -717,17 +714,23 @@ fn emit_delete_insns( ) -> Result<()> { let table_reference = table_references.first().unwrap(); let cursor_id = match &table_reference.op { - Operation::Scan { .. } => program.resolve_cursor_id(&table_reference.identifier), + Operation::Scan { .. } => { + program.resolve_cursor_id(&CursorKey::table(table_reference.internal_id)) + } Operation::Search(search) => match search { Search::RowidEq { .. } | Search::Seek { index: None, .. } => { - program.resolve_cursor_id(&table_reference.identifier) + program.resolve_cursor_id(&CursorKey::table(table_reference.internal_id)) } Search::Seek { index: Some(index), .. - } => program.resolve_cursor_id(&index.name), + } => program.resolve_cursor_id(&CursorKey::index( + table_reference.internal_id, + index.clone(), + )), }, }; - let main_table_cursor_id = program.resolve_cursor_id(table_reference.table.get_name()); + let main_table_cursor_id = + program.resolve_cursor_id(&CursorKey::table(table_reference.internal_id)); // Emit the instructions to delete the row let key_reg = program.alloc_register(); @@ -753,10 +756,7 @@ fn emit_delete_insns( }); } else { for index in index_references { - let index_cursor_id = program.alloc_cursor_id( - Some(index.name.clone()), - crate::vdbe::builder::CursorType::BTreeIndex(index.clone()), - ); + let index_cursor_id = program.alloc_cursor_id(CursorType::BTreeIndex(index.clone())); program.emit_insn(Insn::OpenWrite { cursor_id: index_cursor_id, @@ -819,7 +819,7 @@ fn emit_program_for_update( if let Some(0) = plan.limit { program.epilogue(TransactionMode::None); program.result_columns = plan.returning.unwrap_or_default(); - program.table_references = plan.table_references; + program.table_references.extend(plan.table_references); return Ok(()); } @@ -845,10 +845,7 @@ fn emit_program_for_update( // TODO: do not reopen if there is table reference using it. for index in &plan.indexes_to_update { - let index_cursor = program.alloc_cursor_id( - Some(index.table_name.clone()), - CursorType::BTreeIndex(index.clone()), - ); + let index_cursor = program.alloc_cursor_id(CursorType::BTreeIndex(index.clone())); program.emit_insn(Insn::OpenWrite { cursor_id: index_cursor, root_page: RegisterOrLiteral::Literal(index.root_page), @@ -888,7 +885,7 @@ fn emit_program_for_update( // Finalize program program.epilogue(TransactionMode::Write); program.result_columns = plan.returning.unwrap_or_default(); - program.table_references = plan.table_references; + program.table_references.extend(plan.table_references); Ok(()) } @@ -901,22 +898,32 @@ fn emit_update_insns( let table_ref = &plan.table_references.first().unwrap(); let loop_labels = t_ctx.labels_main_loop.first().unwrap(); let (cursor_id, index, is_virtual) = match &table_ref.op { - Operation::Scan { .. } => ( - program.resolve_cursor_id(&table_ref.identifier), - None, + Operation::Scan { index, .. } => ( + program.resolve_cursor_id(&CursorKey::table(table_ref.internal_id)), + index.as_ref().map(|index| { + ( + index.clone(), + program + .resolve_cursor_id(&CursorKey::index(table_ref.internal_id, index.clone())), + ) + }), table_ref.virtual_table().is_some(), ), Operation::Search(search) => match search { &Search::RowidEq { .. } | Search::Seek { index: None, .. } => ( - program.resolve_cursor_id(&table_ref.identifier), + program.resolve_cursor_id(&CursorKey::table(table_ref.internal_id)), None, false, ), Search::Seek { index: Some(index), .. } => ( - program.resolve_cursor_id(&table_ref.identifier), - Some((index.clone(), program.resolve_cursor_id(&index.name))), + program.resolve_cursor_id(&CursorKey::table(table_ref.internal_id)), + Some(( + index.clone(), + program + .resolve_cursor_id(&CursorKey::index(table_ref.internal_id, index.clone())), + )), false, ), }, diff --git a/core/translate/expr.rs b/core/translate/expr.rs index 5e382535a..8e47bbcdd 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -9,6 +9,7 @@ use crate::function::{Func, FuncCtx, MathFuncArity, ScalarFunc, VectorFunc}; use crate::functions::datetime; use crate::schema::{Table, Type}; use crate::util::{exprs_are_equivalent, normalize_ident, parse_numeric_literal}; +use crate::vdbe::builder::CursorKey; use crate::vdbe::{ builder::ProgramBuilder, insn::{CmpInsFlags, Insn}, @@ -1813,9 +1814,17 @@ pub fn translate_expr( let table_cursor_id = if use_covering_index { None } else { - Some(program.resolve_cursor_id(&table_reference.identifier)) + Some( + program + .resolve_cursor_id(&CursorKey::table(table_reference.internal_id)), + ) }; - let index_cursor_id = index.map(|index| program.resolve_cursor_id(&index.name)); + let index_cursor_id = index.map(|index| { + program.resolve_cursor_id(&CursorKey::index( + table_reference.internal_id, + index.clone(), + )) + }); if *is_rowid_alias { if let Some(index_cursor_id) = index_cursor_id { program.emit_insn(Insn::IdxRowId { @@ -1876,7 +1885,8 @@ pub fn translate_expr( Ok(target_register) } Table::Virtual(_) => { - let cursor_id = program.resolve_cursor_id(&table_reference.identifier); + let cursor_id = + program.resolve_cursor_id(&CursorKey::table(table_reference.internal_id)); program.emit_insn(Insn::VColumn { cursor_id, column: *column, @@ -1899,13 +1909,17 @@ pub fn translate_expr( if use_covering_index { let index = index.expect("index cursor should be opened when use_covering_index=true"); - let cursor_id = program.resolve_cursor_id(&index.name); + let cursor_id = program.resolve_cursor_id(&CursorKey::index( + table_reference.internal_id, + index.clone(), + )); program.emit_insn(Insn::IdxRowId { cursor_id, dest: target_register, }); } else { - let cursor_id = program.resolve_cursor_id(&table_reference.identifier); + let cursor_id = + program.resolve_cursor_id(&CursorKey::table(table_reference.internal_id)); program.emit_insn(Insn::RowId { cursor_id, dest: target_register, diff --git a/core/translate/group_by.rs b/core/translate/group_by.rs index 8f9a11af8..afcffcfdc 100644 --- a/core/translate/group_by.rs +++ b/core/translate/group_by.rs @@ -117,7 +117,7 @@ pub fn init_group_by( let reg_group_by_source_cols_start = program.alloc_registers(column_count); let row_source = if let Some(sort_order) = group_by.sort_order.as_ref() { - let sort_cursor = program.alloc_cursor_id(None, CursorType::Sorter); + let sort_cursor = program.alloc_cursor_id(CursorType::Sorter); let sorter_column_count = plan.group_by_sorter_column_count(); // Should work the same way as Order By /* @@ -262,7 +262,7 @@ pub fn group_by_create_pseudo_table( columns: pseudo_columns, }); - program.alloc_cursor_id(None, CursorType::Pseudo(pseudo_table.clone())) + program.alloc_cursor_id(CursorType::Pseudo(pseudo_table.clone())) } /// In case sorting is needed for GROUP BY, sorts the rows in the GROUP BY sorter diff --git a/core/translate/index.rs b/core/translate/index.rs index 7b1e898de..d8edfa591 100644 --- a/core/translate/index.rs +++ b/core/translate/index.rs @@ -72,21 +72,13 @@ pub fn translate_create_index( // 4. sorter_cursor_id - sorter // 5. pseudo_cursor_id - pseudo table to store the sorted index values let sqlite_table = schema.get_btree_table(SQLITE_TABLEID).unwrap(); - let sqlite_schema_cursor_id = program.alloc_cursor_id( - Some(SQLITE_TABLEID.to_owned()), - CursorType::BTreeTable(sqlite_table.clone()), - ); - let btree_cursor_id = program.alloc_cursor_id( - Some(idx_name.to_owned()), - CursorType::BTreeIndex(idx.clone()), - ); - let table_cursor_id = program.alloc_cursor_id( - Some(tbl_name.to_owned()), - CursorType::BTreeTable(tbl.clone()), - ); - let sorter_cursor_id = program.alloc_cursor_id(None, CursorType::Sorter); + let sqlite_schema_cursor_id = + program.alloc_cursor_id(CursorType::BTreeTable(sqlite_table.clone())); + let btree_cursor_id = program.alloc_cursor_id(CursorType::BTreeIndex(idx.clone())); + let table_cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(tbl.clone())); + let sorter_cursor_id = program.alloc_cursor_id(CursorType::Sorter); let pseudo_table = PseudoTable::new_with_columns(tbl.columns.clone()); - let pseudo_cursor_id = program.alloc_cursor_id(None, CursorType::Pseudo(pseudo_table.into())); + let pseudo_cursor_id = program.alloc_cursor_id(CursorType::Pseudo(pseudo_table.into())); // Create a new B-Tree and store the root page index in a register let root_page_reg = program.alloc_register(); @@ -359,10 +351,8 @@ pub fn translate_drop_index( // We're going to use this cursor to search through sqlite_schema let sqlite_table = schema.get_btree_table(SQLITE_TABLEID).unwrap(); - let sqlite_schema_cursor_id = program.alloc_cursor_id( - Some(SQLITE_TABLEID.to_owned()), - CursorType::BTreeTable(sqlite_table.clone()), - ); + let sqlite_schema_cursor_id = + program.alloc_cursor_id(CursorType::BTreeTable(sqlite_table.clone())); // Open root=1 iDb=0; sqlite_schema for writing program.emit_insn(Insn::OpenWrite { diff --git a/core/translate/insert.rs b/core/translate/insert.rs index 60a386651..d664aca6c 100644 --- a/core/translate/insert.rs +++ b/core/translate/insert.rs @@ -121,10 +121,7 @@ pub fn translate_insert( { ( values.as_ref().unwrap().len(), - program.alloc_cursor_id( - Some(table_name.0.clone()), - CursorType::BTreeTable(btree_table.clone()), - ), + program.alloc_cursor_id(CursorType::BTreeTable(btree_table.clone())), ) } else { // Multiple rows - use coroutine for value population @@ -158,12 +155,8 @@ pub fn translate_insert( program.emit_insn(Insn::EndCoroutine { yield_reg }); program.preassign_label_to_next_insn(jump_on_definition_label); - // Have to allocate the cursor here to avoid having `init_loop` inside `translate_select` selecting the incorrect - // cursor_id - let cursor_id = program.alloc_cursor_id( - Some(table_name.0.clone()), - CursorType::BTreeTable(btree_table.clone()), - ); + let cursor_id = + program.alloc_cursor_id(CursorType::BTreeTable(btree_table.clone())); // From SQLite /* Set useTempTable to TRUE if the result of the SELECT statement @@ -175,11 +168,9 @@ pub fn translate_insert( ** of the tables being read by the SELECT statement. Also use a ** temp table in the case of row triggers. */ - if program.is_table_open(&table, schema) { - let temp_cursor_id = program.alloc_cursor_id( - Some("temp table".to_string()), - CursorType::BTreeTable(btree_table.clone()), - ); + if program.is_table_open(&table) { + let temp_cursor_id = + program.alloc_cursor_id(CursorType::BTreeTable(btree_table.clone())); temp_table_ctx = Some(TempTableCtx { cursor_id: temp_cursor_id, loop_start_label: program.allocate_label(), @@ -260,10 +251,7 @@ pub fn translate_insert( } InsertBody::DefaultValues => ( 0, - program.alloc_cursor_id( - Some(table_name.0.clone()), - CursorType::BTreeTable(btree_table.clone()), - ), + program.alloc_cursor_id(CursorType::BTreeTable(btree_table.clone())), ), }; @@ -276,10 +264,7 @@ pub fn translate_insert( ( &idx.name, idx.root_page, - program.alloc_cursor_id( - Some(table_name.0.clone()), - CursorType::BTreeIndex(idx.clone()), - ), + program.alloc_cursor_id(CursorType::BTreeIndex(idx.clone())), ) }) .collect::>(); @@ -882,10 +867,7 @@ fn translate_virtual_table_insert( )?; let conflict_action = on_conflict.as_ref().map(|c| c.bit_value()).unwrap_or(0) as u16; - let cursor_id = program.alloc_cursor_id( - Some(virtual_table.name.clone()), - CursorType::VirtualTable(virtual_table.clone()), - ); + let cursor_id = program.alloc_cursor_id(CursorType::VirtualTable(virtual_table.clone())); program.emit_insn(Insn::VUpdate { cursor_id, diff --git a/core/translate/main_loop.rs b/core/translate/main_loop.rs index 7ae7abd62..1e695730e 100644 --- a/core/translate/main_loop.rs +++ b/core/translate/main_loop.rs @@ -92,10 +92,7 @@ pub fn init_distinct(program: &mut ProgramBuilder, plan: &mut SelectPlan) { unique: false, has_rowid: false, }); - let cursor_id = program.alloc_cursor_id( - Some(index_name.clone()), - CursorType::BTreeIndex(index.clone()), - ); + let cursor_id = program.alloc_cursor_id(CursorType::BTreeIndex(index.clone())); *ctx = Some(DistinctCtx { cursor_id, ephemeral_index_name: index_name, @@ -147,10 +144,7 @@ pub fn init_loop( has_rowid: false, unique: false, }); - let cursor_id = program.alloc_cursor_id( - Some(index_name.clone()), - CursorType::BTreeIndex(index.clone()), - ); + let cursor_id = program.alloc_cursor_id(CursorType::BTreeIndex(index.clone())); if group_by.is_none() { // In GROUP BY, the ephemeral index is reinitialized for every group // in the clear accumulator subroutine, so we only do it here if there is no GROUP BY. diff --git a/core/translate/order_by.rs b/core/translate/order_by.rs index 349bd6578..67ab13422 100644 --- a/core/translate/order_by.rs +++ b/core/translate/order_by.rs @@ -36,7 +36,7 @@ pub fn init_order_by( order_by: &[(ast::Expr, SortOrder)], referenced_tables: &[TableReference], ) -> Result<()> { - let sort_cursor = program.alloc_cursor_id(None, CursorType::Sorter); + let sort_cursor = program.alloc_cursor_id(CursorType::Sorter); t_ctx.meta_sort = Some(SortMetadata { sort_cursor, reg_sorter_data: program.alloc_register(), @@ -137,7 +137,7 @@ pub fn emit_order_by( let pseudo_table = Rc::new(PseudoTable { columns: pseudo_columns, }); - let pseudo_cursor = program.alloc_cursor_id(None, CursorType::Pseudo(pseudo_table.clone())); + let pseudo_cursor = program.alloc_cursor_id(CursorType::Pseudo(pseudo_table.clone())); let SortMetadata { sort_cursor, reg_sorter_data, diff --git a/core/translate/plan.rs b/core/translate/plan.rs index 08946086a..d47ad740e 100644 --- a/core/translate/plan.rs +++ b/core/translate/plan.rs @@ -14,7 +14,7 @@ use crate::{ schema::{BTreeTable, Column, FromClauseSubquery, Index, Table}, util::exprs_are_equivalent, vdbe::{ - builder::{CursorType, ProgramBuilder}, + builder::{CursorKey, CursorType, ProgramBuilder}, insn::{IdxInsertFlags, Insn}, BranchOffset, CursorID, }, @@ -802,14 +802,14 @@ impl TableReference { let table_cursor_id = if table_not_required { None } else { - Some(program.alloc_cursor_id( - Some(self.identifier.clone()), + Some(program.alloc_cursor_id_keyed( + CursorKey::table(self.internal_id), CursorType::BTreeTable(btree.clone()), )) }; let index_cursor_id = if let Some(index) = index { - Some(program.alloc_cursor_id( - Some(index.name.clone()), + Some(program.alloc_cursor_id_keyed( + CursorKey::index(self.internal_id, index.clone()), CursorType::BTreeIndex(index.clone()), )) } else { @@ -818,8 +818,8 @@ impl TableReference { Ok((table_cursor_id, index_cursor_id)) } Table::Virtual(virtual_table) => { - let table_cursor_id = Some(program.alloc_cursor_id( - Some(self.identifier.clone()), + let table_cursor_id = Some(program.alloc_cursor_id_keyed( + CursorKey::table(self.internal_id), CursorType::VirtualTable(virtual_table.clone()), )); let index_cursor_id = None; @@ -836,8 +836,10 @@ impl TableReference { program: &mut ProgramBuilder, ) -> Result<(Option, Option)> { let index = self.op.index(); - let table_cursor_id = program.resolve_cursor_id_safe(&self.identifier); - let index_cursor_id = index.map(|index| program.resolve_cursor_id(&index.name)); + let table_cursor_id = program.resolve_cursor_id_safe(&CursorKey::table(self.internal_id)); + let index_cursor_id = index.map(|index| { + program.resolve_cursor_id(&CursorKey::index(self.internal_id, index.clone())) + }); Ok((table_cursor_id, index_cursor_id)) } diff --git a/core/translate/schema.rs b/core/translate/schema.rs index d6693e0da..1b1f6c040 100644 --- a/core/translate/schema.rs +++ b/core/translate/schema.rs @@ -110,10 +110,7 @@ pub fn translate_create_table( } let table = schema.get_btree_table(SQLITE_TABLEID).unwrap(); - let sqlite_schema_cursor_id = program.alloc_cursor_id( - Some(SQLITE_TABLEID.to_owned()), - CursorType::BTreeTable(table.clone()), - ); + let sqlite_schema_cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(table.clone())); program.emit_insn(Insn::OpenWrite { cursor_id: sqlite_schema_cursor_id, root_page: 1usize.into(), @@ -588,10 +585,7 @@ pub fn translate_create_virtual_table( args_reg, }); let table = schema.get_btree_table(SQLITE_TABLEID).unwrap(); - let sqlite_schema_cursor_id = program.alloc_cursor_id( - Some(SQLITE_TABLEID.to_owned()), - CursorType::BTreeTable(table.clone()), - ); + let sqlite_schema_cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(table.clone())); program.emit_insn(Insn::OpenWrite { cursor_id: sqlite_schema_cursor_id, root_page: 1usize.into(), @@ -658,7 +652,6 @@ pub fn translate_drop_table( let schema_table = schema.get_btree_table(SQLITE_TABLEID).unwrap(); let sqlite_schema_cursor_id_0 = program.alloc_cursor_id( // cursor 0 - Some(SQLITE_TABLEID.to_string()), CursorType::BTreeTable(schema_table.clone()), ); program.emit_insn(Insn::OpenWrite { @@ -772,10 +765,8 @@ pub fn translate_drop_table( // 4. Open an ephemeral table, and read over the entry from the schema table whose root page was moved in the destroy operation // cursor id 1 - let sqlite_schema_cursor_id_1 = program.alloc_cursor_id( - Some(SQLITE_TABLEID.to_owned()), - CursorType::BTreeTable(schema_table.clone()), - ); + let sqlite_schema_cursor_id_1 = + program.alloc_cursor_id(CursorType::BTreeTable(schema_table.clone())); let simple_table_rc = Rc::new(BTreeTable { root_page: 0, // Not relevant for ephemeral table definition name: "ephemeral_scratch".to_string(), @@ -796,10 +787,7 @@ pub fn translate_drop_table( unique_sets: None, }); // cursor id 2 - let ephemeral_cursor_id = program.alloc_cursor_id( - Some("scratch_table".to_string()), - CursorType::BTreeTable(simple_table_rc), - ); + let ephemeral_cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(simple_table_rc)); program.emit_insn(Insn::OpenEphemeral { cursor_id: ephemeral_cursor_id, is_table: true, diff --git a/core/vdbe/builder.rs b/core/vdbe/builder.rs index 3c75794d1..aa3f9a315 100644 --- a/core/vdbe/builder.rs +++ b/core/vdbe/builder.rs @@ -10,7 +10,7 @@ use limbo_sqlite3_parser::ast::{self, TableInternalId}; use crate::{ fast_lock::SpinLock, parameters::Parameters, - schema::{BTreeTable, Index, PseudoTable, Schema, Table}, + schema::{BTreeTable, Index, PseudoTable, Table}, storage::sqlite3_ondisk::DatabaseHeader, translate::{ collate::CollationSeq, @@ -37,10 +37,51 @@ impl TableRefIdCounter { } } -use super::{ - insn::RegisterOrLiteral, BranchOffset, CursorID, Insn, InsnFunction, InsnReference, JumpTarget, - Program, -}; +use super::{BranchOffset, CursorID, Insn, InsnFunction, InsnReference, JumpTarget, Program}; + +/// A key that uniquely identifies a cursor. +/// The key is a pair of table reference id and index. +/// The index is only provided when the cursor is an index cursor. +#[derive(Debug, Clone)] +pub struct CursorKey { + /// The table reference that the cursor is associated with. + /// We cannot use e.g. the table query identifier (e.g. 'users' or 'u') + /// because it might be ambiguous, e.g. this silly example: + /// `SELECT * FROM t WHERE EXISTS (SELECT * from t)` <-- two different cursors, which 't' should we use as key? + /// TableInternalIds are unique within a program, since there is one id per table reference. + pub table_reference_id: TableInternalId, + /// The index, in case of an index cursor. + /// The combination of table internal id and index is enough to disambiguate. + pub index: Option>, +} + +impl CursorKey { + pub fn table(table_reference_id: TableInternalId) -> Self { + Self { + table_reference_id, + index: None, + } + } + + pub fn index(table_reference_id: TableInternalId, index: Arc) -> Self { + Self { + table_reference_id, + index: Some(index), + } + } + + pub fn equals(&self, other: &CursorKey) -> bool { + if self.table_reference_id != other.table_reference_id { + return false; + } + match (self.index.as_ref(), other.index.as_ref()) { + (Some(self_index), Some(other_index)) => self_index.name == other_index.name, + (None, None) => true, + _ => false, + } + } +} + #[allow(dead_code)] pub struct ProgramBuilder { pub table_reference_counter: TableRefIdCounter, @@ -52,8 +93,11 @@ pub struct ProgramBuilder { /// that are deemed to be compile-time constant and can be hoisted out of loops /// so that they get evaluated only once at the start of the program. pub constant_spans: Vec<(usize, usize)>, - // Cursors that are referenced by the program. Indexed by CursorID. - pub cursor_ref: Vec<(Option, CursorType)>, + /// Cursors that are referenced by the program. Indexed by [CursorKey]. + /// Certain types of cursors do not need a [CursorKey] (e.g. temp tables, sorter), + /// because they never need to use [ProgramBuilder::resolve_cursor_id] to find it + /// again. Hence, the key is optional. + pub cursor_ref: Vec<(Option, CursorType)>, /// A vector where index=label number, value=resolved offset. Resolved in build(). label_to_resolved_offset: Vec>, // Bitmask of cursors that have emitted a SeekRowid instruction. @@ -204,14 +248,25 @@ impl ProgramBuilder { reg } - pub fn alloc_cursor_id( - &mut self, - table_identifier: Option, - cursor_type: CursorType, - ) -> usize { + pub fn alloc_cursor_id_keyed(&mut self, key: CursorKey, cursor_type: CursorType) -> usize { + assert!( + !self + .cursor_ref + .iter() + .any(|(k, _)| k.as_ref().map_or(false, |k| k.equals(&key))), + "duplicate cursor key" + ); + self._alloc_cursor_id(Some(key), cursor_type) + } + + pub fn alloc_cursor_id(&mut self, cursor_type: CursorType) -> usize { + self._alloc_cursor_id(None, cursor_type) + } + + fn _alloc_cursor_id(&mut self, key: Option, cursor_type: CursorType) -> usize { let cursor = self.next_free_cursor_id; self.next_free_cursor_id += 1; - self.cursor_ref.push((table_identifier, cursor_type)); + self.cursor_ref.push((key, cursor_type)); assert_eq!(self.cursor_ref.len(), self.next_free_cursor_id); cursor } @@ -611,18 +666,16 @@ impl ProgramBuilder { self.label_to_resolved_offset.clear(); } - // translate table to cursor id - pub fn resolve_cursor_id_safe(&self, table_identifier: &str) -> Option { - self.cursor_ref.iter().position(|(t_ident, _)| { - t_ident - .as_ref() - .is_some_and(|ident| ident == table_identifier) - }) + // translate [CursorKey] to cursor id + pub fn resolve_cursor_id_safe(&self, key: &CursorKey) -> Option { + self.cursor_ref + .iter() + .position(|(k, _)| k.as_ref().map_or(false, |k| k.equals(key))) } - pub fn resolve_cursor_id(&self, table_identifier: &str) -> CursorID { - self.resolve_cursor_id_safe(table_identifier) - .unwrap_or_else(|| panic!("Cursor not found: {}", table_identifier)) + pub fn resolve_cursor_id(&self, key: &CursorKey) -> CursorID { + self.resolve_cursor_id_safe(key) + .unwrap_or_else(|| panic!("Cursor not found: {:?}", key)) } pub fn set_collation(&mut self, c: Option<(CollationSeq, bool)>) { @@ -686,63 +739,8 @@ impl ProgramBuilder { } /// Checks whether `table` or any of its indices has been opened in the program - pub fn is_table_open(&self, table: &Table, schema: &Schema) -> bool { - let btree = table.btree(); - let vtab = table.virtual_table(); - for (insn, ..) in self.insns.iter() { - match insn { - Insn::OpenRead { - cursor_id, - root_page, - .. - } => { - if let Some(btree) = &btree { - if btree.root_page == *root_page { - return true; - } - } - let name = self.cursor_ref[*cursor_id].0.as_ref(); - if name.is_none() { - continue; - } - let name = name.unwrap(); - let indices = schema.get_indices(name); - for index in indices { - if index.root_page == *root_page { - return true; - } - } - } - Insn::OpenWrite { - root_page, name, .. - } => { - let RegisterOrLiteral::Literal(root_page) = root_page else { - unreachable!("root page can only be a literal"); - }; - if let Some(btree) = &btree { - if btree.root_page == *root_page { - return true; - } - } - let indices = schema.get_indices(name); - for index in indices { - if index.root_page == *root_page { - return true; - } - } - } - Insn::VOpen { cursor_id, .. } => { - if let Some(vtab) = &vtab { - let name = self.cursor_ref[*cursor_id].0.as_ref().unwrap(); - if vtab.name == *name { - return true; - } - } - } - _ => {} - } - } - false + pub fn is_table_open(&self, table: &Table) -> bool { + self.table_references.iter().any(|t| t.table == *table) } pub fn build( diff --git a/core/vdbe/explain.rs b/core/vdbe/explain.rs index e2e9bf979..35bdac8f7 100644 --- a/core/vdbe/explain.rs +++ b/core/vdbe/explain.rs @@ -12,6 +12,16 @@ pub fn insn_to_str( indent: String, manual_comment: Option<&'static str>, ) -> String { + let get_table_or_index_name = |cursor_id: usize| { + let cursor_type = &program.cursor_ref[cursor_id].1; + match cursor_type { + CursorType::BTreeTable(table) => &table.name, + CursorType::BTreeIndex(index) => &index.name, + CursorType::Pseudo(_) => "pseudo", + CursorType::VirtualTable(virtual_table) => &virtual_table.name, + CursorType::Sorter => "sorter", + } + }; let (opcode, p1, p2, p3, p4, p5, comment): (&str, i32, i32, i32, Value, u16, String) = match insn { Insn::Init { target_pc } => ( @@ -354,14 +364,19 @@ pub fn insn_to_str( 0, Value::build_text(""), 0, - format!( - "table={}, root={}", - program.cursor_ref[*cursor_id] - .0 - .as_ref() - .unwrap_or(&format!("cursor {}", cursor_id)), - root_page - ), + { + let cursor_key = program.cursor_ref[*cursor_id].0.as_ref().unwrap(); + format!( + "{}={}, root={}", + if cursor_key.index.is_some() { + "index" + } else { + "table" + }, + get_table_or_index_name(*cursor_id), + root_page + ) + }, ), Insn::VOpen { cursor_id } => ( "VOpen", @@ -370,11 +385,18 @@ pub fn insn_to_str( 0, Value::build_text(""), 0, - program.cursor_ref[*cursor_id] - .0 - .as_ref() - .unwrap() - .to_string(), + { + let cursor_key = program.cursor_ref[*cursor_id].0.as_ref().unwrap(); + format!( + "{} {}", + if cursor_key.index.is_some() { + "index" + } else { + "table" + }, + get_table_or_index_name(*cursor_id), + ) + }, ), Insn::VCreate { table_name, @@ -474,20 +496,25 @@ pub fn insn_to_str( 0, Value::build_text(""), 0, - format!( - "Rewind {}", - program.cursor_ref[*cursor_id] - .0 - .as_ref() - .unwrap_or(&format!("cursor {}", cursor_id)) - ), + { + let cursor_key = program.cursor_ref[*cursor_id].0.as_ref().unwrap(); + format!( + "Rewind {} {}", + if cursor_key.index.is_some() { + "index" + } else { + "table" + }, + get_table_or_index_name(*cursor_id), + ) + }, ), Insn::Column { cursor_id, column, dest, } => { - let (table_identifier, cursor_type) = &program.cursor_ref[*cursor_id]; + let cursor_type = &program.cursor_ref[*cursor_id].1; let column_name: Option<&String> = match cursor_type { CursorType::BTreeTable(table) => { let name = table.columns.get(*column).unwrap().name.as_ref(); @@ -514,9 +541,7 @@ pub fn insn_to_str( format!( "r[{}]={}.{}", dest, - table_identifier - .as_ref() - .unwrap_or(&format!("cursor {}", cursor_id)), + get_table_or_index_name(*cursor_id), column_name.unwrap_or(&format!("column {}", *column)) ), ) @@ -694,14 +719,7 @@ pub fn insn_to_str( 0, Value::build_text(""), 0, - format!( - "r[{}]={}.rowid", - dest, - &program.cursor_ref[*cursor_id] - .0 - .as_ref() - .unwrap_or(&format!("cursor {}", cursor_id)) - ), + format!("r[{}]={}.rowid", dest, get_table_or_index_name(*cursor_id)), ), Insn::IdxRowId { cursor_id, dest } => ( "IdxRowId", @@ -713,10 +731,16 @@ pub fn insn_to_str( format!( "r[{}]={}.rowid", dest, - &program.cursor_ref[*cursor_id] + program.cursor_ref[*cursor_id] .0 .as_ref() - .unwrap_or(&format!("cursor {}", cursor_id)) + .map(|k| format!( + "cursor {} for {} {}", + cursor_id, + if k.index.is_some() { "index" } else { "table" }, + get_table_or_index_name(*cursor_id), + )) + .unwrap_or(format!("cursor {}", cursor_id)) ), ), Insn::SeekRowid { @@ -736,7 +760,13 @@ pub fn insn_to_str( &program.cursor_ref[*cursor_id] .0 .as_ref() - .unwrap_or(&format!("cursor {}", cursor_id)), + .map(|k| format!( + "cursor {} for {} {}", + cursor_id, + if k.index.is_some() { "index" } else { "table" }, + get_table_or_index_name(*cursor_id), + )) + .unwrap_or(format!("cursor {}", cursor_id)), target_pc.to_debug_int() ), ), diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index e5d0cd237..d62413c58 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -41,6 +41,7 @@ use crate::{ #[cfg(feature = "json")] use crate::json::JsonCacheCell; use crate::{Connection, MvStore, Result, TransactionState}; +use builder::CursorKey; use execute::{InsnFunction, InsnFunctionStepResult, OpIdxDeleteState}; use rand::{ @@ -384,7 +385,7 @@ macro_rules! must_be_btree_cursor { pub struct Program { pub max_registers: usize, pub insns: Vec<(Insn, InsnFunction)>, - pub cursor_ref: Vec<(Option, CursorType)>, + pub cursor_ref: Vec<(Option, CursorType)>, pub database_header: Arc>, pub comments: Option>, pub parameters: crate::parameters::Parameters, diff --git a/vendored/sqlite3-parser/src/parser/ast/mod.rs b/vendored/sqlite3-parser/src/parser/ast/mod.rs index 4bf2fbf9c..390bc9609 100644 --- a/vendored/sqlite3-parser/src/parser/ast/mod.rs +++ b/vendored/sqlite3-parser/src/parser/ast/mod.rs @@ -290,9 +290,13 @@ pub struct Delete { #[repr(transparent)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] -/// Internal ID of a table. +/// Internal ID of a table reference. /// /// Used by [Expr::Column] and [Expr::RowId] to refer to a table. +/// E.g. in 'SELECT * FROM t UNION ALL SELECT * FROM t', there are two table references, +/// so there are two TableInternalIds. +/// +/// FIXME: rename this to TableReferenceId. pub struct TableInternalId(usize); impl Default for TableInternalId {