Schema 1.2.0: meta.series_schemas registry; lower-case name checks

- meta.series_schemas (name, description, created_at, created_by), seeded
  with 'series'. FK series_db_schema_fkey from meta.series.db_schema: a
  series may only live in a registered schema, for every client.
  Unregistered schemas belong to the user and are never touched.
- CHECK series_schemas_name_check and series_table_name_check enforce the
  shared lower-case identifier rule in the database.
- meta.find_schema_problems() covers the registry and reports a registered
  schema that no longer exists; user schemas are not examined.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
peter 2026-10-06 11:42:10 +01:00
parent 155fc7c3dd
commit 4c067bc4b9
7 changed files with 59 additions and 5 deletions

View file

@ -9,7 +9,7 @@ timeseries database. Both client libraries target the schema defined here:
Neither project should keep its own copy of the DDL. When the schema changes, Neither project should keep its own copy of the DDL. When the schema changes,
it changes **here**, and both projects re-provision / re-test against it. it changes **here**, and both projects re-provision / re-test against it.
Current schema version: **1.1.0** (see [`VERSION`](VERSION); recorded at runtime Current schema version: **1.2.0** (see [`VERSION`](VERSION); recorded at runtime
in the `meta.schema_version` table). in the `meta.schema_version` table).
--- ---

View file

@ -1 +1 @@
1.1.0 1.2.0

View file

