Files
paliad/internal/db/migrations/092_drop_event_deadlines_tables.up.sql
mAi 29a6b58747 refactor(t-paliad-199): Slice 9 follow-up A — drop legacy event_deadlines tables
EventDeadlineService.Calculate now reads source rows from
paliad.deadline_rules directly (WHERE trigger_event_id IS NOT NULL),
joining via UUID instead of title_de string. The legacy SELECTs against
paliad.event_deadlines + paliad.event_deadline_rule_codes are gone.

Migration 092:
- Snapshots both legacy tables into _pre_092 audit anchors.
- Adds paliad.deadline_rules.rule_codes text[] and backfills the 72
  multi-code citations from event_deadline_rule_codes via the
  sequence_order = 1000 + ed.id convention from mig 085 (70 of 77
  Pipeline-C deadlines carry codes; 7 are codeless).
- Hard assertion ties source-junction-row count to backfilled
  text[]-element count — any sequence_order mismatch aborts the drop.
- Drops the mig 086 read-only trigger (orphan once event_deadlines
  goes away).
- Drops paliad.event_deadlines + paliad.event_deadline_rule_codes.
- Final assertion: >=77 active deadline_rules with trigger_event_id
  NOT NULL — Slice 3 corpus must not have collapsed.
- audit_reason wrapper at top so the deadline_rules UPDATE row-trigger
  records the reason in deadline_rule_audit.

Verified via BEGIN..ROLLBACK against the live paliad DB: 72 codes
backfilled into 70 rule_codes arrays, multi-code rules (RoP.029.a +
RoP.030 for ed_id=6) preserve their ordering, composite rules
(combine_op=max) remain intact, both tables drop cleanly, all
assertions pass.

Parity test rebound to deadline_rules — independent computation still
re-runs applyDuration against raw column values for date/composite
parity. EventDeadlineResult.ID stays int64 via the sequence_order -
1000 convention so the public /api/tools/event-deadlines wire shape
is unchanged.
2026-05-16 01:17:23 +02:00

196 lines
9.2 KiB
SQL

