diff --git a/core/translate/insert.rs b/core/translate/insert.rs index ed63b80b6..439328468 100644 --- a/core/translate/insert.rs +++ b/core/translate/insert.rs @@ -1,7 +1,8 @@ use std::num::NonZeroUsize; use std::sync::Arc; use turso_parser::ast::{ - self, Expr, InsertBody, OneSelect, QualifiedName, ResolveType, ResultColumn, Upsert, UpsertDo, + self, Expr, InsertBody, OneSelect, QualifiedName, ResolveType, ResultColumn, TriggerEvent, + TriggerTime, Upsert, UpsertDo, }; use crate::error::{ @@ -24,6 +25,7 @@ use crate::translate::plan::{ ColumnUsedMask, JoinedTable, Operation, ResultSetColumn, TableReferences, }; use crate::translate::planner::ROWID_STRS; +use crate::translate::trigger_exec::{fire_trigger, get_relevant_triggers_type_and_time}; use crate::translate::upsert::{ collect_set_clauses_for_upsert, emit_upsert, resolve_upsert_target, ResolvedUpsertTarget, }; @@ -45,6 +47,7 @@ use super::emitter::Resolver; use super::expr::{translate_expr, translate_expr_no_constant_opt, NoConstantOptReason}; use super::plan::QueryDestination; use super::select::translate_select; +use super::trigger_exec::{has_relevant_triggers_type_only, TriggerContext}; /// Validate anything with this insert statement that should throw an early parse error fn validate(table_name: &str, resolver: &Resolver, table: &Table) -> Result<()> { @@ -316,6 +319,40 @@ pub fn translate_insert( init_autoincrement(&mut program, &mut ctx, resolver)?; } + // Fire BEFORE INSERT triggers + // Build NEW registers: for rowid alias columns, use the rowid register; otherwise use column register + let new_registers: Vec = insertion + .col_mappings + .iter() + .map(|col_mapping| { + if col_mapping.column.is_rowid_alias() { + insertion.key_register() + } else { + col_mapping.register + } + }) + .chain(std::iter::once(insertion.key_register())) + .collect(); + + let relevant_before_triggers = get_relevant_triggers_type_and_time( + resolver.schema, + TriggerEvent::Insert, + TriggerTime::Before, + None, + &btree_table, + ); + let has_relevant_before_triggers = relevant_before_triggers.clone().count() > 0; + if has_relevant_before_triggers { + let trigger_ctx = TriggerContext::new( + btree_table.clone(), + Some(new_registers), + None, // No OLD for INSERT + ); + for trigger in relevant_before_triggers { + fire_trigger(&mut program, resolver, trigger, &trigger_ctx, connection)?; + } + } + if has_user_provided_rowid { let must_be_int_label = program.allocate_label(); @@ -427,6 +464,42 @@ pub fn translate_insert( table_name: table_name.to_string(), }); + // Fire AFTER INSERT triggers + let relevant_after_triggers = get_relevant_triggers_type_and_time( + resolver.schema, + TriggerEvent::Insert, + TriggerTime::After, + None, + &btree_table, + ); + let has_relevant_after_triggers = relevant_after_triggers.clone().count() > 0; + if has_relevant_after_triggers { + // Build NEW registers: for rowid alias columns, use the rowid register; otherwise use column register + let new_registers_after: Vec = insertion + .col_mappings + .iter() + .map(|col_mapping| { + if col_mapping.column.is_rowid_alias() { + insertion.key_register() + } else { + col_mapping.register + } + }) + .chain(std::iter::once(insertion.key_register())) + .collect(); + let trigger_ctx_after = + TriggerContext::new(btree_table.clone(), Some(new_registers_after), None); + for trigger in relevant_after_triggers { + fire_trigger( + &mut program, + resolver, + trigger, + &trigger_ctx_after, + connection, + )?; + } + } + if has_fks { // After the row is actually present, repair deferred counters for children referencing this NEW parent key. // For REPLACE: delete increments counters above; the insert path should try to repay @@ -1069,6 +1142,7 @@ fn bind_insert( } match on_conflict { ResolveType::Ignore => { + program.set_resolve_type(ResolveType::Ignore); upsert.replace(Box::new(ast::Upsert { do_clause: UpsertDo::Nothing, index: None, @@ -1163,6 +1237,14 @@ fn init_source_emission<'a>( ); } } + // Check if INSERT triggers exist - if so, we need to use ephemeral table for VALUES with more than one row + let has_insert_triggers = has_relevant_triggers_type_only( + resolver.schema, + TriggerEvent::Insert, + None, + ctx.table.as_ref(), + ); + let (num_values, cursor_id) = match body { InsertBody::Select(select, _) => { // Simple common case of INSERT INTO VALUES (...) without compounds. @@ -1221,7 +1303,7 @@ fn init_source_emission<'a>( ** of the tables being read by the SELECT statement. Also use a ** temp table in the case of row triggers. */ - if program.is_table_open(table) { + if program.is_table_open(table) || has_insert_triggers { let temp_cursor_id = program.alloc_cursor_id(CursorType::BTreeTable(ctx.table.clone())); ctx.temp_table_ctx = Some(TempTableCtx {