mirror of
https://github.com/tursodatabase/limbo.git
synced 2025-08-04 18:18:03 +00:00
Merge 'Ephemeral Table in Update' from Pedro Muniz
Closes #1713. Adds ephemeral table when a rowid_alias is being updated. Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com> Closes #1726
This commit is contained in:
commit
a549f2971d
14 changed files with 396 additions and 153 deletions
|
@ -6610,7 +6610,8 @@ mod tests {
|
|||
let page = page.get();
|
||||
let page = page.get_contents();
|
||||
let header_size = 8;
|
||||
let record = ImmutableRecord::from_registers(&[Register::Value(Value::Integer(1))]);
|
||||
let regs = &[Register::Value(Value::Integer(1))];
|
||||
let record = ImmutableRecord::from_registers(regs, regs.len());
|
||||
let payload = add_record(1, 0, page, record, &conn);
|
||||
assert_eq!(page.cell_count(), 1);
|
||||
let free = compute_free_space(page, 4096);
|
||||
|
@ -6639,8 +6640,8 @@ mod tests {
|
|||
let mut cells = Vec::new();
|
||||
let usable_space = 4096;
|
||||
for i in 0..3 {
|
||||
let record =
|
||||
ImmutableRecord::from_registers(&[Register::Value(Value::Integer(i as i64))]);
|
||||
let regs = &[Register::Value(Value::Integer(i as i64))];
|
||||
let record = ImmutableRecord::from_registers(regs, regs.len());
|
||||
let payload = add_record(i, i, page, record, &conn);
|
||||
assert_eq!(page.cell_count(), i + 1);
|
||||
let free = compute_free_space(page, usable_space);
|
||||
|
@ -6928,10 +6929,8 @@ mod tests {
|
|||
pager.deref(),
|
||||
)
|
||||
.unwrap();
|
||||
let value = ImmutableRecord::from_registers(&[Register::Value(Value::Blob(vec![
|
||||
0;
|
||||
*size
|
||||
]))]);
|
||||
let regs = &[Register::Value(Value::Blob(vec![0; *size]))];
|
||||
let value = ImmutableRecord::from_registers(regs, regs.len());
|
||||
tracing::info!("insert key:{}", key);
|
||||
run_until_done(
|
||||
|| cursor.insert(&BTreeKey::new_table_rowid(*key, Some(&value)), true),
|
||||
|
@ -7022,8 +7021,8 @@ mod tests {
|
|||
pager.deref(),
|
||||
)
|
||||
.unwrap();
|
||||
let value =
|
||||
ImmutableRecord::from_registers(&[Register::Value(Value::Blob(vec![0; size]))]);
|
||||
let regs = &[Register::Value(Value::Blob(vec![0; size]))];
|
||||
let value = ImmutableRecord::from_registers(regs, regs.len());
|
||||
let btree_before = if do_validate {
|
||||
format_btree(pager.clone(), root_page, 0)
|
||||
} else {
|
||||
|
@ -7143,11 +7142,11 @@ mod tests {
|
|||
};
|
||||
tracing::info!("insert {}/{}: {:?}", i + 1, inserts, key);
|
||||
keys.push(key.clone());
|
||||
let value = ImmutableRecord::from_registers(
|
||||
&key.iter()
|
||||
.map(|col| Register::Value(Value::Integer(*col)))
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
let regs = key
|
||||
.iter()
|
||||
.map(|col| Register::Value(Value::Integer(*col)))
|
||||
.collect::<Vec<_>>();
|
||||
let value = ImmutableRecord::from_registers(®s, regs.len());
|
||||
run_until_done(
|
||||
|| {
|
||||
cursor.insert(
|
||||
|
@ -7176,12 +7175,12 @@ mod tests {
|
|||
tracing::info!("seeking key {}/{}: {:?}", i + 1, keys.len(), key);
|
||||
let exists = run_until_done(
|
||||
|| {
|
||||
let regs = key
|
||||
.iter()
|
||||
.map(|col| Register::Value(Value::Integer(*col)))
|
||||
.collect::<Vec<_>>();
|
||||
cursor.seek(
|
||||
SeekKey::IndexKey(&ImmutableRecord::from_registers(
|
||||
&key.iter()
|
||||
.map(|col| Register::Value(Value::Integer(*col)))
|
||||
.collect::<Vec<_>>(),
|
||||
)),
|
||||
SeekKey::IndexKey(&ImmutableRecord::from_registers(®s, regs.len())),
|
||||
SeekOp::GE { eq_only: true },
|
||||
)
|
||||
},
|
||||
|
@ -7244,8 +7243,8 @@ mod tests {
|
|||
let usable_space = 4096;
|
||||
let total_cells = 10;
|
||||
for i in 0..total_cells {
|
||||
let record =
|
||||
ImmutableRecord::from_registers(&[Register::Value(Value::Integer(i as i64))]);
|
||||
let regs = &[Register::Value(Value::Integer(i as i64))];
|
||||
let record = ImmutableRecord::from_registers(regs, regs.len());
|
||||
let payload = add_record(i, i, page, record, &conn);
|
||||
assert_eq!(page.cell_count(), i + 1);
|
||||
let free = compute_free_space(page, usable_space);
|
||||
|
@ -7650,8 +7649,8 @@ mod tests {
|
|||
let mut cells = Vec::new();
|
||||
let usable_space = 4096;
|
||||
for i in 0..3 {
|
||||
let record =
|
||||
ImmutableRecord::from_registers(&[Register::Value(Value::Integer(i as i64))]);
|
||||
let regs = &[Register::Value(Value::Integer(i as i64))];
|
||||
let record = ImmutableRecord::from_registers(regs, regs.len());
|
||||
let payload = add_record(i, i, page, record, &conn);
|
||||
assert_eq!(page.cell_count(), i + 1);
|
||||
let free = compute_free_space(page, usable_space);
|
||||
|
@ -7692,8 +7691,8 @@ mod tests {
|
|||
let usable_space = 4096;
|
||||
let total_cells = 10;
|
||||
for i in 0..total_cells {
|
||||
let record =
|
||||
ImmutableRecord::from_registers(&[Register::Value(Value::Integer(i as i64))]);
|
||||
let regs = &[Register::Value(Value::Integer(i as i64))];
|
||||
let record = ImmutableRecord::from_registers(regs, regs.len());
|
||||
let payload = add_record(i, i, page, record, &conn);
|
||||
assert_eq!(page.cell_count(), i + 1);
|
||||
let free = compute_free_space(page, usable_space);
|
||||
|
@ -7748,9 +7747,8 @@ mod tests {
|
|||
// allow appends with extra place to insert
|
||||
let cell_idx = rng.next_u64() as usize % (page.cell_count() + 1);
|
||||
let free = compute_free_space(page, usable_space);
|
||||
let record = ImmutableRecord::from_registers(&[Register::Value(
|
||||
Value::Integer(i as i64),
|
||||
)]);
|
||||
let regs = &[Register::Value(Value::Integer(i as i64))];
|
||||
let record = ImmutableRecord::from_registers(regs, regs.len());
|
||||
let mut payload: Vec<u8> = Vec::new();
|
||||
fill_cell_payload(
|
||||
page.page_type(),
|
||||
|
@ -7827,9 +7825,8 @@ mod tests {
|
|||
// allow appends with extra place to insert
|
||||
let cell_idx = rng.next_u64() as usize % (page.cell_count() + 1);
|
||||
let free = compute_free_space(page, usable_space);
|
||||
let record = ImmutableRecord::from_registers(&[Register::Value(
|
||||
Value::Integer(i as i64),
|
||||
)]);
|
||||
let regs = &[Register::Value(Value::Integer(i as i64))];
|
||||
let record = ImmutableRecord::from_registers(regs, regs.len());
|
||||
let mut payload: Vec<u8> = Vec::new();
|
||||
fill_cell_payload(
|
||||
page.page_type(),
|
||||
|
@ -7987,7 +7984,8 @@ mod tests {
|
|||
let header_size = 8;
|
||||
let usable_space = 4096;
|
||||
|
||||
let record = ImmutableRecord::from_registers(&[Register::Value(Value::Integer(0))]);
|
||||
let regs = &[Register::Value(Value::Integer(0))];
|
||||
let record = ImmutableRecord::from_registers(regs, regs.len());
|
||||
let payload = add_record(0, 0, page, record, &conn);
|
||||
let free = compute_free_space(page, usable_space);
|
||||
assert_eq!(free, 4096 - payload.len() as u16 - 2 - header_size);
|
||||
|
@ -8003,7 +8001,8 @@ mod tests {
|
|||
let page = page.get_contents();
|
||||
let usable_space = 4096;
|
||||
|
||||
let record = ImmutableRecord::from_registers(&[Register::Value(Value::Integer(0))]);
|
||||
let regs = &[Register::Value(Value::Integer(0))];
|
||||
let record = ImmutableRecord::from_registers(regs, regs.len());
|
||||
let payload = add_record(0, 0, page, record, &conn);
|
||||
|
||||
assert_eq!(page.cell_count(), 1);
|
||||
|
@ -8029,17 +8028,19 @@ mod tests {
|
|||
let page = page.get_contents();
|
||||
let usable_space = 4096;
|
||||
|
||||
let record = ImmutableRecord::from_registers(&[
|
||||
let regs = &[
|
||||
Register::Value(Value::Integer(0)),
|
||||
Register::Value(Value::Text(Text::new("aaaaaaaa"))),
|
||||
]);
|
||||
];
|
||||
let record = ImmutableRecord::from_registers(regs, regs.len());
|
||||
let _ = add_record(0, 0, page, record, &conn);
|
||||
|
||||
assert_eq!(page.cell_count(), 1);
|
||||
drop_cell(page, 0, usable_space).unwrap();
|
||||
assert_eq!(page.cell_count(), 0);
|
||||
|
||||
let record = ImmutableRecord::from_registers(&[Register::Value(Value::Integer(0))]);
|
||||
let regs = &[Register::Value(Value::Integer(0))];
|
||||
let record = ImmutableRecord::from_registers(regs, regs.len());
|
||||
let payload = add_record(0, 0, page, record, &conn);
|
||||
assert_eq!(page.cell_count(), 1);
|
||||
|
||||
|
@ -8063,10 +8064,11 @@ mod tests {
|
|||
let page = page.get_contents();
|
||||
let usable_space = 4096;
|
||||
|
||||
let record = ImmutableRecord::from_registers(&[
|
||||
let regs = &[
|
||||
Register::Value(Value::Integer(0)),
|
||||
Register::Value(Value::Text(Text::new("aaaaaaaa"))),
|
||||
]);
|
||||
];
|
||||
let record = ImmutableRecord::from_registers(regs, regs.len());
|
||||
let _ = add_record(0, 0, page, record, &conn);
|
||||
|
||||
for _ in 0..100 {
|
||||
|
@ -8074,7 +8076,8 @@ mod tests {
|
|||
drop_cell(page, 0, usable_space).unwrap();
|
||||
assert_eq!(page.cell_count(), 0);
|
||||
|
||||
let record = ImmutableRecord::from_registers(&[Register::Value(Value::Integer(0))]);
|
||||
let regs = &[Register::Value(Value::Integer(0))];
|
||||
let record = ImmutableRecord::from_registers(regs, regs.len());
|
||||
let payload = add_record(0, 0, page, record, &conn);
|
||||
assert_eq!(page.cell_count(), 1);
|
||||
|
||||
|
@ -8099,11 +8102,14 @@ mod tests {
|
|||
let page = page.get_contents();
|
||||
let usable_space = 4096;
|
||||
|
||||
let record = ImmutableRecord::from_registers(&[Register::Value(Value::Integer(0))]);
|
||||
let regs = &[Register::Value(Value::Integer(0))];
|
||||
let record = ImmutableRecord::from_registers(regs, regs.len());
|
||||
let payload = add_record(0, 0, page, record, &conn);
|
||||
let record = ImmutableRecord::from_registers(&[Register::Value(Value::Integer(1))]);
|
||||
let regs = &[Register::Value(Value::Integer(1))];
|
||||
let record = ImmutableRecord::from_registers(regs, regs.len());
|
||||
let _ = add_record(1, 1, page, record, &conn);
|
||||
let record = ImmutableRecord::from_registers(&[Register::Value(Value::Integer(2))]);
|
||||
let regs = &[Register::Value(Value::Integer(2))];
|
||||
let record = ImmutableRecord::from_registers(regs, regs.len());
|
||||
let _ = add_record(2, 2, page, record, &conn);
|
||||
|
||||
drop_cell(page, 1, usable_space).unwrap();
|
||||
|
@ -8122,21 +8128,25 @@ mod tests {
|
|||
let page = page.get_contents();
|
||||
let usable_space = 4096;
|
||||
|
||||
let record = ImmutableRecord::from_registers(&[Register::Value(Value::Integer(0))]);
|
||||
let regs = &[Register::Value(Value::Integer(0))];
|
||||
let record = ImmutableRecord::from_registers(regs, regs.len());
|
||||
let _ = add_record(0, 0, page, record, &conn);
|
||||
|
||||
let record = ImmutableRecord::from_registers(&[Register::Value(Value::Integer(0))]);
|
||||
let regs = &[Register::Value(Value::Integer(0))];
|
||||
let record = ImmutableRecord::from_registers(regs, regs.len());
|
||||
let _ = add_record(0, 0, page, record, &conn);
|
||||
drop_cell(page, 0, usable_space).unwrap();
|
||||
|
||||
defragment_page(page, usable_space);
|
||||
|
||||
let record = ImmutableRecord::from_registers(&[Register::Value(Value::Integer(0))]);
|
||||
let regs = &[Register::Value(Value::Integer(0))];
|
||||
let record = ImmutableRecord::from_registers(regs, regs.len());
|
||||
let _ = add_record(0, 1, page, record, &conn);
|
||||
|
||||
drop_cell(page, 0, usable_space).unwrap();
|
||||
|
||||
let record = ImmutableRecord::from_registers(&[Register::Value(Value::Integer(0))]);
|
||||
let regs = &[Register::Value(Value::Integer(0))];
|
||||
let record = ImmutableRecord::from_registers(regs, regs.len());
|
||||
let _ = add_record(0, 1, page, record, &conn);
|
||||
}
|
||||
|
||||
|
@ -8148,7 +8158,8 @@ mod tests {
|
|||
let page = get_page(2);
|
||||
let usable_space = 4096;
|
||||
let insert = |pos, page| {
|
||||
let record = ImmutableRecord::from_registers(&[Register::Value(Value::Integer(0))]);
|
||||
let regs = &[Register::Value(Value::Integer(0))];
|
||||
let record = ImmutableRecord::from_registers(regs, regs.len());
|
||||
let _ = add_record(0, pos, page, record, &conn);
|
||||
};
|
||||
let drop = |pos, page| {
|
||||
|
@ -8188,7 +8199,8 @@ mod tests {
|
|||
let page = get_page(2);
|
||||
let usable_space = 4096;
|
||||
let insert = |pos, page| {
|
||||
let record = ImmutableRecord::from_registers(&[Register::Value(Value::Integer(0))]);
|
||||
let regs = &[Register::Value(Value::Integer(0))];
|
||||
let record = ImmutableRecord::from_registers(regs, regs.len());
|
||||
let _ = add_record(0, pos, page, record, &conn);
|
||||
};
|
||||
let drop = |pos, page| {
|
||||
|
@ -8197,7 +8209,8 @@ mod tests {
|
|||
let defragment = |page| {
|
||||
defragment_page(page, usable_space);
|
||||
};
|
||||
let record = ImmutableRecord::from_registers(&[Register::Value(Value::Integer(0))]);
|
||||
let regs = &[Register::Value(Value::Integer(0))];
|
||||
let record = ImmutableRecord::from_registers(regs, regs.len());
|
||||
let mut payload: Vec<u8> = Vec::new();
|
||||
fill_cell_payload(
|
||||
page.get().get_contents().page_type(),
|
||||
|
@ -8231,7 +8244,8 @@ mod tests {
|
|||
for i in 0..10000 {
|
||||
let mut cursor = BTreeCursor::new_table(None, pager.clone(), root_page);
|
||||
tracing::info!("INSERT INTO t VALUES ({});", i,);
|
||||
let value = ImmutableRecord::from_registers(&[Register::Value(Value::Integer(i))]);
|
||||
let regs = &[Register::Value(Value::Integer(i))];
|
||||
let value = ImmutableRecord::from_registers(regs, regs.len());
|
||||
tracing::trace!("before insert {}", i);
|
||||
run_until_done(
|
||||
|| {
|
||||
|
@ -8270,8 +8284,8 @@ mod tests {
|
|||
|
||||
let page = get_page(2);
|
||||
let usable_space = 4096;
|
||||
let record =
|
||||
ImmutableRecord::from_registers(&[Register::Value(Value::Blob(vec![0; 3600]))]);
|
||||
let regs = &[Register::Value(Value::Blob(vec![0; 3600]))];
|
||||
let record = ImmutableRecord::from_registers(regs, regs.len());
|
||||
let mut payload: Vec<u8> = Vec::new();
|
||||
fill_cell_payload(
|
||||
page.get().get_contents().page_type(),
|
||||
|
@ -8306,9 +8320,8 @@ mod tests {
|
|||
// Insert 10,000 records in to the BTree.
|
||||
for i in 1..=10000 {
|
||||
let mut cursor = BTreeCursor::new_table(None, pager.clone(), root_page);
|
||||
let value = ImmutableRecord::from_registers(&[Register::Value(Value::Text(
|
||||
Text::new("hello world"),
|
||||
))]);
|
||||
let regs = &[Register::Value(Value::Text(Text::new("hello world")))];
|
||||
let value = ImmutableRecord::from_registers(regs, regs.len());
|
||||
|
||||
run_until_done(
|
||||
|| {
|
||||
|
@ -8385,10 +8398,11 @@ mod tests {
|
|||
for i in 0..iterations {
|
||||
let mut cursor = BTreeCursor::new_table(None, pager.clone(), root_page);
|
||||
tracing::info!("INSERT INTO t VALUES ({});", i,);
|
||||
let value = ImmutableRecord::from_registers(&[Register::Value(Value::Text(Text {
|
||||
let regs = &[Register::Value(Value::Text(Text {
|
||||
value: huge_texts[i].as_bytes().to_vec(),
|
||||
subtype: crate::types::TextSubtype::Text,
|
||||
}))]);
|
||||
}))];
|
||||
let value = ImmutableRecord::from_registers(regs, regs.len());
|
||||
tracing::trace!("before insert {}", i);
|
||||
tracing::debug!(
|
||||
"=========== btree before ===========\n{}\n\n",
|
||||
|
@ -8433,8 +8447,8 @@ mod tests {
|
|||
let offset = 2; // blobs data starts at offset 2
|
||||
let initial_text = "hello world";
|
||||
let initial_blob = initial_text.as_bytes().to_vec();
|
||||
let value =
|
||||
ImmutableRecord::from_registers(&[Register::Value(Value::Blob(initial_blob.clone()))]);
|
||||
let regs = &[Register::Value(Value::Blob(initial_blob.clone()))];
|
||||
let value = ImmutableRecord::from_registers(regs, regs.len());
|
||||
|
||||
run_until_done(
|
||||
|| {
|
||||
|
@ -8509,8 +8523,8 @@ mod tests {
|
|||
let mut large_blob = vec![b'A'; 40960 - 11]; // insert large blob. 40960 = 10 page long.
|
||||
let hello_world = b"hello world";
|
||||
large_blob.extend_from_slice(hello_world);
|
||||
let value =
|
||||
ImmutableRecord::from_registers(&[Register::Value(Value::Blob(large_blob.clone()))]);
|
||||
let regs = &[Register::Value(Value::Blob(large_blob.clone()))];
|
||||
let value = ImmutableRecord::from_registers(regs, regs.len());
|
||||
|
||||
run_until_done(
|
||||
|| {
|
||||
|
@ -8700,10 +8714,8 @@ mod tests {
|
|||
page_type: PageType,
|
||||
) {
|
||||
let mut payload = Vec::new();
|
||||
let record = ImmutableRecord::from_registers(&[Register::Value(Value::Blob(vec![
|
||||
0;
|
||||
size as usize
|
||||
]))]);
|
||||
let regs = &[Register::Value(Value::Blob(vec![0; size as usize]))];
|
||||
let record = ImmutableRecord::from_registers(regs, regs.len());
|
||||
fill_cell_payload(
|
||||
page_type,
|
||||
Some(i as i64),
|
||||
|
|
|
@ -24,11 +24,12 @@ use crate::error::SQLITE_CONSTRAINT_PRIMARYKEY;
|
|||
use crate::function::Func;
|
||||
use crate::schema::Schema;
|
||||
use crate::translate::compound_select::emit_program_for_compound_select;
|
||||
use crate::translate::plan::{DeletePlan, Plan, Search};
|
||||
use crate::translate::plan::{DeletePlan, Plan, QueryDestination, Search};
|
||||
use crate::translate::values::emit_values;
|
||||
use crate::util::exprs_are_equivalent;
|
||||
use crate::vdbe::builder::{CursorKey, CursorType, ProgramBuilder};
|
||||
use crate::vdbe::insn::{CmpInsFlags, IdxInsertFlags, InsertFlags, RegisterOrLiteral};
|
||||
use crate::vdbe::CursorID;
|
||||
use crate::vdbe::{insn::Insn, BranchOffset};
|
||||
use crate::{Result, SymbolTable};
|
||||
|
||||
|
@ -256,9 +257,9 @@ fn emit_program_for_select(
|
|||
|
||||
#[instrument(skip_all, level = Level::TRACE)]
|
||||
pub fn emit_query<'a>(
|
||||
program: &'a mut ProgramBuilder,
|
||||
program: &mut ProgramBuilder,
|
||||
plan: &'a mut SelectPlan,
|
||||
t_ctx: &'a mut TranslateCtx<'a>,
|
||||
t_ctx: &mut TranslateCtx<'a>,
|
||||
) -> Result<usize> {
|
||||
if !plan.values.is_empty() {
|
||||
let reg_result_cols_start = emit_values(program, &plan, &t_ctx.resolver)?;
|
||||
|
@ -348,13 +349,20 @@ pub fn emit_query<'a>(
|
|||
&plan.table_references,
|
||||
&plan.join_order,
|
||||
&mut plan.where_clause,
|
||||
None,
|
||||
)?;
|
||||
|
||||
// Process result columns and expressions in the inner loop
|
||||
emit_loop(program, t_ctx, plan)?;
|
||||
|
||||
// Clean up and close the main execution loop
|
||||
close_loop(program, t_ctx, &plan.table_references, &plan.join_order)?;
|
||||
close_loop(
|
||||
program,
|
||||
t_ctx,
|
||||
&plan.table_references,
|
||||
&plan.join_order,
|
||||
None,
|
||||
)?;
|
||||
|
||||
program.preassign_label_to_next_insn(after_main_loop_label);
|
||||
|
||||
|
@ -439,6 +447,7 @@ fn emit_program_for_delete(
|
|||
&plan.table_references,
|
||||
&[JoinOrderMember::default()],
|
||||
&mut plan.where_clause,
|
||||
None,
|
||||
)?;
|
||||
|
||||
emit_delete_insns(program, &mut t_ctx, &plan.table_references)?;
|
||||
|
@ -449,6 +458,7 @@ fn emit_program_for_delete(
|
|||
&mut t_ctx,
|
||||
&plan.table_references,
|
||||
&[JoinOrderMember::default()],
|
||||
None,
|
||||
)?;
|
||||
program.preassign_label_to_next_insn(after_main_loop_label);
|
||||
|
||||
|
@ -603,6 +613,24 @@ fn emit_program_for_update(
|
|||
});
|
||||
}
|
||||
|
||||
let ephemeral_plan = plan.ephemeral_plan.take();
|
||||
let temp_cursor_id = ephemeral_plan.as_ref().map(|plan| {
|
||||
let QueryDestination::EphemeralTable { cursor_id, .. } = &plan.query_destination else {
|
||||
unreachable!()
|
||||
};
|
||||
*cursor_id
|
||||
});
|
||||
if let Some(ephemeral_plan) = ephemeral_plan {
|
||||
program.emit_insn(Insn::OpenEphemeral {
|
||||
cursor_id: temp_cursor_id.unwrap(),
|
||||
is_table: true,
|
||||
});
|
||||
program.incr_nesting();
|
||||
emit_program_for_select(program, ephemeral_plan, schema, syms)?;
|
||||
program.decr_nesting();
|
||||
}
|
||||
|
||||
// Initialize the main loop
|
||||
init_loop(
|
||||
program,
|
||||
&mut t_ctx,
|
||||
|
@ -612,10 +640,11 @@ fn emit_program_for_update(
|
|||
OperationMode::UPDATE,
|
||||
&plan.where_clause,
|
||||
)?;
|
||||
// Open indexes for update.
|
||||
|
||||
// Prepare index cursors
|
||||
let mut index_cursors = Vec::with_capacity(plan.indexes_to_update.len());
|
||||
for index in &plan.indexes_to_update {
|
||||
if let Some(index_cursor) = program.resolve_cursor_id_safe(&CursorKey::index(
|
||||
let index_cursor = if let Some(cursor) = program.resolve_cursor_id_safe(&CursorKey::index(
|
||||
plan.table_references
|
||||
.joined_tables()
|
||||
.first()
|
||||
|
@ -623,34 +652,40 @@ fn emit_program_for_update(
|
|||
.internal_id,
|
||||
index.clone(),
|
||||
)) {
|
||||
// Don't reopen index if it was already opened as the iteration cursor for this update plan.
|
||||
let record_reg = program.alloc_register();
|
||||
index_cursors.push((index_cursor, record_reg));
|
||||
continue;
|
||||
}
|
||||
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),
|
||||
name: index.name.clone(),
|
||||
});
|
||||
cursor
|
||||
} else {
|
||||
let cursor = program.alloc_cursor_id(CursorType::BTreeIndex(index.clone()));
|
||||
program.emit_insn(Insn::OpenWrite {
|
||||
cursor_id: cursor,
|
||||
root_page: RegisterOrLiteral::Literal(index.root_page),
|
||||
name: index.name.clone(),
|
||||
});
|
||||
cursor
|
||||
};
|
||||
let record_reg = program.alloc_register();
|
||||
index_cursors.push((index_cursor, record_reg));
|
||||
}
|
||||
|
||||
// Open the main loop
|
||||
open_loop(
|
||||
program,
|
||||
&mut t_ctx,
|
||||
&plan.table_references,
|
||||
&[JoinOrderMember::default()],
|
||||
&mut plan.where_clause,
|
||||
temp_cursor_id,
|
||||
)?;
|
||||
emit_update_insns(&plan, &t_ctx, program, index_cursors)?;
|
||||
|
||||
// Emit update instructions
|
||||
emit_update_insns(&plan, &t_ctx, program, index_cursors, temp_cursor_id)?;
|
||||
|
||||
// Close the main loop
|
||||
close_loop(
|
||||
program,
|
||||
&mut t_ctx,
|
||||
&plan.table_references,
|
||||
&[JoinOrderMember::default()],
|
||||
temp_cursor_id,
|
||||
)?;
|
||||
|
||||
program.preassign_label_to_next_insn(after_main_loop_label);
|
||||
|
@ -670,12 +705,13 @@ fn emit_update_insns(
|
|||
t_ctx: &TranslateCtx,
|
||||
program: &mut ProgramBuilder,
|
||||
index_cursors: Vec<(usize, usize)>,
|
||||
temp_cursor_id: Option<CursorID>,
|
||||
) -> crate::Result<()> {
|
||||
let table_ref = plan.table_references.joined_tables().first().unwrap();
|
||||
let loop_labels = t_ctx.labels_main_loop.first().unwrap();
|
||||
let (cursor_id, index, is_virtual) = match &table_ref.op {
|
||||
let cursor_id = program.resolve_cursor_id(&CursorKey::table(table_ref.internal_id));
|
||||
let (index, is_virtual) = match &table_ref.op {
|
||||
Operation::Scan { index, .. } => (
|
||||
program.resolve_cursor_id(&CursorKey::table(table_ref.internal_id)),
|
||||
index.as_ref().map(|index| {
|
||||
(
|
||||
index.clone(),
|
||||
|
@ -686,15 +722,10 @@ fn emit_update_insns(
|
|||
table_ref.virtual_table().is_some(),
|
||||
),
|
||||
Operation::Search(search) => match search {
|
||||
&Search::RowidEq { .. } | Search::Seek { index: None, .. } => (
|
||||
program.resolve_cursor_id(&CursorKey::table(table_ref.internal_id)),
|
||||
None,
|
||||
false,
|
||||
),
|
||||
&Search::RowidEq { .. } | Search::Seek { index: None, .. } => (None, false),
|
||||
Search::Seek {
|
||||
index: Some(index), ..
|
||||
} => (
|
||||
program.resolve_cursor_id(&CursorKey::table(table_ref.internal_id)),
|
||||
Some((
|
||||
index.clone(),
|
||||
program
|
||||
|
@ -714,26 +745,26 @@ fn emit_update_insns(
|
|||
},
|
||||
);
|
||||
program.emit_insn(Insn::RowId {
|
||||
cursor_id,
|
||||
cursor_id: temp_cursor_id.unwrap_or(cursor_id),
|
||||
dest: beg,
|
||||
});
|
||||
|
||||
// Check if rowid was provided (through INTEGER PRIMARY KEY as a rowid alias)
|
||||
|
||||
let rowid_alias_index = {
|
||||
let rowid_alias_index = table_ref.columns().iter().position(|c| c.is_rowid_alias);
|
||||
if let Some(index) = rowid_alias_index {
|
||||
plan.set_clauses.iter().position(|(idx, _)| *idx == index)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
let rowid_set_clause_reg = if rowid_alias_index.is_some() {
|
||||
let rowid_alias_index = table_ref.columns().iter().position(|c| c.is_rowid_alias);
|
||||
|
||||
let has_user_provided_rowid = if let Some(index) = rowid_alias_index {
|
||||
plan.set_clauses.iter().position(|(idx, _)| *idx == index)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
.is_some();
|
||||
|
||||
let rowid_set_clause_reg = if has_user_provided_rowid {
|
||||
Some(program.alloc_register())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let has_user_provided_rowid = rowid_alias_index.is_some();
|
||||
|
||||
let check_rowid_not_exists_label = if has_user_provided_rowid {
|
||||
Some(program.allocate_label())
|
||||
|
|
|
@ -514,8 +514,8 @@ impl<'a> GroupByAggArgumentSource<'a> {
|
|||
/// Emits bytecode for processing a single GROUP BY group.
|
||||
pub fn group_by_process_single_group<'a>(
|
||||
program: &mut ProgramBuilder,
|
||||
group_by: &'a GroupBy,
|
||||
plan: &'a SelectPlan,
|
||||
group_by: &GroupBy,
|
||||
plan: &SelectPlan,
|
||||
t_ctx: &mut TranslateCtx<'a>,
|
||||
) -> Result<()> {
|
||||
let GroupByMetadata {
|
||||
|
@ -718,7 +718,7 @@ pub fn group_by_process_single_group<'a>(
|
|||
pub fn group_by_agg_phase<'a>(
|
||||
program: &mut ProgramBuilder,
|
||||
t_ctx: &mut TranslateCtx<'a>,
|
||||
plan: &'a SelectPlan,
|
||||
plan: &SelectPlan,
|
||||
) -> Result<()> {
|
||||
let GroupByMetadata {
|
||||
labels, row_source, ..
|
||||
|
|
|
@ -261,12 +261,16 @@ pub fn init_loop(
|
|||
}
|
||||
}
|
||||
OperationMode::DELETE | OperationMode::UPDATE => {
|
||||
let table_cursor_id = table_cursor_id.expect("table cursor is always opened in OperationMode::DELETE or OperationMode::UPDATE");
|
||||
let table_cursor_id = table_cursor_id.expect(
|
||||
"table cursor is always opened in OperationMode::DELETE or OperationMode::UPDATE",
|
||||
);
|
||||
|
||||
program.emit_insn(Insn::OpenWrite {
|
||||
cursor_id: table_cursor_id,
|
||||
root_page: table.table.get_root_page().into(),
|
||||
name: table.table.get_name().to_string(),
|
||||
});
|
||||
|
||||
// For DELETE, we need to open all the indexes for writing
|
||||
// UPDATE opens these in emit_program_for_update() separately
|
||||
if mode == OperationMode::DELETE {
|
||||
|
@ -357,6 +361,7 @@ pub fn open_loop(
|
|||
table_references: &TableReferences,
|
||||
join_order: &[JoinOrderMember],
|
||||
predicates: &[WhereTerm],
|
||||
temp_cursor_id: Option<CursorID>,
|
||||
) -> Result<()> {
|
||||
for (join_index, join) in join_order.iter().enumerate() {
|
||||
let joined_table_index = join.original_idx;
|
||||
|
@ -389,8 +394,12 @@ pub fn open_loop(
|
|||
Operation::Scan { iter_dir, .. } => {
|
||||
match &table.table {
|
||||
Table::BTree(_) => {
|
||||
let iteration_cursor_id = index_cursor_id.unwrap_or_else(|| {
|
||||
table_cursor_id.expect("Either index or table cursor must be opened")
|
||||
let iteration_cursor_id = temp_cursor_id.unwrap_or_else(|| {
|
||||
index_cursor_id.unwrap_or_else(|| {
|
||||
table_cursor_id.expect(
|
||||
"Either ephemeral or index or table cursor must be opened",
|
||||
)
|
||||
})
|
||||
});
|
||||
if *iter_dir == IterationDirection::Backwards {
|
||||
program.emit_insn(Insn::Last {
|
||||
|
@ -637,8 +646,11 @@ pub fn open_loop(
|
|||
};
|
||||
|
||||
let is_index = index_cursor_id.is_some();
|
||||
let seek_cursor_id = index_cursor_id.unwrap_or_else(|| {
|
||||
table_cursor_id.expect("Either index or table cursor must be opened")
|
||||
let seek_cursor_id = temp_cursor_id.unwrap_or_else(|| {
|
||||
index_cursor_id.unwrap_or_else(|| {
|
||||
table_cursor_id
|
||||
.expect("Either ephemeral or index or table cursor must be opened")
|
||||
})
|
||||
});
|
||||
let Search::Seek { seek_def, .. } = search else {
|
||||
unreachable!("Rowid equality point lookup should have been handled above");
|
||||
|
@ -738,7 +750,7 @@ enum LoopEmitTarget {
|
|||
pub fn emit_loop<'a>(
|
||||
program: &mut ProgramBuilder,
|
||||
t_ctx: &mut TranslateCtx<'a>,
|
||||
plan: &'a SelectPlan,
|
||||
plan: &SelectPlan,
|
||||
) -> Result<()> {
|
||||
// if we have a group by, we emit a record into the group by sorter,
|
||||
// or if the rows are already sorted, we do the group by aggregation phase directly.
|
||||
|
@ -764,7 +776,7 @@ pub fn emit_loop<'a>(
|
|||
fn emit_loop_source<'a>(
|
||||
program: &mut ProgramBuilder,
|
||||
t_ctx: &mut TranslateCtx<'a>,
|
||||
plan: &'a SelectPlan,
|
||||
plan: &SelectPlan,
|
||||
emit_target: LoopEmitTarget,
|
||||
) -> Result<()> {
|
||||
match emit_target {
|
||||
|
@ -971,6 +983,7 @@ pub fn close_loop(
|
|||
t_ctx: &mut TranslateCtx,
|
||||
tables: &TableReferences,
|
||||
join_order: &[JoinOrderMember],
|
||||
temp_cursor_id: Option<CursorID>,
|
||||
) -> Result<()> {
|
||||
// We close the loops for all tables in reverse order, i.e. innermost first.
|
||||
// OPEN t1
|
||||
|
@ -995,8 +1008,12 @@ pub fn close_loop(
|
|||
program.resolve_label(loop_labels.next, program.offset());
|
||||
match &table.table {
|
||||
Table::BTree(_) => {
|
||||
let iteration_cursor_id = index_cursor_id.unwrap_or_else(|| {
|
||||
table_cursor_id.expect("Either index or table cursor must be opened")
|
||||
let iteration_cursor_id = temp_cursor_id.unwrap_or_else(|| {
|
||||
index_cursor_id.unwrap_or_else(|| {
|
||||
table_cursor_id.expect(
|
||||
"Either ephemeral or index or table cursor must be opened",
|
||||
)
|
||||
})
|
||||
});
|
||||
if *iter_dir == IterationDirection::Backwards {
|
||||
program.emit_insn(Insn::Prev {
|
||||
|
@ -1035,8 +1052,11 @@ pub fn close_loop(
|
|||
"Subqueries do not support index seeks"
|
||||
);
|
||||
program.resolve_label(loop_labels.next, program.offset());
|
||||
let iteration_cursor_id = index_cursor_id.unwrap_or_else(|| {
|
||||
table_cursor_id.expect("Either index or table cursor must be opened")
|
||||
let iteration_cursor_id = temp_cursor_id.unwrap_or_else(|| {
|
||||
index_cursor_id.unwrap_or_else(|| {
|
||||
table_cursor_id
|
||||
.expect("Either ephemeral or index or table cursor must be opened")
|
||||
})
|
||||
});
|
||||
// Rowid equality point lookups are handled with a SeekRowid instruction which does not loop, so there is no need to emit a Next instruction.
|
||||
if !matches!(search, Search::RowidEq { .. }) {
|
||||
|
|
|
@ -107,7 +107,7 @@ fn optimize_delete_plan(plan: &mut DeletePlan, _schema: &Schema) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn optimize_update_plan(plan: &mut UpdatePlan, _schema: &Schema) -> Result<()> {
|
||||
fn optimize_update_plan(plan: &mut UpdatePlan, schema: &Schema) -> Result<()> {
|
||||
rewrite_exprs_update(plan)?;
|
||||
if let ConstantConditionEliminationResult::ImpossibleCondition =
|
||||
eliminate_constant_conditions(&mut plan.where_clause)?
|
||||
|
@ -115,18 +115,16 @@ fn optimize_update_plan(plan: &mut UpdatePlan, _schema: &Schema) -> Result<()> {
|
|||
plan.contains_constant_false_condition = true;
|
||||
return Ok(());
|
||||
}
|
||||
// FIXME: don't use indexes for update right now because it's not safe to traverse an index
|
||||
// while also updating the same table, things go wrong.
|
||||
// e.g. in 'explain update t set x=x+5 where x > 10;' where x is an indexed column,
|
||||
// sqlite first creates an ephemeral index to store the current values so the tree traversal
|
||||
// doesn't get messed up while updating.
|
||||
// let _ = optimize_table_access(
|
||||
// &mut plan.table_references,
|
||||
// &schema.indexes,
|
||||
// &mut plan.where_clause,
|
||||
// &mut plan.order_by,
|
||||
// &mut None,
|
||||
// )?;
|
||||
if let Some(ephemeral_plan) = &mut plan.ephemeral_plan {
|
||||
optimize_select_plan(ephemeral_plan, schema)?;
|
||||
}
|
||||
let _ = optimize_table_access(
|
||||
&mut plan.table_references,
|
||||
&schema.indexes,
|
||||
&mut plan.where_clause,
|
||||
&mut plan.order_by,
|
||||
&mut None,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -236,6 +234,7 @@ fn optimize_table_access(
|
|||
.map_or(false, |join_info| join_info.outer),
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Mutate the Operations in `joined_tables` to use the selected access methods.
|
||||
for (i, join_order_member) in best_join_order.iter().enumerate() {
|
||||
let table_idx = join_order_member.original_idx;
|
||||
|
|
|
@ -325,6 +325,14 @@ pub enum QueryDestination {
|
|||
/// The index that will be used to store the results.
|
||||
index: Arc<Index>,
|
||||
},
|
||||
/// The results of the query are stored in an ephemeral table,
|
||||
/// later used by the parent query.
|
||||
EphemeralTable {
|
||||
/// The cursor ID of the ephemeral table that will be used to store the results.
|
||||
cursor_id: CursorID,
|
||||
/// The table that will be used to store the results.
|
||||
table: Rc<BTreeTable>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
@ -534,6 +542,8 @@ pub struct UpdatePlan {
|
|||
// whether the WHERE clause is always false
|
||||
pub contains_constant_false_condition: bool,
|
||||
pub indexes_to_update: Vec<Arc<Index>>,
|
||||
// If the table's rowid alias is used, gather all the target rowids into an ephemeral table, and then use that table as the single JoinedTable for the actual UPDATE loop.
|
||||
pub ephemeral_plan: Option<SelectPlan>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
vdbe::{
|
||||
builder::ProgramBuilder,
|
||||
insn::{IdxInsertFlags, Insn},
|
||||
insn::{IdxInsertFlags, InsertFlags, Insn},
|
||||
BranchOffset,
|
||||
},
|
||||
Result,
|
||||
|
@ -100,6 +100,27 @@ pub fn emit_result_row_and_limit(
|
|||
flags: IdxInsertFlags::new(),
|
||||
});
|
||||
}
|
||||
QueryDestination::EphemeralTable {
|
||||
cursor_id: table_cursor_id,
|
||||
table,
|
||||
} => {
|
||||
let record_reg = program.alloc_register();
|
||||
if plan.result_columns.len() > 1 {
|
||||
program.emit_insn(Insn::MakeRecord {
|
||||
start_reg: result_columns_start_reg,
|
||||
count: plan.result_columns.len() - 1,
|
||||
dest_reg: record_reg,
|
||||
index_name: Some(table.name.clone()),
|
||||
});
|
||||
}
|
||||
program.emit_insn(Insn::Insert {
|
||||
cursor: *table_cursor_id,
|
||||
key_reg: result_columns_start_reg + (plan.result_columns.len() - 1), // Rowid reg is the last register
|
||||
record_reg,
|
||||
flag: InsertFlags(0),
|
||||
table_name: table.name.clone(),
|
||||
});
|
||||
}
|
||||
QueryDestination::CoroutineYield { yield_reg, .. } => {
|
||||
program.emit_insn(Insn::Yield {
|
||||
yield_reg: *yield_reg,
|
||||
|
|
|
@ -715,7 +715,7 @@ fn estimate_num_labels(select: &SelectPlan) -> usize {
|
|||
pub fn emit_simple_count<'a>(
|
||||
program: &mut ProgramBuilder,
|
||||
_t_ctx: &mut TranslateCtx<'a>,
|
||||
plan: &'a SelectPlan,
|
||||
plan: &SelectPlan,
|
||||
) -> Result<()> {
|
||||
let cursors = plan
|
||||
.joined_tables()
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
use crate::translate::plan::Operation;
|
||||
use crate::vdbe::builder::TableRefIdCounter;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::schema::{BTreeTable, Column, Type};
|
||||
use crate::translate::plan::{Operation, QueryDestination, SelectPlan};
|
||||
use crate::vdbe::builder::CursorType;
|
||||
use crate::{
|
||||
bail_parse_error,
|
||||
schema::{Schema, Table},
|
||||
|
@ -53,7 +56,7 @@ pub fn translate_update(
|
|||
syms: &SymbolTable,
|
||||
mut program: ProgramBuilder,
|
||||
) -> crate::Result<ProgramBuilder> {
|
||||
let mut plan = prepare_update_plan(schema, body, &mut program.table_reference_counter)?;
|
||||
let mut plan = prepare_update_plan(&mut program, schema, body)?;
|
||||
optimize_plan(&mut plan, schema)?;
|
||||
// TODO: freestyling these numbers
|
||||
let opts = ProgramBuilderOpts {
|
||||
|
@ -75,7 +78,7 @@ pub fn translate_update_with_after(
|
|||
mut program: ProgramBuilder,
|
||||
after: impl FnOnce(&mut ProgramBuilder),
|
||||
) -> crate::Result<ProgramBuilder> {
|
||||
let mut plan = prepare_update_plan(schema, body, &mut program.table_reference_counter)?;
|
||||
let mut plan = prepare_update_plan(&mut program, schema, body)?;
|
||||
optimize_plan(&mut plan, schema)?;
|
||||
// TODO: freestyling these numbers
|
||||
let opts = ProgramBuilderOpts {
|
||||
|
@ -90,9 +93,9 @@ pub fn translate_update_with_after(
|
|||
}
|
||||
|
||||
pub fn prepare_update_plan(
|
||||
program: &mut ProgramBuilder,
|
||||
schema: &Schema,
|
||||
body: &mut Update,
|
||||
table_ref_counter: &mut TableRefIdCounter,
|
||||
) -> crate::Result<Plan> {
|
||||
if body.with.is_some() {
|
||||
bail_parse_error!("WITH clause is not supported");
|
||||
|
@ -127,6 +130,7 @@ pub fn prepare_update_plan(
|
|||
})
|
||||
})
|
||||
.unwrap_or(IterationDirection::Forwards);
|
||||
|
||||
let joined_tables = vec![JoinedTable {
|
||||
table: match table.as_ref() {
|
||||
Table::Virtual(vtab) => Table::Virtual(vtab.clone()),
|
||||
|
@ -134,7 +138,7 @@ pub fn prepare_update_plan(
|
|||
_ => unreachable!(),
|
||||
},
|
||||
identifier: table_name.0.clone(),
|
||||
internal_id: table_ref_counter.next(),
|
||||
internal_id: program.table_reference_counter.next(),
|
||||
op: Operation::Scan {
|
||||
iter_dir,
|
||||
index: None,
|
||||
|
@ -198,13 +202,103 @@ pub fn prepare_update_plan(
|
|||
.map(|o| (o.expr.clone(), o.order.unwrap_or(SortOrder::Asc)))
|
||||
.collect()
|
||||
});
|
||||
// Parse the WHERE clause
|
||||
parse_where(
|
||||
body.where_clause.as_ref().map(|w| *w.clone()),
|
||||
&mut table_references,
|
||||
Some(&result_columns),
|
||||
&mut where_clause,
|
||||
)?;
|
||||
|
||||
// Sqlite determines we should create an ephemeral table if we do not have a FROM clause
|
||||
// Difficult to say what items from the plan can be checked for this so currently just checking if a RowId Alias is referenced
|
||||
// https://github.com/sqlite/sqlite/blob/master/src/update.c#L395
|
||||
// https://github.com/sqlite/sqlite/blob/master/src/update.c#L670
|
||||
let columns = table.columns();
|
||||
|
||||
let rowid_alias_used = set_clauses.iter().fold(false, |accum, (idx, _)| {
|
||||
accum || columns[*idx].is_rowid_alias
|
||||
});
|
||||
|
||||
let (ephemeral_plan, where_clause) = if rowid_alias_used {
|
||||
let internal_id = program.table_reference_counter.next();
|
||||
|
||||
let joined_tables = vec![JoinedTable {
|
||||
table: match table.as_ref() {
|
||||
Table::Virtual(vtab) => Table::Virtual(vtab.clone()),
|
||||
Table::BTree(btree_table) => Table::BTree(btree_table.clone()),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
identifier: table_name.0.clone(),
|
||||
internal_id,
|
||||
op: Operation::Scan {
|
||||
iter_dir,
|
||||
index: None,
|
||||
},
|
||||
join_info: None,
|
||||
col_used_mask: ColumnUsedMask::new(),
|
||||
}];
|
||||
let mut table_references = TableReferences::new(joined_tables, vec![]);
|
||||
|
||||
// Parse the WHERE clause
|
||||
parse_where(
|
||||
body.where_clause.as_ref().map(|w| *w.clone()),
|
||||
&mut table_references,
|
||||
Some(&result_columns),
|
||||
&mut where_clause,
|
||||
)?;
|
||||
|
||||
let table = Rc::new(BTreeTable {
|
||||
root_page: 0, // Not relevant for ephemeral table definition
|
||||
name: "ephemeral_scratch".to_string(),
|
||||
has_rowid: true,
|
||||
primary_key_columns: vec![],
|
||||
columns: vec![Column {
|
||||
name: Some("rowid".to_string()),
|
||||
ty: Type::Integer,
|
||||
ty_str: "INTEGER".to_string(),
|
||||
primary_key: true,
|
||||
is_rowid_alias: false,
|
||||
notnull: true,
|
||||
default: None,
|
||||
unique: false,
|
||||
collation: None,
|
||||
}],
|
||||
is_strict: false,
|
||||
unique_sets: None,
|
||||
});
|
||||
|
||||
let temp_cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(table.clone()));
|
||||
|
||||
let ephemeral_plan = SelectPlan {
|
||||
table_references,
|
||||
result_columns: vec![ResultSetColumn {
|
||||
expr: Expr::RowId {
|
||||
database: None,
|
||||
table: internal_id,
|
||||
},
|
||||
alias: None,
|
||||
contains_aggregates: false,
|
||||
}],
|
||||
where_clause, // original WHERE terms from the UPDATE clause
|
||||
group_by: None, // N/A
|
||||
order_by: None, // N/A
|
||||
aggregates: vec![], // N/A
|
||||
limit: None, // N/A
|
||||
query_destination: QueryDestination::EphemeralTable {
|
||||
cursor_id: temp_cursor_id,
|
||||
table,
|
||||
},
|
||||
join_order: vec![],
|
||||
offset: None,
|
||||
contains_constant_false_condition: false,
|
||||
distinctness: super::plan::Distinctness::NonDistinct,
|
||||
values: vec![],
|
||||
};
|
||||
(Some(ephemeral_plan), vec![])
|
||||
} else {
|
||||
// Parse the WHERE clause
|
||||
parse_where(
|
||||
body.where_clause.as_ref().map(|w| *w.clone()),
|
||||
&mut table_references,
|
||||
Some(&result_columns),
|
||||
&mut where_clause,
|
||||
)?;
|
||||
(None, where_clause)
|
||||
};
|
||||
|
||||
// Parse the LIMIT/OFFSET clause
|
||||
let (limit, offset) = body
|
||||
|
@ -238,5 +332,6 @@ pub fn prepare_update_plan(
|
|||
offset,
|
||||
contains_constant_false_condition: false,
|
||||
indexes_to_update,
|
||||
ephemeral_plan,
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ pub fn emit_values(
|
|||
emit_values_in_subquery(program, plan, resolver, yield_reg)?
|
||||
}
|
||||
QueryDestination::EphemeralIndex { .. } => unreachable!(),
|
||||
QueryDestination::EphemeralTable { .. } => unreachable!(),
|
||||
};
|
||||
Ok(reg_result_cols_start)
|
||||
}
|
||||
|
@ -58,6 +59,7 @@ fn emit_values_when_single_row(
|
|||
});
|
||||
}
|
||||
QueryDestination::EphemeralIndex { .. } => unreachable!(),
|
||||
QueryDestination::EphemeralTable { .. } => unreachable!(),
|
||||
}
|
||||
Ok(start_reg)
|
||||
}
|
||||
|
|
|
@ -859,9 +859,12 @@ impl ImmutableRecord {
|
|||
self.values.len()
|
||||
}
|
||||
|
||||
pub fn from_registers(registers: &[Register]) -> Self {
|
||||
let mut values = Vec::with_capacity(registers.len());
|
||||
let mut serials = Vec::with_capacity(registers.len());
|
||||
pub fn from_registers<'a>(
|
||||
registers: impl IntoIterator<Item = &'a Register> + Copy,
|
||||
len: usize,
|
||||
) -> Self {
|
||||
let mut values = Vec::with_capacity(len);
|
||||
let mut serials = Vec::with_capacity(len);
|
||||
let mut size_header = 0;
|
||||
let mut size_values = 0;
|
||||
|
||||
|
|
|
@ -4236,15 +4236,25 @@ pub fn op_insert(
|
|||
{
|
||||
let mut cursor = state.get_cursor(*cursor);
|
||||
let cursor = cursor.as_btree_mut();
|
||||
let record = match &state.registers[*record_reg] {
|
||||
Register::Record(r) => r,
|
||||
_ => unreachable!("Not a record! Cannot insert a non record value."),
|
||||
};
|
||||
|
||||
let key = match &state.registers[*key_reg].get_owned_value() {
|
||||
Value::Integer(i) => *i,
|
||||
_ => unreachable!("expected integer key"),
|
||||
};
|
||||
return_if_io!(cursor.insert(&BTreeKey::new_table_rowid(key, Some(record)), true));
|
||||
|
||||
let record = match &state.registers[*record_reg] {
|
||||
Register::Record(r) => std::borrow::Cow::Borrowed(r),
|
||||
Register::Value(value) => {
|
||||
let x = 1;
|
||||
let regs = &state.registers[*record_reg..*record_reg + 1];
|
||||
let new_regs = [&state.registers[*record_reg]];
|
||||
let record = ImmutableRecord::from_registers(new_regs, new_regs.len());
|
||||
std::borrow::Cow::Owned(record)
|
||||
}
|
||||
Register::Aggregate(..) => unreachable!("Cannot insert an aggregate value."),
|
||||
};
|
||||
|
||||
return_if_io!(cursor.insert(&BTreeKey::new_table_rowid(key, Some(record.as_ref())), true));
|
||||
// Only update last_insert_rowid for regular table inserts, not schema modifications
|
||||
if cursor.root_page() != 1 {
|
||||
if let Some(rowid) = return_if_io!(cursor.rowid()) {
|
||||
|
|
|
@ -522,7 +522,8 @@ fn get_new_rowid<R: Rng>(cursor: &mut BTreeCursor, mut rng: R) -> Result<CursorR
|
|||
}
|
||||
|
||||
fn make_record(registers: &[Register], start_reg: &usize, count: &usize) -> ImmutableRecord {
|
||||
ImmutableRecord::from_registers(®isters[*start_reg..*start_reg + *count])
|
||||
let regs = ®isters[*start_reg..*start_reg + *count];
|
||||
ImmutableRecord::from_registers(regs, regs.len())
|
||||
}
|
||||
|
||||
#[instrument(skip(program), level = Level::TRACE)]
|
||||
|
|
|
@ -214,3 +214,42 @@ do_execsql_test_on_specific_db {:memory:} update_where_or_regression_test {
|
|||
SELECT * from t;
|
||||
} {lovely_revolt
|
||||
lovely_revolt}
|
||||
|
||||
do_execsql_test_in_memory_any_error update_primary_key_constraint_error {
|
||||
CREATE TABLE eye (study REAL, spring BLOB, save TEXT, thank REAL, election INTEGER, PRIMARY KEY (election));
|
||||
INSERT INTO eye VALUES (183559032.521585, x'6625d092', 'Trial six should.', 2606132742.43174, 2817);
|
||||
INSERT INTO eye VALUES (78255586.9204539, x'651061e8', 'World perhaps.', -5815764.49018679, 1917);
|
||||
UPDATE eye SET election = 6150;
|
||||
}
|
||||
|
||||
do_execsql_test_in_memory_any_error update_primary_key_constraint_error_2 {
|
||||
CREATE TABLE eye (study REAL, spring BLOB, save TEXT, thank REAL, election INTEGER, PRIMARY KEY (election));
|
||||
INSERT INTO eye VALUES (183559032.521585, x'6625d092', 'Trial six should.', 2606132742.43174, 2817);
|
||||
INSERT INTO eye VALUES (78255586.9204539, x'651061e8', 'World perhaps.', -5815764.49018679, 1917);
|
||||
INSERT INTO eye VALUES (53.3274327094467, x'f574c507', 'Senior wish degree.', -423.432750526747, 2650);
|
||||
INSERT INTO eye VALUES (-908148213048.983, x'6d812051', 'Possible able.', 101.171781837336, 4100);
|
||||
INSERT INTO eye VALUES (-572332773760.924, x'd7a4d9fb', 'Money catch expect.', -271065488.756746, 4667);
|
||||
UPDATE eye SET election = 6150 WHERE election != 1917;
|
||||
}
|
||||
|
||||
do_execsql_test_in_memory_any_error update_primary_key_constraint_error_3 {
|
||||
CREATE TABLE eye (study REAL, spring BLOB, save TEXT, thank REAL, election INTEGER, PRIMARY KEY (election));
|
||||
INSERT INTO eye VALUES (183559032.521585, x'6625d092', 'Trial six should.', 2606132742.43174, 2817);
|
||||
INSERT INTO eye VALUES (78255586.9204539, x'651061e8', 'World perhaps.', -5815764.49018679, 1917);
|
||||
INSERT INTO eye VALUES (53.3274327094467, x'f574c507', 'Senior wish degree.', -423.432750526747, 2650);
|
||||
INSERT INTO eye VALUES (-908148213048.983, x'6d812051', 'Possible able.', 101.171781837336, 4100);
|
||||
INSERT INTO eye VALUES (-572332773760.924, x'd7a4d9fb', 'Money catch expect.', -271065488.756746, 4667);
|
||||
UPDATE eye SET election = 6150 WHERE election > 1000 AND study > 1;
|
||||
}
|
||||
|
||||
do_execsql_test_in_memory_any_error update_primary_key_constraint_error_4 {
|
||||
CREATE TABLE t(a PRIMARY KEY INTEGER, b UNIQUE);
|
||||
INSERT INTO t(b) VALUES (100), (200), (300);
|
||||
UPDATE t SET a = 1;
|
||||
}
|
||||
|
||||
do_execsql_test_in_memory_any_error update_primary_key_unique_constraint_error {
|
||||
CREATE TABLE t(a PRIMARY KEY INTEGER, b UNIQUE);
|
||||
INSERT INTO t(b) VALUES (100), (200), (300);
|
||||
UPDATE t SET b = 2;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue