tsapi-seriesdb-shared-sql/README.md
peter 9032fd3a45 run_setup: refuse an existing installation; remove --clean-slate
- Removed --clean-slate: it dropped every schema in the database,
  including user data. Nothing used it.
- Pre-check (same rule as the Python client's init_postgres_db()): refuse
  if meta or log exists, series is not empty, or the prevent_series_drops
  trigger exists, naming the installed version. User schemas and tables
  are allowed and never touched; an empty series schema is reused.
- --drop-existing drops every registered series schema plus meta and log,
  and refuses if a series schema holds non-series objects.
- All files run in one transaction: a failure leaves nothing behind.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 11:58:46 +01:00

137 lines
5.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# TSAPI_SERIESDB_SCHEMA — canonical timeseries DB schema
**Single source of truth** for the PostgreSQL schema used by the TSAPI
timeseries database. Both client libraries target the schema defined here:
- **Python** — `TSAPI_SERIESDB_PG_PY`
- **C++** — `TSAPI_POSTGRES_CPP`
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.3.0** (see [`VERSION`](VERSION); recorded at runtime
in the `meta.schema_version` table).
---
## Layout
```
TSAPI_SERIESDB_SCHEMA/
├── VERSION # canonical schema version string
├── run_setup.py # provisioner — runs the numbered files in order
├── db_config.json.example # copy to db_config.json and edit (gitignored)
├── README.md
└── sql/
├── 00_extensions.sql # pg_trgm
├── 01_schemas.sql # meta / series / log schemas
├── 02_sequences.sql # id sequences
├── 03_tables.sql # meta.* and log.* tables
├── 04_indexes.sql # indexes + registry constraints
├── 05_procedures.sql # meta.* / log.* stored procedures
├── 06_triggers.sql # prevent_series_drops event trigger
├── 07_reference_data.sql # timezones + currencies + security types
├── 08_script_log.sql # log.script_log table + view
├── 09_schema_check.sql # meta.find_schema_problems()
└── 10_schema_version.sql # records VERSION in meta.schema_version
```
Every fresh install runs `00`–`10` in order (`run_setup.py`, and the Python
client's `init_postgres_db()`). There are no migration scripts: a database
from an older schema is re-provisioned, not upgraded.
---
## Provisioning a database
```bash
# 1. configure
cp db_config.json.example db_config.json # then edit host/user/password
# (or pass connection flags on the command line instead)
# 2. create the schema
python run_setup.py # uses db_config.json / defaults
python run_setup.py --host 192.168.1.7 --user claude --password claudepwd
python run_setup.py --drop-existing # drop the existing installation first
# (ALL series data); user schemas untouched
```
The database may hold the user's own schemas and tables; provisioning never
touches them. It **refuses** a database that already holds the library's
objects -- `meta`, `log`, a non-empty `series` schema, or the
`prevent_series_drops` event trigger -- and reports the installed version.
There are no migrations: re-provision with `--drop-existing` (deletes all
series) or use another database. All files run in one transaction, so a
failure leaves the database untouched.
Requires `psycopg2` and a PostgreSQL user with CREATE privileges on the target
database.
---
## What gets created
- **Schemas:** `meta` (registry + reference data), `series` (timeseries data
tables live here), `log` (event/audit trail).
- **Registry:** `meta.series` — one row per series table, PK `table_id`,
`UNIQUE (db_schema, table_name)`.
- **Reference tables:** `meta.currencies`, `meta.timezones`, `meta.exchanges`,
`meta.instruments`, `meta.sessions`, `meta.holidays`, `meta.security_types`,
`meta.schema_version`.
- **Procedures:** `meta.drop_series` / `_if_exists` / `drop_all_series`,
`meta.update_series_last_timestamp`, `meta.refresh_series_timestamps`,
`meta.read_series_batch[_cursor|_ohlc]`, `log.notify_event_log`,
`log.flush_pending_to_event_log`.
- **Event trigger:** `prevent_series_drops` — blocks direct `DROP TABLE` on a
registered series (drops must go through `meta.drop_series*`).
- **Self-check:** `meta.find_schema_problems()` — see below.
Series data tables have **no `UNIQUE` constraint on `timestamp`** — the index is
BRIN. Concurrent writers must serialise per-series with the advisory-lock
protocol (`pg_advisory_lock(hashtext('tsapi_pgdb.write:{schema}.{table}'))`); see
the C++ project's `planning/2026-09-02_cpp_locking_interop_plan.md`.
---
## Checking a database
```sql
SELECT * FROM meta.find_schema_problems(); -- zero rows = complete and usable
```
One row per problem, `(object, problem)`, for example
`('table meta.instruments', 'missing columns: sector')`. Read-only. It checks
schemas, the extension, sequences, every table column's type and NOT NULL,
constraints, indexes, views, stored-function bodies (whitespace-insensitive
fingerprint), the event trigger, reference-data row counts and that a version
row exists. If the function itself does not exist, the database is not
initialized or predates it -- re-provision.
`09_schema_check.sql` mirrors `00`–`08`: **change it in the same commit as any
of them**. The release test -- a freshly provisioned database must return
zero rows -- fails until you do. The file's header shows how to refresh the
function-body fingerprints.
---
## Versioning contract
Bump [`VERSION`](VERSION) -- and the string in `10_schema_version.sql` -- whenever
`sql/` changes in a way clients must know about. Each client compares
`meta.schema_version` with the version it was built for on connect, and calls
`meta.find_schema_problems()` for everything else.
---
## Provenance
The `sql/` files were seeded (byte-for-byte, 2026-09-02) from the then-current
authoritative copy in `TSAPI_SERIESDB_PG_PY/tsapi_pgdb/sql/`. From this point on,
**this** directory is authoritative and the Python project should consume it
rather than keep its own copy — see the migration note in the C++ project's
`planning/` folder.
> **Known drift caught during setup:** on 2026-09-02 the remote instance
> `192.168.1.7` had `meta.sessions.exchange_id` as nullable, while the canonical
> DDL (`03_tables.sql`) defines it `NOT NULL`. Re-provision drifted databases
> from these files.