-- Migration: add identity constraints to the series registry and reference tables -- -- meta.series had no primary key and no unique constraint, so two rows could -- describe the same series -- and copy_series() actively created that, by -- copying the source's table_id and db_schema. meta.currencies, -- meta.security_types and meta.timezones had no key on their natural -- identifier either, while add_currency()/add_security_type()/add_timezone() -- all catch IntegrityError and promise an "already exists" error that no -- constraint could raise. -- -- These are small registries, not timeseries data. A btree key here costs -- almost nothing, and this is unrelated to the deliberate decision NOT to -- constrain series timestamps, where BRIN is used precisely because a btree -- would be prohibitive at millions of rows per series. -- -- New databases get these from 04_indexes.sql. Run this against any database -- initialised before the fix. -- -- Safe to run more than once: each constraint is added only if absent. -- -- IMPORTANT: this ABORTS if duplicate rows already exist, printing what -- collides. It does not guess which row to keep -- that is a data decision. -- Resolve the duplicates, then run it again. DO $$ DECLARE dup_count integer; dup_list text; BEGIN ---------------------------------------------------------------------- -- Pre-flight: refuse to run if any natural key already collides. ---------------------------------------------------------------------- SELECT count(*), string_agg(format('%s.%s', db_schema, table_name), ', ') INTO dup_count, dup_list FROM (SELECT db_schema, table_name FROM meta.series GROUP BY db_schema, table_name HAVING count(*) > 1) d; IF dup_count > 0 THEN RAISE EXCEPTION 'meta.series has % duplicated (db_schema, table_name): %. ' 'Remove the extra rows, then re-run this migration.', dup_count, dup_list; END IF; SELECT count(*), string_agg(table_id::text, ', ') INTO dup_count, dup_list FROM (SELECT table_id FROM meta.series GROUP BY table_id HAVING count(*) > 1) d; IF dup_count > 0 THEN RAISE EXCEPTION 'meta.series has % duplicated table_id: %. Most likely created by ' 'copy_series() before it was fixed. Reassign with ' 'UPDATE meta.series SET table_id = nextval(''meta._table_meta__table_id_seq'') ' 'WHERE ... , then re-run this migration.', dup_count, dup_list; END IF; SELECT count(*), string_agg(code, ', ') INTO dup_count, dup_list FROM (SELECT code FROM meta.currencies GROUP BY code HAVING count(*) > 1) d; IF dup_count > 0 THEN RAISE EXCEPTION 'meta.currencies has % duplicated code: %', dup_count, dup_list; END IF; SELECT count(*), string_agg(code, ', ') INTO dup_count, dup_list FROM (SELECT code FROM meta.security_types GROUP BY code HAVING count(*) > 1) d; IF dup_count > 0 THEN RAISE EXCEPTION 'meta.security_types has % duplicated code: %', dup_count, dup_list; END IF; SELECT count(*), string_agg(iana_name, ', ') INTO dup_count, dup_list FROM (SELECT iana_name FROM meta.timezones GROUP BY iana_name HAVING count(*) > 1) d; IF dup_count > 0 THEN RAISE EXCEPTION 'meta.timezones has % duplicated iana_name: %', dup_count, dup_list; END IF; ---------------------------------------------------------------------- -- Add the constraints, skipping any that already exist. ---------------------------------------------------------------------- IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'series_pkey' AND conrelid = 'meta.series'::regclass) THEN ALTER TABLE meta.series ADD CONSTRAINT series_pkey PRIMARY KEY (table_id); RAISE NOTICE 'added meta.series primary key on (table_id)'; END IF; IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'series_schema_table_key' AND conrelid = 'meta.series'::regclass) THEN ALTER TABLE meta.series ADD CONSTRAINT series_schema_table_key UNIQUE (db_schema, table_name); RAISE NOTICE 'added meta.series unique constraint on (db_schema, table_name)'; END IF; IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'currencies_pkey' AND conrelid = 'meta.currencies'::regclass) THEN ALTER TABLE meta.currencies ADD CONSTRAINT currencies_pkey PRIMARY KEY (code); RAISE NOTICE 'added meta.currencies primary key on (code)'; END IF; IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'security_types_pkey' AND conrelid = 'meta.security_types'::regclass) THEN ALTER TABLE meta.security_types ADD CONSTRAINT security_types_pkey PRIMARY KEY (code); RAISE NOTICE 'added meta.security_types primary key on (code)'; END IF; IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'timezones_pkey' AND conrelid = 'meta.timezones'::regclass) THEN ALTER TABLE meta.timezones ADD CONSTRAINT timezones_pkey PRIMARY KEY (iana_name); RAISE NOTICE 'added meta.timezones primary key on (iana_name)'; END IF; END $$;