Files
LedgerLine/db/schema.sql
T

81 lines
3.0 KiB
SQL
Raw Normal View History

-- Ledgerline schema. Idempotent: safe to run against an existing database.
-- Index policy is deliberate -- see docs/phase-0-base-project-spec.md §5.
-- Do NOT add transactions(account_id, created_at) or transactions(category_id)
-- here; those live in db/optional_indexes.sql, applied only by `make fix-index`.
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
CREATE TABLE IF NOT EXISTS accounts (
id UUID PRIMARY KEY,
holder_name TEXT NOT NULL,
currency CHAR(3) NOT NULL,
balance_minor BIGINT NOT NULL,
is_hot BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS merchant_categories (
id SMALLINT PRIMARY KEY,
code TEXT NOT NULL UNIQUE,
label TEXT NOT NULL,
risk_weight NUMERIC(3,2) NOT NULL
);
CREATE TABLE IF NOT EXISTS transactions (
id UUID PRIMARY KEY,
account_id UUID NOT NULL,
kind TEXT NOT NULL,
amount_minor BIGINT NOT NULL,
currency CHAR(3),
merchant_name TEXT,
category_id SMALLINT,
status TEXT NOT NULL,
risk_score NUMERIC(4,3),
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- Parallel workers otherwise let a plain sequential scan of this table
-- compete with an index scan on raw CPU parallelism for higher-selectivity
-- accounts (e.g. the 5 hot ones, each ~6-8% of all rows) -- masking exactly
-- the missing-index pathology this table's index policy (README §5) exists
-- to demonstrate. Scoped to this one table, not a database-wide setting.
ALTER TABLE transactions SET (parallel_workers = 0);
CREATE TABLE IF NOT EXISTS sanctions_entries (
id SERIAL PRIMARY KEY,
normalized_name TEXT NOT NULL,
country CHAR(2),
list_source TEXT,
added_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS notifications (
id UUID PRIMARY KEY,
transaction_id UUID NOT NULL,
channel TEXT NOT NULL,
status TEXT NOT NULL,
attempts INT NOT NULL DEFAULT 0,
sent_at TIMESTAMPTZ
);
CREATE TABLE IF NOT EXISTS audit_events (
id BIGSERIAL PRIMARY KEY,
transaction_id UUID NOT NULL,
event_type TEXT NOT NULL,
payload JSONB,
recorded_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- Not part of the base spec's domain model: a single-row table the seed
-- script writes its summary manifest into, so both `scripts/smoke.py` and
-- the test suite can read back what a given seed run actually produced
-- (row counts, hot/sanctioned/poison account ids, the SEED value used)
-- without re-deriving it via ad-hoc queries.
CREATE TABLE IF NOT EXISTS seed_manifest (
id SMALLINT PRIMARY KEY DEFAULT 1,
data JSONB NOT NULL
);
CREATE INDEX IF NOT EXISTS accounts_holder_name_idx ON accounts (holder_name);
CREATE INDEX IF NOT EXISTS sanctions_entries_normalized_name_idx ON sanctions_entries (normalized_name);