mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-08 01:34:23 +01:00
modify loop functions to accomodate for ephemeral tables
This commit is contained in:
@@ -22,14 +22,14 @@ use super::select::emit_simple_count;
|
||||
use super::subquery::emit_subqueries;
|
||||
use crate::error::SQLITE_CONSTRAINT_PRIMARYKEY;
|
||||
use crate::function::Func;
|
||||
use crate::schema::{BTreeTable, Column, Index, IndexColumn, Schema, Table, Type};
|
||||
use crate::schema::{Index, IndexColumn, Schema};
|
||||
use crate::translate::main_loop::EphemeralCtx;
|
||||
use crate::translate::compound_select::emit_program_for_compound_select;
|
||||
use crate::translate::plan::{DeletePlan, Plan, 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};
|
||||
|
||||
@@ -335,6 +335,7 @@ pub fn emit_query<'a>(
|
||||
plan.group_by.as_ref(),
|
||||
OperationMode::SELECT,
|
||||
&plan.where_clause,
|
||||
None,
|
||||
)?;
|
||||
|
||||
if plan.is_simple_count() {
|
||||
@@ -438,6 +439,7 @@ fn emit_program_for_delete(
|
||||
None,
|
||||
OperationMode::DELETE,
|
||||
&plan.where_clause,
|
||||
None,
|
||||
)?;
|
||||
|
||||
// Set up main query execution loop
|
||||
@@ -613,28 +615,56 @@ fn emit_program_for_update(
|
||||
});
|
||||
}
|
||||
|
||||
let temp_cursor_id = {
|
||||
// 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 the where clause
|
||||
// https://github.com/sqlite/sqlite/blob/master/src/update.c#L395
|
||||
// https://github.com/sqlite/sqlite/blob/master/src/update.c#L670
|
||||
if !plan.where_clause.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let table_ref = plan
|
||||
.table_references
|
||||
.joined_tables()
|
||||
.first()
|
||||
.expect("at least one table needs to be referenced for UPDATE");
|
||||
let columns = table_ref.columns();
|
||||
let table_ref = plan
|
||||
.table_references
|
||||
.joined_tables()
|
||||
.first()
|
||||
.expect("at least one table needs to be referenced for UPDATE");
|
||||
|
||||
let rowid_alias_used = plan.set_clauses.iter().fold(false, |accum, (idx, _)| {
|
||||
accum || columns[*idx].is_rowid_alias
|
||||
});
|
||||
let mut ephemeral_ctx = (plan.rowid_alias_used
|
||||
&& !matches!(table_ref.op, Operation::Search(Search::RowidEq { .. })))
|
||||
.then(|| EphemeralCtx::from_table(program, &table_ref));
|
||||
|
||||
rowid_alias_used.then(|| emit_ephemeral(program, &table_ref.table))
|
||||
}
|
||||
};
|
||||
if let Some(ephemeral_ctx) = &mut ephemeral_ctx {
|
||||
let mut t_ctx = TranslateCtx::new(
|
||||
program,
|
||||
schema,
|
||||
syms,
|
||||
plan.table_references.joined_tables().len(),
|
||||
plan.returning.as_ref().map_or(0, |r| r.len()),
|
||||
);
|
||||
|
||||
init_loop(
|
||||
program,
|
||||
&mut t_ctx,
|
||||
&plan.table_references,
|
||||
&mut [],
|
||||
None,
|
||||
OperationMode::UPDATE,
|
||||
Some(ephemeral_ctx),
|
||||
)?;
|
||||
|
||||
open_loop(
|
||||
program,
|
||||
&mut t_ctx,
|
||||
&plan.table_references,
|
||||
&[JoinOrderMember::default()],
|
||||
&mut plan.where_clause,
|
||||
Some(ephemeral_ctx),
|
||||
)?;
|
||||
|
||||
emit_ephemeral_insert(program, ephemeral_ctx)?;
|
||||
|
||||
close_loop(
|
||||
program,
|
||||
&mut t_ctx,
|
||||
&plan.table_references,
|
||||
&[JoinOrderMember::default()],
|
||||
Some(ephemeral_ctx),
|
||||
)?;
|
||||
|
||||
ephemeral_ctx.finished_insert_loop = true;
|
||||
}
|
||||
|
||||
init_loop(
|
||||
program,
|
||||
@@ -644,6 +674,7 @@ fn emit_program_for_update(
|
||||
None,
|
||||
OperationMode::UPDATE,
|
||||
&plan.where_clause,
|
||||
ephemeral_ctx.as_ref(),
|
||||
)?;
|
||||
// Open indexes for update.
|
||||
let mut index_cursors = Vec::with_capacity(plan.indexes_to_update.len());
|
||||
@@ -670,24 +701,72 @@ fn emit_program_for_update(
|
||||
let record_reg = program.alloc_register();
|
||||
index_cursors.push((index_cursor, record_reg));
|
||||
}
|
||||
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, temp_cursor_id)?;
|
||||
|
||||
close_loop(
|
||||
if ephemeral_ctx.is_none() {
|
||||
open_loop(
|
||||
program,
|
||||
&mut t_ctx,
|
||||
&plan.table_references,
|
||||
&[JoinOrderMember::default()],
|
||||
&mut plan.where_clause,
|
||||
None,
|
||||
)?;
|
||||
} else {
|
||||
let ctx = ephemeral_ctx.as_ref().unwrap();
|
||||
let LoopLabels {
|
||||
loop_start,
|
||||
loop_end,
|
||||
..
|
||||
} = t_ctx
|
||||
.labels_main_loop
|
||||
.first()
|
||||
.expect("table has no loop labels");
|
||||
if !matches!(ctx.table.op, Operation::Search(Search::RowidEq { .. })) {
|
||||
program.emit_insn(Insn::Rewind {
|
||||
cursor_id: ctx.temp_cursor_id,
|
||||
pc_if_empty: *loop_end,
|
||||
});
|
||||
}
|
||||
|
||||
program.preassign_label_to_next_insn(*loop_start);
|
||||
}
|
||||
emit_update_insns(
|
||||
&plan,
|
||||
&t_ctx,
|
||||
program,
|
||||
&mut t_ctx,
|
||||
&plan.table_references,
|
||||
&[JoinOrderMember::default()],
|
||||
temp_cursor_id,
|
||||
index_cursors,
|
||||
ephemeral_ctx.as_ref(),
|
||||
)?;
|
||||
|
||||
if ephemeral_ctx.is_none() {
|
||||
close_loop(
|
||||
program,
|
||||
&mut t_ctx,
|
||||
&plan.table_references,
|
||||
&[JoinOrderMember::default()],
|
||||
None,
|
||||
)?;
|
||||
} else {
|
||||
let ctx = ephemeral_ctx.as_ref().unwrap();
|
||||
let LoopLabels {
|
||||
loop_start,
|
||||
loop_end,
|
||||
next,
|
||||
..
|
||||
} = t_ctx
|
||||
.labels_main_loop
|
||||
.first()
|
||||
.expect("table has no loop labels");
|
||||
program.preassign_label_to_next_insn(*next);
|
||||
if !matches!(ctx.table.op, Operation::Search(Search::RowidEq { .. })) {
|
||||
program.emit_insn(Insn::Next {
|
||||
cursor_id: ephemeral_ctx.as_ref().unwrap().temp_cursor_id,
|
||||
pc_if_next: *loop_start,
|
||||
});
|
||||
}
|
||||
program.preassign_label_to_next_insn(*loop_end);
|
||||
}
|
||||
|
||||
program.preassign_label_to_next_insn(after_main_loop_label);
|
||||
|
||||
after(program);
|
||||
@@ -705,7 +784,7 @@ fn emit_update_insns(
|
||||
t_ctx: &TranslateCtx,
|
||||
program: &mut ProgramBuilder,
|
||||
index_cursors: Vec<(usize, usize)>,
|
||||
temp_cursor_id: Option<CursorID>,
|
||||
ephemeral_ctx: Option<&EphemeralCtx>,
|
||||
) -> crate::Result<()> {
|
||||
let table_ref = plan.table_references.joined_tables().first().unwrap();
|
||||
let loop_labels = t_ctx.labels_main_loop.first().unwrap();
|
||||
@@ -736,6 +815,28 @@ fn emit_update_insns(
|
||||
},
|
||||
};
|
||||
|
||||
if ephemeral_ctx.is_none() {
|
||||
for cond in plan
|
||||
.where_clause
|
||||
.iter()
|
||||
.filter(|c| c.should_eval_before_loop(&[JoinOrderMember::default()]))
|
||||
{
|
||||
let jump_target = program.allocate_label();
|
||||
let meta = ConditionMetadata {
|
||||
jump_if_condition_is_true: false,
|
||||
jump_target_when_true: jump_target,
|
||||
jump_target_when_false: t_ctx.label_main_loop_end.unwrap(),
|
||||
};
|
||||
translate_condition_expr(
|
||||
program,
|
||||
&plan.table_references,
|
||||
&cond.expr,
|
||||
meta,
|
||||
&t_ctx.resolver,
|
||||
)?;
|
||||
program.preassign_label_to_next_insn(jump_target);
|
||||
}
|
||||
}
|
||||
let beg = program.alloc_registers(
|
||||
table_ref.table.columns().len()
|
||||
+ if is_virtual {
|
||||
@@ -745,7 +846,7 @@ fn emit_update_insns(
|
||||
},
|
||||
);
|
||||
program.emit_insn(Insn::RowId {
|
||||
cursor_id: temp_cursor_id.unwrap_or(cursor_id),
|
||||
cursor_id: ephemeral_ctx.map_or(cursor_id, |ctx| ctx.temp_cursor_id),
|
||||
dest: beg,
|
||||
});
|
||||
|
||||
@@ -802,6 +903,29 @@ fn emit_update_insns(
|
||||
});
|
||||
}
|
||||
|
||||
if ephemeral_ctx.is_none() {
|
||||
for cond in plan
|
||||
.where_clause
|
||||
.iter()
|
||||
.filter(|c| c.should_eval_before_loop(&[JoinOrderMember::default()]))
|
||||
{
|
||||
let jump_target = program.allocate_label();
|
||||
let meta = ConditionMetadata {
|
||||
jump_if_condition_is_true: false,
|
||||
jump_target_when_true: jump_target,
|
||||
jump_target_when_false: loop_labels.next,
|
||||
};
|
||||
translate_condition_expr(
|
||||
program,
|
||||
&plan.table_references,
|
||||
&cond.expr,
|
||||
meta,
|
||||
&t_ctx.resolver,
|
||||
)?;
|
||||
program.preassign_label_to_next_insn(jump_target);
|
||||
}
|
||||
}
|
||||
|
||||
// we scan a column at a time, loading either the column's values, or the new value
|
||||
// from the Set expression, into registers so we can emit a MakeRecord and update the row.
|
||||
let start = if is_virtual { beg + 2 } else { beg + 1 };
|
||||
@@ -1140,75 +1264,17 @@ fn init_limit(
|
||||
}
|
||||
|
||||
/// Emits an ephemeral table that reads the rowids from `table`
|
||||
fn emit_ephemeral(program: &mut ProgramBuilder, table: &Table) -> CursorID {
|
||||
let cursor_type = CursorType::BTreeTable(table.btree().unwrap());
|
||||
let cursor_id = program.alloc_cursor_id(cursor_type);
|
||||
|
||||
let simple_table_rc = 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: false,
|
||||
is_rowid_alias: false,
|
||||
notnull: false,
|
||||
default: None,
|
||||
unique: false,
|
||||
collation: None,
|
||||
}],
|
||||
is_strict: false,
|
||||
unique_sets: None,
|
||||
});
|
||||
let temp_cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(simple_table_rc));
|
||||
|
||||
let null_data_reg = program.alloc_register();
|
||||
let rowid_reg = program.alloc_register();
|
||||
program.emit_insn(Insn::Null {
|
||||
dest: null_data_reg,
|
||||
dest_end: Some(rowid_reg),
|
||||
});
|
||||
|
||||
program.emit_insn(Insn::OpenEphemeral {
|
||||
cursor_id: temp_cursor_id,
|
||||
is_table: true,
|
||||
});
|
||||
|
||||
program.emit_insn(Insn::OpenRead {
|
||||
cursor_id,
|
||||
root_page: table.get_root_page(),
|
||||
});
|
||||
|
||||
let loop_labels = LoopLabels::new(program);
|
||||
|
||||
program.emit_insn(Insn::Rewind {
|
||||
cursor_id,
|
||||
pc_if_empty: loop_labels.loop_end,
|
||||
});
|
||||
|
||||
program.preassign_label_to_next_insn(loop_labels.loop_start);
|
||||
|
||||
fn emit_ephemeral_insert(program: &mut ProgramBuilder, ctx: &EphemeralCtx) -> Result<()> {
|
||||
program.emit_insn(Insn::RowId {
|
||||
cursor_id,
|
||||
dest: rowid_reg,
|
||||
cursor_id: ctx.table_cursor_id,
|
||||
dest: ctx.rowid_reg,
|
||||
});
|
||||
program.emit_insn(Insn::Insert {
|
||||
cursor: temp_cursor_id,
|
||||
key_reg: rowid_reg,
|
||||
record_reg: null_data_reg,
|
||||
cursor: ctx.temp_cursor_id,
|
||||
key_reg: ctx.rowid_reg,
|
||||
record_reg: ctx.null_data_reg,
|
||||
flag: InsertFlags(0), // TODO: when we use the flags see if this needs to change
|
||||
table_name: table.get_name().to_string(),
|
||||
table_name: ctx.table.table.get_name().to_string(),
|
||||
});
|
||||
|
||||
program.preassign_label_to_next_insn(loop_labels.next);
|
||||
program.emit_insn(Insn::Next {
|
||||
cursor_id,
|
||||
pc_if_next: loop_labels.loop_start,
|
||||
});
|
||||
program.preassign_label_to_next_insn(loop_labels.loop_end);
|
||||
|
||||
temp_cursor_id
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use limbo_ext::VTabKind;
|
||||
use limbo_sqlite3_parser::ast::{self, SortOrder};
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::{rc::Rc, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
schema::{Affinity, Index, IndexColumn, Table},
|
||||
schema::{Affinity, BTreeTable, Column, Index, IndexColumn, Table, Type},
|
||||
translate::{
|
||||
plan::{DistinctCtx, Distinctness},
|
||||
plan::{DistinctCtx, Distinctness, JoinedTable},
|
||||
result_row::emit_select_result,
|
||||
},
|
||||
types::SeekOp,
|
||||
@@ -103,6 +103,56 @@ pub fn init_distinct(program: &mut ProgramBuilder, plan: &SelectPlan) -> Distinc
|
||||
return ctx;
|
||||
}
|
||||
|
||||
pub struct EphemeralCtx<'a> {
|
||||
pub temp_cursor_id: CursorID,
|
||||
pub table_cursor_id: CursorID,
|
||||
pub table: &'a JoinedTable,
|
||||
pub null_data_reg: usize,
|
||||
pub rowid_reg: usize,
|
||||
/// Indicates whether we closed the insert loop
|
||||
pub finished_insert_loop: bool,
|
||||
}
|
||||
|
||||
impl<'a> EphemeralCtx<'a> {
|
||||
/// Creates an [EphemeralCtx] a Btree `table`
|
||||
pub fn from_table(program: &mut ProgramBuilder, table: &'a JoinedTable) -> Self {
|
||||
let cursor_type = CursorType::BTreeTable(table.table.btree().unwrap());
|
||||
|
||||
let cursor_id =
|
||||
program.alloc_cursor_id_keyed(CursorKey::table(table.internal_id), cursor_type);
|
||||
|
||||
let simple_table_rc = 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: false,
|
||||
is_rowid_alias: false,
|
||||
notnull: false,
|
||||
default: None,
|
||||
unique: false,
|
||||
collation: None,
|
||||
}],
|
||||
is_strict: false,
|
||||
unique_sets: None,
|
||||
});
|
||||
let temp_cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(simple_table_rc));
|
||||
|
||||
Self {
|
||||
temp_cursor_id,
|
||||
table_cursor_id: cursor_id,
|
||||
table,
|
||||
null_data_reg: program.alloc_register(),
|
||||
rowid_reg: program.alloc_register(),
|
||||
finished_insert_loop: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize resources needed for the source operators (tables, joins, etc)
|
||||
pub fn init_loop(
|
||||
program: &mut ProgramBuilder,
|
||||
@@ -111,7 +161,12 @@ pub fn init_loop(
|
||||
aggregates: &mut [Aggregate],
|
||||
group_by: Option<&GroupBy>,
|
||||
mode: OperationMode,
|
||||
<<<<<<< HEAD
|
||||
where_clause: &[WhereTerm],
|
||||
||||||| parent of 400ce819 (modify loop functions to accomodate for ephemeral tables)
|
||||
=======
|
||||
ephemeral_ctx: Option<&EphemeralCtx>,
|
||||
>>>>>>> 400ce819 (modify loop functions to accomodate for ephemeral tables)
|
||||
) -> Result<()> {
|
||||
assert!(
|
||||
t_ctx.meta_left_joins.len() == tables.joined_tables().len(),
|
||||
@@ -172,7 +227,11 @@ pub fn init_loop(
|
||||
t_ctx.meta_left_joins[table_index] = Some(lj_metadata);
|
||||
}
|
||||
}
|
||||
let (table_cursor_id, index_cursor_id) = table.open_cursors(program, mode)?;
|
||||
let (table_cursor_id, index_cursor_id) = if ephemeral_ctx.is_some() {
|
||||
(None, None)
|
||||
} else {
|
||||
table.open_cursors(program, mode)?
|
||||
};
|
||||
match &table.op {
|
||||
Operation::Scan { index, .. } => match (mode, &table.table) {
|
||||
(OperationMode::SELECT, Table::BTree(btree)) => {
|
||||
@@ -229,18 +288,41 @@ pub fn init_loop(
|
||||
}
|
||||
(OperationMode::UPDATE, Table::BTree(btree)) => {
|
||||
let root_page = btree.root_page;
|
||||
program.emit_insn(Insn::OpenWrite {
|
||||
cursor_id: table_cursor_id
|
||||
.expect("table cursor is always opened in OperationMode::UPDATE"),
|
||||
root_page: root_page.into(),
|
||||
name: btree.name.clone(),
|
||||
});
|
||||
if let Some(index_cursor_id) = index_cursor_id {
|
||||
program.emit_insn(Insn::OpenWrite {
|
||||
cursor_id: index_cursor_id,
|
||||
root_page: index.as_ref().unwrap().root_page.into(),
|
||||
name: index.as_ref().unwrap().name.clone(),
|
||||
if let Some(ctx) = &ephemeral_ctx.filter(|ctx| !ctx.finished_insert_loop) {
|
||||
program.emit_insn(Insn::Null {
|
||||
dest: ctx.null_data_reg,
|
||||
dest_end: Some(ctx.rowid_reg),
|
||||
});
|
||||
|
||||
program.emit_insn(Insn::OpenEphemeral {
|
||||
cursor_id: ctx.temp_cursor_id,
|
||||
is_table: true,
|
||||
});
|
||||
|
||||
program.emit_insn(Insn::OpenRead {
|
||||
cursor_id: ctx.table_cursor_id,
|
||||
root_page: ctx.table.table.get_root_page(),
|
||||
});
|
||||
} else {
|
||||
program.emit_insn(Insn::OpenWrite {
|
||||
cursor_id: ephemeral_ctx.map_or_else(
|
||||
|| {
|
||||
table_cursor_id.expect(
|
||||
"table cursor is always opened in OperationMode::UPDATE",
|
||||
)
|
||||
},
|
||||
|ctx| ctx.table_cursor_id,
|
||||
),
|
||||
root_page: root_page.into(),
|
||||
name: btree.name.clone(),
|
||||
});
|
||||
if let Some(index_cursor_id) = index_cursor_id {
|
||||
program.emit_insn(Insn::OpenWrite {
|
||||
cursor_id: index_cursor_id,
|
||||
root_page: index.as_ref().unwrap().root_page.into(),
|
||||
name: index.as_ref().unwrap().name.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
(_, Table::Virtual(_)) => {
|
||||
@@ -261,12 +343,38 @@ 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");
|
||||
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(),
|
||||
});
|
||||
let table_cursor_id = ephemeral_ctx.map_or_else(
|
||||
|| {
|
||||
table_cursor_id.expect(
|
||||
"table cursor is always opened in OperationMode::DELETE or OperationMode::UPDATE",
|
||||
)
|
||||
},
|
||||
|ctx| ctx.table_cursor_id,
|
||||
);
|
||||
|
||||
if let Some(ctx) = &ephemeral_ctx.filter(|ctx| !ctx.finished_insert_loop) {
|
||||
program.emit_insn(Insn::Null {
|
||||
dest: ctx.null_data_reg,
|
||||
dest_end: Some(ctx.rowid_reg),
|
||||
});
|
||||
|
||||
program.emit_insn(Insn::OpenEphemeral {
|
||||
cursor_id: ctx.temp_cursor_id,
|
||||
is_table: true,
|
||||
});
|
||||
|
||||
program.emit_insn(Insn::OpenRead {
|
||||
cursor_id: ctx.table_cursor_id,
|
||||
root_page: ctx.table.table.get_root_page(),
|
||||
});
|
||||
} else {
|
||||
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,7 +465,7 @@ pub fn open_loop(
|
||||
table_references: &TableReferences,
|
||||
join_order: &[JoinOrderMember],
|
||||
predicates: &[WhereTerm],
|
||||
temp_cursor_id: Option<CursorID>,
|
||||
ephemeral_ctx: Option<&EphemeralCtx>,
|
||||
) -> Result<()> {
|
||||
for (join_index, join) in join_order.iter().enumerate() {
|
||||
let joined_table_index = join.original_idx;
|
||||
@@ -390,12 +498,21 @@ pub fn open_loop(
|
||||
Operation::Scan { iter_dir, .. } => {
|
||||
match &table.table {
|
||||
Table::BTree(_) => {
|
||||
let iteration_cursor_id = temp_cursor_id.unwrap_or_else(|| {
|
||||
index_cursor_id.unwrap_or_else(|| {
|
||||
table_cursor_id
|
||||
.expect("Either index or table cursor must be opened")
|
||||
let iteration_cursor_id = ephemeral_ctx
|
||||
.map(|ctx| {
|
||||
if ctx.finished_insert_loop {
|
||||
ctx.temp_cursor_id
|
||||
} else {
|
||||
ctx.table_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 {
|
||||
cursor_id: iteration_cursor_id,
|
||||
@@ -641,9 +758,21 @@ 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 = ephemeral_ctx
|
||||
.map(|ctx| {
|
||||
if ctx.finished_insert_loop {
|
||||
ctx.temp_cursor_id
|
||||
} else {
|
||||
ctx.table_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");
|
||||
};
|
||||
@@ -975,7 +1104,7 @@ pub fn close_loop(
|
||||
t_ctx: &mut TranslateCtx,
|
||||
tables: &TableReferences,
|
||||
join_order: &[JoinOrderMember],
|
||||
temp_cursor_id: Option<CursorID>,
|
||||
ephemeral_ctx: Option<&EphemeralCtx>,
|
||||
) -> Result<()> {
|
||||
// We close the loops for all tables in reverse order, i.e. innermost first.
|
||||
// OPEN t1
|
||||
@@ -1000,12 +1129,21 @@ pub fn close_loop(
|
||||
program.resolve_label(loop_labels.next, program.offset());
|
||||
match &table.table {
|
||||
Table::BTree(_) => {
|
||||
let iteration_cursor_id = temp_cursor_id.unwrap_or_else(|| {
|
||||
index_cursor_id.unwrap_or_else(|| {
|
||||
table_cursor_id
|
||||
.expect("Either index or table cursor must be opened")
|
||||
let iteration_cursor_id = ephemeral_ctx
|
||||
.map(|ctx| {
|
||||
if ctx.finished_insert_loop {
|
||||
ctx.temp_cursor_id
|
||||
} else {
|
||||
ctx.table_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 {
|
||||
cursor_id: iteration_cursor_id,
|
||||
@@ -1043,9 +1181,20 @@ 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 = ephemeral_ctx
|
||||
.map(|ctx| {
|
||||
if ctx.finished_insert_loop {
|
||||
ctx.temp_cursor_id
|
||||
} else {
|
||||
ctx.table_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 { .. }) {
|
||||
let iter_dir = match search {
|
||||
|
||||
@@ -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)?
|
||||
@@ -120,13 +120,13 @@ fn optimize_update_plan(plan: &mut UpdatePlan, _schema: &Schema) -> Result<()> {
|
||||
// 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,
|
||||
// )?;
|
||||
let _ = optimize_table_access(
|
||||
&mut plan.table_references,
|
||||
&schema.indexes,
|
||||
&mut plan.where_clause,
|
||||
&mut plan.order_by,
|
||||
&mut None,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -534,6 +534,7 @@ pub struct UpdatePlan {
|
||||
// whether the WHERE clause is always false
|
||||
pub contains_constant_false_condition: bool,
|
||||
pub indexes_to_update: Vec<Arc<Index>>,
|
||||
pub rowid_alias_used: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
|
||||
@@ -228,6 +228,16 @@ pub fn prepare_update_plan(
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
// 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
|
||||
});
|
||||
|
||||
Ok(Plan::Update(UpdatePlan {
|
||||
table_references,
|
||||
set_clauses,
|
||||
@@ -238,5 +248,6 @@ pub fn prepare_update_plan(
|
||||
offset,
|
||||
contains_constant_false_condition: false,
|
||||
indexes_to_update,
|
||||
rowid_alias_used,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -860,7 +860,7 @@ impl ImmutableRecord {
|
||||
}
|
||||
|
||||
pub fn from_registers<'a>(
|
||||
registers: impl IntoIterator<Item = &'a Register> + Clone,
|
||||
registers: impl IntoIterator<Item = &'a Register> + Copy,
|
||||
len: usize,
|
||||
) -> Self {
|
||||
let mut values = Vec::with_capacity(len);
|
||||
@@ -870,7 +870,7 @@ impl ImmutableRecord {
|
||||
|
||||
let mut serial_type_buf = [0; 9];
|
||||
// write serial types
|
||||
for value in registers.clone() {
|
||||
for value in registers {
|
||||
let value = value.get_owned_value();
|
||||
let serial_type = SerialType::from(value);
|
||||
let n = write_varint(&mut serial_type_buf[0..], serial_type.into());
|
||||
|
||||
Reference in New Issue
Block a user