-- t-paliad-199 / Fristen Phase 3 Slice 9 follow-up A — drop the legacy
-- Pipeline-C source tables (paliad.event_deadlines +
-- paliad.event_deadline_rule_codes) and the read-only trigger from
-- mig 086, now that EventDeadlineService.Calculate has been rewritten
-- to read from paliad.deadline_rules.
--
-- Lorenz's Slice 9 (t-paliad-195) deferred this drop because the
-- legacy service still SELECTed event_deadlines.duration_value /
-- duration_unit / timing / notes / alt_* / combine_op. Slice 9
-- follow-up A refactors the service onto deadline_rules (the unified
-- source-of-truth since Slice 3 / mig 085) and frees us to remove the
-- old tables.
--
-- Sequencing — every step in this single migration is required for the
-- drop to be safe:
--
-- 1. Snapshot both source tables into paliad.event_deadlines_pre_092
-- + paliad.event_deadline_rule_codes_pre_092 (CREATE TABLE IF NOT
-- EXISTS — idempotent re-run). The snapshots persist after the
-- drop as audit anchors; the down migration restores from them.
-- 2. ADD COLUMN rule_codes text[] to paliad.deadline_rules and
-- backfill from paliad.event_deadline_rule_codes. Pipeline-C
-- deadlines carry multi-code rules (e.g. R.198 / R.213 carry
-- [RoP.029.a, RoP.030]) which don't fit deadline_rules.rule_code
-- (singular text); mig 085 left rule_code NULL on the 77
-- Pipeline-C rows. Without this backfill the drop would silently
-- lose 72 RoP citations.
-- 3. Hard assertion: every event_deadline_rule_codes row resolves to
-- a deadline_rules row via the sequence_order = 1000 +
-- event_deadlines.id convention from mig 085. If any row didn't
-- land, fail loudly before dropping the source.
-- 4. DROP TRIGGER + FUNCTION from mig 086 — orphan once the table is
-- gone.
-- 5. DROP TABLE paliad.event_deadline_rule_codes (FK side first).
-- 6. DROP TABLE paliad.event_deadlines.
-- 7. Final assertion: paliad.deadline_rules still carries >=77 active
-- rows with trigger_event_id IS NOT NULL (the Slice 3 corpus must
-- not have collapsed).
--
-- audit_reason wrapper at top — the mig 079 trigger on
-- paliad.deadline_rules logs every row-level edit. The ALTER TABLE +
-- UPDATE on rule_codes fires through that trigger, so the reason
-- persists in paliad.deadline_rule_audit for forever-grade audit.
SELECT set_config(
'paliad.audit_reason',
'mig 092: drop paliad.event_deadlines + event_deadline_rule_codes after backfilling rule_codes into deadline_rules (t-paliad-199, Slice 9 follow-up A, design §3.E)',
true);
-- =============================================================================
-- 1. Backup snapshots — full row copies so the down migration can
-- rebuild both tables byte-identically. CREATE TABLE IF NOT EXISTS
-- keeps the migration idempotent across reapplications; if the
-- snapshot already exists from a prior aborted run, we re-use it.
-- =============================================================================
CREATE TABLE IF NOT EXISTS paliad.event_deadlines_pre_092 AS
SELECT *, now() AS snapshotted_at
FROM paliad.event_deadlines;
COMMENT ON TABLE paliad.event_deadlines_pre_092 IS
'Snapshot of paliad.event_deadlines before mig 092 dropped it. '
'Source-of-truth for the down migration; persists post-drop as the '
'permanent audit record of the 77 Pipeline-C source rows that '
'seeded paliad.deadline_rules via mig 085. Drop with a focused '
'follow-up after Slice 9 is verified in prod (pair with '
'paliad.deadline_rules_pre_091 cleanup).';
CREATE TABLE IF NOT EXISTS paliad.event_deadline_rule_codes_pre_092 AS
SELECT *, now() AS snapshotted_at
FROM paliad.event_deadline_rule_codes;
COMMENT ON TABLE paliad.event_deadline_rule_codes_pre_092 IS
'Snapshot of paliad.event_deadline_rule_codes before mig 092 dropped '
'it. Restored by the down migration; persists post-drop as the '
'permanent audit record of the legacy RoP citations attached to '
'Pipeline-C deadlines (72 rows across 70 of 77 deadlines).';
-- =============================================================================
-- 2. Add paliad.deadline_rules.rule_codes (text[]) and backfill it for
-- the 77 Pipeline-C rules. Mig 085 set rule_code = NULL on every
-- Pipeline-C row because deadline_rules.rule_code is singular and
-- Pipeline-C deadlines can carry multiple citations. rule_codes
-- holds the array form. Pipeline-A rules keep NULL here and continue
-- using rule_code; this column is a Pipeline-C-only field today.
-- =============================================================================
ALTER TABLE paliad.deadline_rules
ADD COLUMN IF NOT EXISTS rule_codes text[];
COMMENT ON COLUMN paliad.deadline_rules.rule_codes IS
'Array of legal-rule citations attached to this deadline, in '
'render order. Pipeline-C rules (event-rooted, trigger_event_id IS '
'NOT NULL) populate this column from the legacy '
'paliad.event_deadline_rule_codes junction (mig 092 backfill); '
'Pipeline-A rules use the singular rule_code column instead. NULL '
'on Pipeline-A rules + on the 7 Pipeline-C deadlines that had no '
'junction rows pre-mig.';
-- Aggregate junction rows into a text[] sorted by (sort_order,
-- rule_code) — matches the legacy ORDER BY contract that
-- EventDeadlineService.loadRuleCodes used.
--
-- Join key: the sequence_order = 1000 + event_deadlines.id convention
-- mig 085 anchored. Every active event_deadlines.id has a corresponding
-- deadline_rules row at sequence_order = 1000 + id; mig 085's hard
-- assertion guarantees that.
WITH agg AS (
SELECT event_deadline_id,
array_agg(rule_code ORDER BY sort_order, rule_code) AS codes
FROM paliad.event_deadline_rule_codes
GROUP BY event_deadline_id
)
UPDATE paliad.deadline_rules dr
SET rule_codes = agg.codes
FROM agg
WHERE dr.trigger_event_id IS NOT NULL
AND dr.sequence_order = 1000 + agg.event_deadline_id
AND dr.rule_codes IS DISTINCT FROM agg.codes;
-- =============================================================================
-- 3. Hard assertion: every junction row landed on a deadline_rules row.
-- Sums elements across all rule_codes arrays — if the count differs
-- from the source junction count, some event_deadline_id failed to
-- match any deadline_rules row (sequence_order convention broken).
-- Fail loudly here BEFORE dropping the source.
-- =============================================================================
DO $$
DECLARE
n_codes_src int;
n_codes_target int;
BEGIN
SELECT count(*) INTO n_codes_src
FROM paliad.event_deadline_rule_codes;
SELECT COALESCE(SUM(array_length(rule_codes, 1)), 0) INTO n_codes_target
FROM paliad.deadline_rules
WHERE rule_codes IS NOT NULL;
RAISE NOTICE 'mig 092: junction rows=%, backfilled rule_codes elements=%',
n_codes_src, n_codes_target;
IF n_codes_target < n_codes_src THEN
RAISE EXCEPTION 'mig 092: rule_codes backfill missed % junction rows '
'(source=%, target=%) — sequence_order = 1000 + ed.id '
'convention broken? Aborting before drop.',
n_codes_src - n_codes_target, n_codes_src, n_codes_target;
END IF;
END $$;
-- =============================================================================
-- 4. Drop the read-only trigger + function from mig 086. They're orphan
-- once paliad.event_deadlines goes away — explicit drop documents
-- that the wrapper's job is done, and keeps the symmetric reverse in
-- the down migration cleanly readable.
-- =============================================================================
DROP TRIGGER IF EXISTS event_deadlines_readonly ON paliad.event_deadlines;
DROP FUNCTION IF EXISTS paliad.event_deadlines_readonly_trigger();
-- =============================================================================
-- 5. Drop the legacy tables. Order: junction first (it has a FK to
-- event_deadlines), then the parent. Explicit ordering is clearer
-- than relying on CASCADE and mirrors the down migration's CREATE
-- sequence.
-- =============================================================================
DROP TABLE IF EXISTS paliad.event_deadline_rule_codes;
DROP TABLE IF EXISTS paliad.event_deadlines;
-- =============================================================================
-- 6. Final assertion: the unified Pipeline-C corpus is still intact.
-- Mig 085 moved 77 active rows; future hand-edited Pipeline-C rules
-- can only raise the count. A drop below 77 means the upstream
-- deadline_rules data was clobbered while this migration ran and
-- the deploy must abort.
-- =============================================================================
DO $$
DECLARE
n_unified int;
BEGIN
SELECT count(*) INTO n_unified
FROM paliad.deadline_rules
WHERE trigger_event_id IS NOT NULL AND is_active = true;
RAISE NOTICE 'mig 092: post-drop Pipeline-C rule count = %', n_unified;
IF n_unified < 77 THEN
RAISE EXCEPTION 'mig 092: Pipeline-C corpus collapsed — expected >=77 '
'active deadline_rules with trigger_event_id IS NOT NULL, got %',
n_unified;
END IF;
END $$;