46 lines
1.4 KiB
PL/PgSQL
46 lines
1.4 KiB
PL/PgSQL
-- 003_outbox_state_machine.sql
|
|
-- Enforce state machine constraints and valid transitions
|
|
-- Prevents invalid status transitions (e.g., FINALIZED -> QUEUED)
|
|
|
|
ALTER TABLE dual_ledger_outbox
|
|
ADD CONSTRAINT dual_ledger_outbox_status_check
|
|
CHECK (status IN ('QUEUED','SENT','ACKED','FINALIZED','FAILED'));
|
|
|
|
CREATE OR REPLACE FUNCTION enforce_outbox_status_transition()
|
|
RETURNS trigger AS $$
|
|
DECLARE
|
|
allowed boolean := false;
|
|
BEGIN
|
|
-- No-op if status unchanged
|
|
IF OLD.status = NEW.status THEN
|
|
RETURN NEW;
|
|
END IF;
|
|
|
|
-- Allowed transitions:
|
|
-- QUEUED -> SENT | FAILED
|
|
-- SENT -> ACKED | FAILED
|
|
-- ACKED -> FINALIZED | FAILED
|
|
-- FAILED -> QUEUED (retry) | FAILED (no change)
|
|
IF OLD.status = 'QUEUED' AND NEW.status IN ('SENT','FAILED') THEN
|
|
allowed := true;
|
|
ELSIF OLD.status = 'SENT' AND NEW.status IN ('ACKED','FAILED') THEN
|
|
allowed := true;
|
|
ELSIF OLD.status = 'ACKED' AND NEW.status IN ('FINALIZED','FAILED') THEN
|
|
allowed := true;
|
|
ELSIF OLD.status = 'FAILED' AND NEW.status IN ('QUEUED','FAILED') THEN
|
|
allowed := true;
|
|
END IF;
|
|
|
|
IF NOT allowed THEN
|
|
RAISE EXCEPTION 'Invalid outbox transition: % -> %', OLD.status, NEW.status;
|
|
END IF;
|
|
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
DROP TRIGGER IF EXISTS dual_ledger_outbox_status_transition ON dual_ledger_outbox;
|
|
CREATE TRIGGER dual_ledger_outbox_status_transition
|
|
BEFORE UPDATE ON dual_ledger_outbox
|
|
FOR EACH ROW EXECUTE FUNCTION enforce_outbox_status_transition();
|