Single source of truth for the timeseries PostgreSQL schema shared by the Python (TSAPI_SERIESDB_PG_PY) and C++ (TSAPI_POSTGRES_CPP) libraries. SQL seeded byte-for-byte from TSAPI_SERIESDB_PG_PY/tsapi_pgdb/sql on 2026-09-02. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
111 lines
5.2 KiB
SQL
111 lines
5.2 KiB
SQL
-- 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 $$;
|