From 9048ad398bf23bbc823cc3054101cb8a10e5f1b9 Mon Sep 17 00:00:00 2001 From: pedrocarlo Date: Sat, 14 Jun 2025 14:55:05 -0300 Subject: [PATCH] modify loop functions to accomodate for ephemeral tables --- core/translate/emitter.rs | 274 ++++++++++++++++++++------------ core/translate/main_loop.rs | 227 +++++++++++++++++++++----- core/translate/optimizer/mod.rs | 16 +- core/translate/plan.rs | 1 + core/translate/update.rs | 11 ++ core/types.rs | 4 +- 6 files changed, 380 insertions(+), 153 deletions(-) diff --git a/core/translate/emitter.rs b/core/translate/emitter.rs index 66fa7ef4c..19d9fb05a 100644 --- a/core/translate/emitter.rs +++ b/core/translate/emitter.rs @@ -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, + 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(()) } diff --git a/core/translate/main_loop.rs b/core/translate/main_loop.rs index cb27468b4..e5cd8b899 100644 --- a/core/translate/main_loop.rs +++ b/core/translate/main_loop.rs @@ -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, + 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, + 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 { diff --git a/core/translate/optimizer/mod.rs b/core/translate/optimizer/mod.rs index 999230d2f..7f2d4aec6 100644 --- a/core/translate/optimizer/mod.rs +++ b/core/translate/optimizer/mod.rs @@ -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(()) } diff --git a/core/translate/plan.rs b/core/translate/plan.rs index e5f8af284..c0e4cc95e 100644 --- a/core/translate/plan.rs +++ b/core/translate/plan.rs @@ -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>, + pub rowid_alias_used: bool, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] diff --git a/core/translate/update.rs b/core/translate/update.rs index 89f0de71c..3469f3af4 100644 --- a/core/translate/update.rs +++ b/core/translate/update.rs @@ -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, })) } diff --git a/core/types.rs b/core/types.rs index 2d3eddae6..d20ec0ac2 100644 --- a/core/types.rs +++ b/core/types.rs @@ -860,7 +860,7 @@ impl ImmutableRecord { } pub fn from_registers<'a>( - registers: impl IntoIterator + Clone, + registers: impl IntoIterator + 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());