mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-09 10:14:21 +01:00
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:
@@ -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,
|
||||
),
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user