-- 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;