Estado: ✅ Aceptada Fecha: 2026-05-28 Bloque: X2 (segundo sub-bloque del bloque X del roadmap) Bump on-disk: ninguno (X1 ya tenía el slot de timing en TriggerMeta)

🧭 Contexto

X1 (ADR-0029) entregó triggers AFTER con body de una sola sentencia. X2 cierra los dos huecos más visibles:

  1. BEFORE triggers: para validaciones que abortan la operación (vía error propagado) y para logs que precedan al write.
  2. Body multi-statement BEGIN ... END: para triggers que necesitan hacer más de una cosa (típico: log + denormalización + counter increment).

Lenguaje procedural completo (variables, IF/THEN, LOOP) y CREATE FUNCTION quedan para X3+.

💡 Decisión

1. BEFORE triggers habilitados

CREATE TRIGGER ... BEFORE ... ya no rebota — X1 lo rechazaba al CREATE-time con [GBY-4093]. Ahora se persiste y se dispara en los hooks BEFORE de exec_insert/exec_update/exec_delete.

Cómo se construyen NEW/OLD para BEFORE:

Evento NEW OLD
BEFORE INSERT User-stated cols + NULL para columnas no especificadas
BEFORE UPDATE OLD con las assignments evaluadas contra OLD (sin tocar disco) Snapshot del row antes del update
BEFORE DELETE Snapshot del row antes del delete

Limitación X2 importante: NEW es read-only desde el trigger BEFORE — no se puede mutar la fila desde el body. Esto significa que BEFORE en X2 NO sirve para “rellenar defaults” o “modificar la fila antes de escribir”; sirve para auditar pre-write o abortar (vía error propagado).

Aborto vía error: si el body de un BEFORE trigger falla (e.g. INSERT con PK duplicada en otra tabla), el error se propaga y el INSERT/UPDATE/DELETE original aborta — el wrap de transacción del caller hace rollback. Es la mecánica de “validación con BEFORE” hecha por composición.

2. Body multi-statement BEGIN ... END

Nueva sintaxis:

CREATE TRIGGER multi AFTER INSERT ON t FOR EACH ROW BEGIN
    INSERT INTO log_a (id) VALUES (NEW.id);
    INSERT INTO log_b (id) VALUES (NEW.id);
END;

Cada substatement se separa por ;. Pueden anidarse BEGIN ... END (depth tracking en parser), aunque en X2 no agrega expresividad (no hay scope, no hay control de flujo).

Cambios estructurales:

3. Reglas del body single-statement preservadas (back-compat con X1)

Triggers creados con X1 (body sin BEGIN/END) siguen funcionando idénticamente. El parser detecta:

📐 Códigos de error

Mismos que X1 — no se agregaron códigos nuevos. [GBY-4093] ahora cubre también:

🧪 Validación

Suite x2_* en tests/integration_test.rs (7 tests):

Plus se removió x1_before_rejected_in_release (la restricción que el test verificaba fue lifteada).

Suite total: 423/423 pass (cargo test --lib --tests).

🔭 Futuro (X3+)