Schema 1.5.0: holiday calendar registry, citext names; drop num_records

For the coverage-aware holiday-calendar API in TSAPI core (db_base
holiday_calendar__get / __set_coverage / __set_metadata / __upsert).

- meta.holiday_calendars (name, description, covered_from, covered_to,
  source, updated_at): a calendar exists when it has a row here, not
  when it has holidays (retires the 9999-12-31 sentinel row).
  covered_from/to: the range over which the closure list is complete;
  both set or both NULL, from <= to (holiday_calendars_coverage_check).
- Calendar names are citext in every table (registry, holidays.calendar_name,
  exchanges.default_holiday_calendar, instruments.holiday_calendar):
  stored as given, unique and matched case-insensitively -- also in the
  foreign keys -- as the native TSAPI backend does with COLLATE NOCASE.
  New extension citext (00_extensions.sql). Name check: non-empty,
  trimmed, <= 50 characters.
- FKs: holidays -> registry ON DELETE CASCADE; exchanges/instruments ->
  registry ON DELETE RESTRICT (a calendar in use cannot be dropped);
  ON UPDATE CASCADE everywhere.
- meta.series.num_records dropped. Only some write paths maintained it
  (never C++), it sometimes held an estimate, and it was NULL for a series
  never written to. Clients ask the table: count(*), or
  SELECT EXISTS (SELECT 1 FROM <table>) for "is it empty?".
- find_schema_problems() covers the new table, columns, constraints and
  extension, and runs with search_path = pg_catalog so format_type()
  reports 'public.citext' whatever the caller's search_path.

Verified on a fresh database (PG 17): run_setup.py installs it and
find_schema_problems() returns zero rows, also for a caller without
public on the search_path; constraint behaviour checked by hand
(case-insensitive duplicate refused, FK matches any spelling, RESTRICT
and CASCADE, coverage and name checks).

Re-provision databases (no migration).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
peter 2026-10-07 17:02:40 +01:00
parent cddaec5730
commit 85679b5e78
7 changed files with 81 additions and 13 deletions

View file

@ -38,15 +38,29 @@ CREATE TABLE meta.exchanges (
currency character varying(3),
default_sec_type character varying(18),
default_session character varying(40),
default_holiday_calendar character varying(40),
default_holiday_calendar public.citext,
default_tick_size double precision,
default_tick_value double precision,
default_extended_hours boolean DEFAULT false,
country character varying(3)
);
-- Holiday calendar registry (1.5.0). A calendar exists when it has a row
-- here -- not when it has holidays. covered_from/covered_to: the range over
-- which its list of closures is complete (both set, or both NULL = unknown).
-- Names are citext: stored as given, matched case-insensitively ('NYSE' and
-- 'nyse' are one calendar), the same rule as the native TSAPI backend.
CREATE TABLE meta.holiday_calendars (
name public.citext NOT NULL,
description text,
covered_from date,
covered_to date,
source text,
updated_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE meta.holidays (
calendar_name character varying(50) NOT NULL,
calendar_name public.citext NOT NULL,
date date NOT NULL,
description character varying(255),
early_close boolean NOT NULL DEFAULT false,
@ -64,7 +78,7 @@ CREATE TABLE meta.instruments (
strike double precision,
currency character varying(3),
session character varying(40),
holiday_calendar character varying(50),
holiday_calendar public.citext,
sec_id character varying(50),
sec_id_type character varying(20),
description character varying(255),
@ -92,6 +106,10 @@ CREATE TABLE meta.schema_version (
description text
);
-- No record count is stored (removed in 1.5.0): "num_records" was maintained by
-- some write paths only, sometimes held an estimate, and was NULL for a series
-- never written to. Ask the table itself: SELECT count(*), or for "is it empty?"
-- SELECT EXISTS (SELECT 1 FROM <table>) -- fast, stops at the first row.
CREATE TABLE meta.series (
table_id integer DEFAULT nextval('meta._table_meta__table_id_seq'::regclass) NOT NULL,
db_schema character varying(70) NOT NULL,
@ -108,7 +126,6 @@ CREATE TABLE meta.series (
sec_id character varying(30),
sec_id_type character varying(30),
create_timestamp timestamp without time zone DEFAULT CURRENT_TIMESTAMP,
num_records bigint,
first_timestamp timestamp without time zone,
last_timestamp timestamp without time zone,
bar_type character varying(30),