Execute a list of triggers with the given bindings. Evaluates WHEN clauses, parses trigger bodies, and executes each matching trigger body through the statement executor. Handles SECURITY DEFINER: when a trigger has `security = Definer`, the executor runs with the trigger owner's identity instead of the caller's. Tenant boundary is enforced — DEFINER identity always uses the trigger's tenant.
(
state: &SharedState,
identity: &AuthenticatedIdentity,
tenant_id: TenantId,
collection: &str,
triggers: &[StoredTrigger],
bindings: &RowBindings,
cascade_depth: u32,
)
| 37 | /// the executor runs with the trigger owner's identity instead of the caller's. |
| 38 | /// Tenant boundary is enforced — DEFINER identity always uses the trigger's tenant. |
| 39 | pub async fn fire_triggers( |
| 40 | state: &SharedState, |
| 41 | identity: &AuthenticatedIdentity, |
| 42 | tenant_id: TenantId, |
| 43 | collection: &str, |
| 44 | triggers: &[StoredTrigger], |
| 45 | bindings: &RowBindings, |
| 46 | cascade_depth: u32, |
| 47 | ) -> crate::Result<()> { |
| 48 | for trigger in triggers { |
| 49 | if let Some(ref when_cond) = trigger.when_condition { |
| 50 | let bound_cond = bindings.substitute(when_cond); |
| 51 | if !evaluate_simple_condition(&bound_cond) { |
| 52 | continue; |
| 53 | } |
| 54 | } |
| 55 | |
| 56 | let block = match state.block_cache.get_or_parse(&trigger.body_sql) { |
| 57 | Ok(b) => b, |
| 58 | Err(e) => { |
| 59 | warn!( |
| 60 | trigger = %trigger.name, |
| 61 | error = %e, |
| 62 | "failed to parse trigger body, skipping" |
| 63 | ); |
| 64 | continue; |
| 65 | } |
| 66 | }; |
| 67 | |
| 68 | // Resolve effective identity based on security mode. |
| 69 | let effective_identity = resolve_trigger_identity(trigger, identity, tenant_id); |
| 70 | |
| 71 | // Audit log the trigger invocation with effective identity. |
| 72 | info!( |
| 73 | trigger = %trigger.name, |
| 74 | collection = collection, |
| 75 | timing = trigger.timing.as_str(), |
| 76 | security = trigger.security.as_str(), |
| 77 | caller = %identity.username, |
| 78 | effective_user = %effective_identity.username, |
| 79 | cascade_depth = cascade_depth, |
| 80 | "trigger invoked" |
| 81 | ); |
| 82 | |
| 83 | let executor = StatementExecutor::with_source( |
| 84 | state, |
| 85 | effective_identity, |
| 86 | tenant_id, |
| 87 | cascade_depth + 1, |
| 88 | crate::event::EventSource::Trigger, |
| 89 | ); |
| 90 | |
| 91 | if let Err(e) = executor.execute_block(&block, bindings).await { |
| 92 | return Err(crate::Error::BadRequest { |
| 93 | detail: format!( |
| 94 | "trigger '{}' on '{}' failed: {}", |
| 95 | trigger.name, collection, e |
| 96 | ), |
no test coverage detected