diff --git a/README.md b/README.md index 9398869..2ca361e 100644 --- a/README.md +++ b/README.md @@ -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, 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). --- diff --git a/VERSION b/VERSION index 9084fa2..26aaba0 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.0 +1.2.0 diff --git a/sql/03_tables.sql b/sql/03_tables.sql index f6e5a2b..cc009ed 100644 --- a/sql/03_tables.sql +++ b/sql/03_tables.sql @@ -135,6 +135,16 @@ CREATE TABLE meta.sessions ( 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 ( iana_name character varying(50) NOT NULL, display_name character varying(100) NOT NULL, diff --git a/sql/04_indexes.sql b/sql/04_indexes.sql index 20c50c2..6f192df 100644 --- a/sql/04_indexes.sql +++ b/sql/04_indexes.sql @@ -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.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_timestamp ON log.event_log USING btree ("timestamp" DESC); diff --git a/sql/07_reference_data.sql b/sql/07_reference_data.sql index 23c1fdd..2a4b73b 100644 --- a/sql/07_reference_data.sql +++ b/sql/07_reference_data.sql @@ -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 -- Timezones @@ -767,3 +767,7 @@ VALUES ('OPT', 'Option', 'Equity and index options', NULL), ('STK', 'Stock', 'Common and preferred equities', 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'); diff --git a/sql/09_schema_check.sql b/sql/09_schema_check.sql index 48a011d..8e6d46f 100644 --- a/sql/09_schema_check.sql +++ b/sql/09_schema_check.sql @@ -114,6 +114,10 @@ BEGIN ('meta.instruments', 'asset_class', 'character varying(30)', false), ('meta.instruments', 'geography', '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', 'name', 'character varying(50)', true), ('meta.security_types', 'description', 'text', false), @@ -211,6 +215,10 @@ BEGIN ('meta.sessions', 'sessions_pkey'), ('meta.series', 'series_pkey'), ('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.security_types', 'security_types_pkey'), ('meta.timezones', 'timezones_pkey') @@ -301,12 +309,27 @@ BEGIN FROM (VALUES ('meta.timezones', 563), ('meta.currencies', 179), - ('meta.security_types', 13) + ('meta.security_types', 13), + ('meta.series_schemas', 1) ) AS e(tbl, expected) WHERE to_regclass(e.tbl) IS NOT NULL ) AS r 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 RETURN QUERY SELECT 'schema version', 'not recorded in meta.schema_version' diff --git a/sql/10_schema_version.sql b/sql/10_schema_version.sql index 00c6866..c6cd3b4 100644 --- a/sql/10_schema_version.sql +++ b/sql/10_schema_version.sql @@ -5,4 +5,4 @@ -- they were built for (Python: DB_SCHEMA_VERSION in tsapi_pgdb/_version.py). 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');