Merge 'Fix ProgramBuilder::cursor_ref not having unique keys' from Jussi Saurio

Currently we have this:
`program.alloc_cursor_id(Option<String>, CursorType)`
where the `String` is the table's name or alias ('users' or 'u' in the
query).
This is problematic because this can happen:
`SELECT * FROM t WHERE EXISTS (SELECT * FROM t)`
There are two cursors, both with identifier 't'. This causes a bug where
the program will use the same cursor for both the main query and the
subquery, since they are keyed by 't'.
Instead introduce `CursorKey`, which is a combination of:
1. `TableInternalId`, and
2. index name (`Option<Arc<Index>>` -- in case of index cursors.)
This should provide key uniqueness for cursors:
`SELECT * FROM t WHERE EXISTS (SELECT * FROM t)`
here the first 't' will have a different `TableInternalId` than the
second `t`, so there is no clash.
---
These `CursorKey`s are only required when the program needs to retrieve
the cursor ID later:
`program.resolve_cursor_id(key)`
So, there are now two methods for allocating cursors:
`program.alloc_cursor_id_keyed(key, cursor_type); // needs to be
retrieved later with same key`
`program.alloc_cursor_id(cursor_type); // does not need to be retrieved
later`

Reviewed-by: Preston Thorpe (@PThorpe92)

Closes #1604
This commit is contained in:
Jussi Saurio
2025-05-29 10:53:24 +03:00
13 changed files with 246 additions and 236 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -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::<Vec<(&String, usize, usize)>>();
@@ -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,

View File

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

View File

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

View File

@@ -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<CursorID>, Option<CursorID>)> {
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))
}

View File

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

View File

@@ -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<Arc<Index>>,
}
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<Index>) -> 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<String>, 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<CursorKey>, CursorType)>,
/// A vector where index=label number, value=resolved offset. Resolved in build().
label_to_resolved_offset: Vec<Option<(InsnReference, JumpTarget)>>,
// 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<String>,
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<CursorKey>, 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<CursorID> {
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<CursorID> {
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(

View File

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

View File

@@ -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<String>, CursorType)>,
pub cursor_ref: Vec<(Option<CursorKey>, CursorType)>,
pub database_header: Arc<SpinLock<DatabaseHeader>>,
pub comments: Option<Vec<(InsnReference, &'static str)>>,
pub parameters: crate::parameters::Parameters,

View File

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