triggers: add translation logic for INSERT triggers

This commit is contained in:
Jussi Saurio
2025-11-18 12:50:41 +02:00
parent e28301dc2e
commit 70267f8710

View File

@@ -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<usize> = 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<usize> = 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 <table> 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 {