@ -135,6 +135,16 @@ CREATE TABLE meta.sessions (
exchange_id character varying(12) NOT NULL exchange_id character varying(12) NOT NULL
); );
-- Registry of series schemas. A series may only live in a registered schema
-- (FK from meta.series.db_schema, 04_indexes.sql). Every other schema in the
-- database belongs to the user and is never touched by the clients.
CREATE TABLE meta.series_schemas (
name text NOT NULL,
description text,
created_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by text NOT NULL DEFAULT CURRENT_USER
);
CREATE TABLE meta.timezones ( CREATE TABLE meta.timezones (
iana_name character varying(50) NOT NULL, iana_name character varying(50) NOT NULL,
display_name character varying(100) NOT NULL, display_name character varying(100) NOT NULL,

View file

@ -21,6 +21,23 @@ ALTER TABLE meta.currencies ADD CONSTRAINT currencies_pkey PRIMARY KEY (code);
ALTER TABLE meta.security_types ADD CONSTRAINT security_types_pkey PRIMARY KEY (code); ALTER TABLE meta.security_types ADD CONSTRAINT security_types_pkey PRIMARY KEY (code);
ALTER TABLE meta.timezones ADD CONSTRAINT timezones_pkey PRIMARY KEY (iana_name); ALTER TABLE meta.timezones ADD CONSTRAINT timezones_pkey PRIMARY KEY (iana_name);
-- Series schema registry. Names follow the table-name rule shared by both
-- clients: lower case, [a-z_][a-z0-9_]*, at most 63 bytes, never quoted.
ALTER TABLE meta.series_schemas ADD CONSTRAINT series_schemas_pkey PRIMARY KEY (name);
ALTER TABLE meta.series_schemas ADD CONSTRAINT series_schemas_name_check
CHECK (name ~ '^[a-z_][a-z0-9_]*$' AND octet_length(name) <= 63);
-- A series may only live in a registered schema -- enforced here, so no
-- client can bypass it. RESTRICT: a schema cannot be unregistered while it
-- still holds series.
ALTER TABLE meta.series ADD CONSTRAINT series_db_schema_fkey
FOREIGN KEY (db_schema) REFERENCES meta.series_schemas (name)
ON UPDATE RESTRICT ON DELETE RESTRICT;
-- Table names follow the same rule (Core contract 6 in both clients).
ALTER TABLE meta.series ADD CONSTRAINT series_table_name_check
CHECK (table_name ~ '^[a-z_][a-z0-9_]*$' AND octet_length(table_name) <= 63);
CREATE INDEX idx_event_log_series_timestamp ON log.event_log USING btree (series_name, "timestamp" DESC); CREATE INDEX idx_event_log_series_timestamp ON log.event_log USING btree (series_name, "timestamp" DESC);
CREATE INDEX idx_event_log_timestamp ON log.event_log USING btree ("timestamp" DESC); CREATE INDEX idx_event_log_timestamp ON log.event_log USING btree ("timestamp" DESC);

View file

@ -1,4 +1,4 @@
-- Reference data (timezones and currencies) -- Reference data (series schema registry, timezones, currencies, security types)
-- Generated: 2025-10-15T12:24:56.582392 -- Generated: 2025-10-15T12:24:56.582392
-- Timezones -- Timezones
@ -767,3 +767,7 @@ VALUES
('OPT', 'Option', 'Equity and index options', NULL), ('OPT', 'Option', 'Equity and index options', NULL),
('STK', 'Stock', 'Common and preferred equities', NULL), ('STK', 'Stock', 'Common and preferred equities', NULL),
('WAR', 'Warrant', 'Equity warrants', NULL); ('WAR', 'Warrant', 'Equity warrants', NULL);
-- Series schema registry: the default schema, created in 01_schemas.sql
INSERT INTO meta.series_schemas (name, description)
VALUES ('series', 'Default series schema');

View file

@ -114,6 +114,10 @@ BEGIN
('meta.instruments', 'asset_class', 'character varying(30)', false), ('meta.instruments', 'asset_class', 'character varying(30)', false),
('meta.instruments', 'geography', 'character varying(30)', false), ('meta.instruments', 'geography', 'character varying(30)', false),
('meta.instruments', 'underlying', 'character varying(30)', false), ('meta.instruments', 'underlying', 'character varying(30)', false),
('meta.series_schemas', 'name', 'text', true),
('meta.series_schemas', 'description', 'text', false),
('meta.series_schemas', 'created_at', 'timestamp with time zone', true),
('meta.series_schemas', 'created_by', 'text', true),
('meta.security_types', 'code', 'character varying(10)', true), ('meta.security_types', 'code', 'character varying(10)', true),
('meta.security_types', 'name', 'character varying(50)', true), ('meta.security_types', 'name', 'character varying(50)', true),
('meta.security_types', 'description', 'text', false), ('meta.security_types', 'description', 'text', false),
@ -211,6 +215,10 @@ BEGIN
('meta.sessions', 'sessions_pkey'), ('meta.sessions', 'sessions_pkey'),
('meta.series', 'series_pkey'), ('meta.series', 'series_pkey'),
('meta.series', 'series_schema_table_key'), ('meta.series', 'series_schema_table_key'),
('meta.series', 'series_db_schema_fkey'),
('meta.series', 'series_table_name_check'),
('meta.series_schemas', 'series_schemas_pkey'),
('meta.series_schemas', 'series_schemas_name_check'),
('meta.currencies', 'currencies_pkey'), ('meta.currencies', 'currencies_pkey'),
('meta.security_types', 'security_types_pkey'), ('meta.security_types', 'security_types_pkey'),
('meta.timezones', 'timezones_pkey') ('meta.timezones', 'timezones_pkey')
@ -301,12 +309,27 @@ BEGIN
FROM (VALUES FROM (VALUES
('meta.timezones', 563), ('meta.timezones', 563),
('meta.currencies', 179), ('meta.currencies', 179),
('meta.security_types', 13) ('meta.security_types', 13),
('meta.series_schemas', 1)
) AS e(tbl, expected) ) AS e(tbl, expected)
WHERE to_regclass(e.tbl) IS NOT NULL WHERE to_regclass(e.tbl) IS NOT NULL
) AS r ) AS r
WHERE r.actual < r.expected; WHERE r.actual < r.expected;
-- Series schema registry ------------------------------------------------
-- Only REGISTERED schemas are examined; user schemas are never looked at.
IF to_regclass('meta.series_schemas') IS NOT NULL THEN
RETURN QUERY
SELECT 'series schema series', 'not registered in meta.series_schemas'
WHERE NOT EXISTS (SELECT 1 FROM meta.series_schemas WHERE name = 'series');
RETURN QUERY
SELECT 'series schema ' || r.name, 'registered but the schema does not exist'
FROM meta.series_schemas r
WHERE NOT EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = r.name)
ORDER BY r.name;
END IF;
IF to_regclass('meta.schema_version') IS NOT NULL THEN IF to_regclass('meta.schema_version') IS NOT NULL THEN
RETURN QUERY RETURN QUERY
SELECT 'schema version', 'not recorded in meta.schema_version' SELECT 'schema version', 'not recorded in meta.schema_version'

View file

@ -5,4 +5,4 @@
-- they were built for (Python: DB_SCHEMA_VERSION in tsapi_pgdb/_version.py). -- they were built for (Python: DB_SCHEMA_VERSION in tsapi_pgdb/_version.py).
INSERT INTO meta.schema_version (version, description) INSERT INTO meta.schema_version (version, description)
VALUES ('1.1.0', 'meta.find_schema_problems(); every install runs 00-10'); VALUES ('1.2.0', 'meta.series_schemas registry; lower-case table name check');