Canonical TSAPI series DB schema v1.0.0
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>
This commit is contained in:
commit
eed06559c0
17 changed files with 2080 additions and 0 deletions
48
sql/migrate/migrate_drop_series_if_exists.sql
Normal file
48
sql/migrate/migrate_drop_series_if_exists.sql
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
-- Migration: repair meta.drop_series_if_exists()
|
||||
--
|
||||
-- The function dropped a registered series without setting
|
||||
-- application.dropping_series, so the prevent_direct_drop event trigger
|
||||
-- rejected its own DROP:
|
||||
--
|
||||
-- Table series.X is registered in series and can only be dropped
|
||||
-- using TimeseriesDB.drop_series()
|
||||
--
|
||||
-- Unlike meta.drop_series() and meta.drop_all_series(), which set the flag
|
||||
-- correctly, this one never did -- so it always failed on exactly the case it
|
||||
-- exists to handle. The Python API does not call it, which is why the defect
|
||||
-- went unnoticed.
|
||||
--
|
||||
-- New databases pick this up from 05_procedures.sql. Run this against any
|
||||
-- database initialised before the fix.
|
||||
--
|
||||
-- Safe to run more than once (CREATE OR REPLACE).
|
||||
|
||||
CREATE OR REPLACE FUNCTION meta.drop_series_if_exists(_table_name text, _schema_name text DEFAULT 'series'::text)
|
||||
RETURNS void
|
||||
LANGUAGE plpgsql
|
||||
AS $function$
|
||||
BEGIN
|
||||
-- Check if the series exists in series table
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM meta.series
|
||||
WHERE "table_name" = _table_name
|
||||
AND "db_schema" = _schema_name
|
||||
) THEN
|
||||
-- Signal the prevent_direct_drop event trigger that this DROP is
|
||||
-- coming from a sanctioned procedure. Without it the trigger rejects
|
||||
-- the statement below, because the table is registered in meta.series.
|
||||
-- Local to the transaction, so it cannot leak to later statements.
|
||||
PERFORM set_config('application.dropping_series', 'true', true);
|
||||
|
||||
-- Drop the table
|
||||
EXECUTE format('DROP TABLE IF EXISTS %I.%I', _schema_name, _table_name);
|
||||
|
||||
-- Remove from series table
|
||||
DELETE FROM meta.series
|
||||
WHERE "table_name" = _table_name
|
||||
AND "db_schema" = _schema_name;
|
||||
|
||||
PERFORM set_config('application.dropping_series', 'false', true);
|
||||
END IF;
|
||||
END;
|
||||
$function$;
|
||||
99
sql/migrate/migrate_instrument_metadata.sql
Normal file
99
sql/migrate/migrate_instrument_metadata.sql
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
-- Migration: Add Instrument Metadata Columns
|
||||
-- Date: 2026-02-11
|
||||
-- Description: Adds classification and trading specification columns to meta.exchanges and meta.instruments
|
||||
|
||||
-- ============================================================================
|
||||
-- 1. Add columns to meta.exchanges
|
||||
-- ============================================================================
|
||||
|
||||
-- Trading specifications
|
||||
ALTER TABLE meta.exchanges ADD COLUMN IF NOT EXISTS default_tick_size DOUBLE PRECISION;
|
||||
ALTER TABLE meta.exchanges ADD COLUMN IF NOT EXISTS default_tick_value DOUBLE PRECISION;
|
||||
ALTER TABLE meta.exchanges ADD COLUMN IF NOT EXISTS default_extended_hours BOOLEAN DEFAULT false;
|
||||
|
||||
-- Geographic info
|
||||
ALTER TABLE meta.exchanges ADD COLUMN IF NOT EXISTS country VARCHAR(3);
|
||||
|
||||
COMMENT ON COLUMN meta.exchanges.default_tick_size IS 'Default minimum price increment (e.g., 0.01 for stocks)';
|
||||
COMMENT ON COLUMN meta.exchanges.default_tick_value IS 'Default value per tick in currency (0.01 for stocks = 1 cent per share)';
|
||||
COMMENT ON COLUMN meta.exchanges.default_extended_hours IS 'Default for whether instruments support pre/post market trading';
|
||||
COMMENT ON COLUMN meta.exchanges.country IS 'ISO 3166-1 alpha-3 country code (USA, GBR, JPN)';
|
||||
|
||||
-- ============================================================================
|
||||
-- 2. Add columns to meta.instruments
|
||||
-- ============================================================================
|
||||
|
||||
-- Basic info
|
||||
ALTER TABLE meta.instruments ADD COLUMN IF NOT EXISTS description VARCHAR(255);
|
||||
ALTER TABLE meta.instruments ADD COLUMN IF NOT EXISTS country VARCHAR(3);
|
||||
|
||||
-- Trading specifications
|
||||
ALTER TABLE meta.instruments ADD COLUMN IF NOT EXISTS extended_hours BOOLEAN;
|
||||
|
||||
-- Company/Fund classification
|
||||
ALTER TABLE meta.instruments ADD COLUMN IF NOT EXISTS sector VARCHAR(50);
|
||||
ALTER TABLE meta.instruments ADD COLUMN IF NOT EXISTS industry VARCHAR(100);
|
||||
ALTER TABLE meta.instruments ADD COLUMN IF NOT EXISTS category VARCHAR(50);
|
||||
ALTER TABLE meta.instruments ADD COLUMN IF NOT EXISTS subcategory VARCHAR(50);
|
||||
ALTER TABLE meta.instruments ADD COLUMN IF NOT EXISTS asset_class VARCHAR(30);
|
||||
ALTER TABLE meta.instruments ADD COLUMN IF NOT EXISTS geography VARCHAR(30);
|
||||
|
||||
-- ETF-specific
|
||||
ALTER TABLE meta.instruments ADD COLUMN IF NOT EXISTS underlying VARCHAR(30);
|
||||
|
||||
COMMENT ON COLUMN meta.instruments.description IS 'Full name/description of the instrument';
|
||||
COMMENT ON COLUMN meta.instruments.country IS 'ISO 3166-1 alpha-3 country code (overrides exchange default)';
|
||||
COMMENT ON COLUMN meta.instruments.extended_hours IS 'Override exchange default for pre/post market trading';
|
||||
COMMENT ON COLUMN meta.instruments.sector IS 'GICS sector (e.g., Technology, Financials)';
|
||||
COMMENT ON COLUMN meta.instruments.industry IS 'GICS industry (e.g., Software-Application)';
|
||||
COMMENT ON COLUMN meta.instruments.category IS 'Custom categorization (e.g., Sector ETF, Broad Market)';
|
||||
COMMENT ON COLUMN meta.instruments.subcategory IS 'Custom sub-categorization (e.g., S&P 500, Technology)';
|
||||
COMMENT ON COLUMN meta.instruments.asset_class IS 'Asset class (Equity, Fixed Income, Commodity, Currency)';
|
||||
COMMENT ON COLUMN meta.instruments.geography IS 'Geographic focus (US, International, Emerging)';
|
||||
COMMENT ON COLUMN meta.instruments.underlying IS 'Underlying symbol for ETFs/derivatives (e.g., SPX, NDX)';
|
||||
|
||||
-- ============================================================================
|
||||
-- 3. Update existing exchange data
|
||||
-- ============================================================================
|
||||
|
||||
-- Update NYSE if it exists
|
||||
UPDATE meta.exchanges
|
||||
SET
|
||||
default_tick_size = 0.01,
|
||||
default_tick_value = 0.01,
|
||||
default_extended_hours = true,
|
||||
country = 'USA'
|
||||
WHERE id = 'NYSE';
|
||||
|
||||
-- Update CME if it exists
|
||||
UPDATE meta.exchanges
|
||||
SET
|
||||
country = 'USA',
|
||||
default_extended_hours = true
|
||||
WHERE id = 'CME';
|
||||
|
||||
-- Add NASDAQ if it doesn't exist
|
||||
INSERT INTO meta.exchanges (
|
||||
id, name, timezone, currency, default_sec_type,
|
||||
default_tick_size, default_tick_value, default_extended_hours, country
|
||||
)
|
||||
SELECT 'NASDAQ', 'NASDAQ Stock Market', 'America/New_York', 'USD', 'STK',
|
||||
0.01, 0.01, true, 'USA'
|
||||
WHERE NOT EXISTS (SELECT 1 FROM meta.exchanges WHERE id = 'NASDAQ');
|
||||
|
||||
-- ============================================================================
|
||||
-- Migration Complete
|
||||
-- ============================================================================
|
||||
|
||||
-- Verify the changes
|
||||
SELECT 'meta.exchanges columns:' as info;
|
||||
SELECT column_name, data_type, character_maximum_length
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'meta' AND table_name = 'exchanges'
|
||||
ORDER BY ordinal_position;
|
||||
|
||||
SELECT 'meta.instruments columns:' as info;
|
||||
SELECT column_name, data_type, character_maximum_length
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'meta' AND table_name = 'instruments'
|
||||
ORDER BY ordinal_position;
|
||||
111
sql/migrate/migrate_metadata_identity.sql
Normal file
111
sql/migrate/migrate_metadata_identity.sql
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
-- 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 $$;
|
||||
Loading…
Add table
Add a link
Reference in a new issue