mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-23 08:55:40 +01:00
triggers: add translation logic for INSERT triggers
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user