yau-plant-assistant/db/008_ci_item_rename.sql
Claude 6a64c64588 Add 008 - migrate an existing database onto CI Server item keys
The seed rename in 3d19b18 changed the repository. It could not change a
database that already exists: 001_schema.sql is CREATE TABLE IF NOT
EXISTS, so it will not add ci_station, ci_point or poll_group to a live
historian_items, and deploy.sh only ever upserts - nothing deletes. A
plain reload would have failed on the missing columns, and had it not, it
would have left 45 retired keys beside their 45 replacements with two
foreign keys still pointing at the old ones.

The migration adds the columns, carries scada_point across into ci_point,
drops it, then re-keys: it captures the old-to-new mapping BEFORE touching
anything, inserts a replacement tags row per retired key, repoints
alarm_bits and historian_items, checks nothing still references a retired
key, and only then deletes.

The mapping is captured first because historian_items.tag_id is itself
about to change - reading the mapping through it afterwards finds nothing,
which would have left alarm_bits pointing at deleted rows. A silently
unmapped alarm bit is how an alarm count becomes wrong, so there is an
explicit check that every alarm_bits key has somewhere to go, and the
migration stops before touching anything if one does not.

Applied to lin001 2026-09-01 after a pg_dump of the three tables to
~/ai/backups/ and a dry run inside a rolled-back transaction. Result: 45
retired keys mapped, 6 alarm_bits rows repointed, 45 tags rows deleted,
65 tags before and after, ci_point populated on all 49 items.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 20:19:00 +10:00

89 lines
4.1 KiB
PL/PgSQL

-- 008 - Re-key the seed on CI Server item names
--
-- WHY. The point list delivered in August named the published values
-- PS_STN_WET_WELL_LEVEL and the like. Those were never CI Server names: they
-- came from the PLC-side generator, and the CI Server generator ignored them.
-- A corrected delivery on 2026-09-01 is keyed on the CI Server ITEM each point
-- feeds - AID.WRPS.STN.LEVEL - which is what the historian is keyed on too.
--
-- WHY A MIGRATION AND NOT JUST A RELOAD. 001_schema.sql is CREATE TABLE IF NOT
-- EXISTS, so it will not add the three new columns to a table that already
-- exists, and deploy.sh only ever upserts - nothing deletes. Re-running the
-- seed load on its own would fail on the missing columns and, if it did not,
-- would leave 45 retired keys sitting beside their 45 replacements with two
-- foreign keys still pointing at the old ones.
--
-- SAFE TO RE-RUN. Every step is guarded. On a fresh database created by
-- 001_schema.sql this does nothing at all.
BEGIN;
-- 1. The column change 001_schema.sql cannot make on an existing table.
ALTER TABLE historian_items ADD COLUMN IF NOT EXISTS ci_station TEXT;
ALTER TABLE historian_items ADD COLUMN IF NOT EXISTS ci_point TEXT;
ALTER TABLE historian_items ADD COLUMN IF NOT EXISTS poll_group TEXT;
-- Carry the old value across before dropping it, so the column is not simply
-- lost if the seed reload is not run immediately after this migration.
UPDATE historian_items SET ci_point = scada_point
WHERE ci_point IS NULL
AND EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'historian_items' AND column_name = 'scada_point');
ALTER TABLE historian_items DROP COLUMN IF EXISTS scada_point;
-- 2. Capture the mapping BEFORE anything is updated. historian_items.tag_id is
-- itself about to change, so a later lookup through it finds nothing - an
-- ordering bug that would have left alarm_bits pointing at deleted rows.
CREATE TEMP TABLE _rename ON COMMIT DROP AS
SELECT tag_id AS old_tag, item_name AS new_tag
FROM historian_items
WHERE tag_id LIKE 'PS!_%' ESCAPE '!';
-- Every alarm_bits key must be in that mapping, or we stop before touching
-- anything. A silently unmapped alarm is how an alarm count becomes wrong.
DO $$
DECLARE unmapped INT;
BEGIN
SELECT count(*) INTO unmapped
FROM alarm_bits a
WHERE a.tag_id LIKE 'PS!_%' ESCAPE '!'
AND NOT EXISTS (SELECT 1 FROM _rename r WHERE r.old_tag = a.tag_id);
IF unmapped > 0 THEN
RAISE EXCEPTION '% alarm_bits rows have no item to move to', unmapped;
END IF;
END $$;
-- 3. Give every retired key its replacement row, copying the old row's own
-- facts. The seed reload that follows overwrites these with the delivered
-- values; they exist here so the foreign keys below have somewhere to land.
INSERT INTO tags (tag_id, equipment_id, display_name, aliases, signal_type,
engineering_unit, range_low, range_high, alarm_setpoint_hi,
alarm_setpoint_lo, trip_setpoint, description)
SELECT r.new_tag, t.equipment_id, t.display_name, t.aliases, t.signal_type,
t.engineering_unit, t.range_low, t.range_high, t.alarm_setpoint_hi,
t.alarm_setpoint_lo, t.trip_setpoint, t.description
FROM _rename r JOIN tags t ON t.tag_id = r.old_tag
ON CONFLICT (tag_id) DO NOTHING;
-- 4. Repoint both foreign keys, alarm_bits first so it still reads the mapping.
UPDATE alarm_bits a SET tag_id = r.new_tag
FROM _rename r WHERE a.tag_id = r.old_tag;
UPDATE historian_items h SET tag_id = r.new_tag
FROM _rename r WHERE h.tag_id = r.old_tag;
-- 5. Anything still referencing a retired key is a defect, not a leftover.
DO $$
DECLARE orphans INT;
BEGIN
SELECT count(*) INTO orphans FROM alarm_bits WHERE tag_id LIKE 'PS!_%' ESCAPE '!';
IF orphans > 0 THEN RAISE EXCEPTION 'alarm_bits still holds % retired keys', orphans; END IF;
SELECT count(*) INTO orphans FROM historian_items WHERE tag_id LIKE 'PS!_%' ESCAPE '!';
IF orphans > 0 THEN RAISE EXCEPTION 'historian_items still holds % retired keys', orphans; END IF;
END $$;
-- 6. Now unreferenced, so they can go.
DELETE FROM tags WHERE tag_id LIKE 'PS!_%' ESCAPE '!';
COMMIT;