querylog.db carries a schema version; migrations run at startup as one transaction after a vacuumed 0600 backup, and every failure refuses startup (exit 2, no systemd restart loop) instead of starting empty. corruption is the only automatic recreate left. the cut gate now requires a fixture-proven migration or an explicit versioned break with restore instructions, and locks shipped migration files and fixtures byte-for-byte.
89 lines
3.2 KiB
SQL
89 lines
3.2 KiB
SQL
-- FROZEN FIXTURE. querylog.db schema version 1: byte-for-byte the `ddl` text of
|
|
-- `src/storage/querylog_schema.zig`, which is the schema the 0.0.12 and 0.0.13
|
|
-- binaries created and the one version 1 names.
|
|
--
|
|
-- IMMUTABLE once released. The release gate byte-compares this file against the
|
|
-- previous tag and fails the cut on any edit, because it is the starting point
|
|
-- every future migration is proved against: editing it would prove a migration
|
|
-- against a file no operator ever had. A new schema version ships a NEW pair.
|
|
--
|
|
-- The `PRAGMA user_version` stamp is deliberately NOT part of this file. The
|
|
-- loader applies it, which is what lets one fixture serve both the version-1
|
|
-- stamp and the 0.0.12/0.0.13 legacy fingerprint.
|
|
|
|
CREATE TABLE domains (
|
|
id INTEGER PRIMARY KEY,
|
|
domain TEXT NOT NULL UNIQUE
|
|
);
|
|
|
|
CREATE TABLE query_log (
|
|
id INTEGER PRIMARY KEY,
|
|
timestamp INTEGER NOT NULL,
|
|
domain_id INTEGER NOT NULL REFERENCES domains(id),
|
|
client_ip TEXT NOT NULL, -- text, not a FK: log rows are immutable facts
|
|
qtype INTEGER,
|
|
blocked INTEGER NOT NULL,
|
|
response_time_us INTEGER,
|
|
cache_hit INTEGER,
|
|
upstream TEXT,
|
|
qclass INTEGER NOT NULL,
|
|
rcode INTEGER NOT NULL,
|
|
group_id INTEGER, -- text/id pairs, not FKs: a renamed
|
|
group_name TEXT, -- group must not rewrite history
|
|
policy_action TEXT NOT NULL,
|
|
policy_reason TEXT NOT NULL,
|
|
matched TEXT,
|
|
source_id INTEGER,
|
|
source_name TEXT,
|
|
cname_target TEXT,
|
|
safe_search_target TEXT,
|
|
route_kind TEXT NOT NULL,
|
|
forward_zone TEXT,
|
|
CHECK (rcode BETWEEN 0 AND 4095) -- twelve bits (RFC 6891 6.1.3)
|
|
);
|
|
CREATE INDEX idx_query_log_ts ON query_log(timestamp);
|
|
CREATE INDEX idx_query_log_client ON query_log(client_ip);
|
|
CREATE INDEX idx_query_log_domain ON query_log(domain_id);
|
|
|
|
CREATE TABLE querylog_meta (
|
|
id INTEGER PRIMARY KEY CHECK (id = 1), -- one row, enforced by the schema
|
|
created_at INTEGER NOT NULL,
|
|
available_since INTEGER NOT NULL
|
|
);
|
|
INSERT INTO querylog_meta (id, created_at, available_since)
|
|
VALUES (1, unixepoch(), unixepoch() + 1);
|
|
|
|
CREATE TABLE bucket_totals (
|
|
bucket INTEGER PRIMARY KEY,
|
|
queries INTEGER NOT NULL,
|
|
blocked INTEGER NOT NULL,
|
|
cached INTEGER NOT NULL,
|
|
rt_sum INTEGER NOT NULL, -- sum(response_time_us) over timed rows
|
|
rt_count INTEGER NOT NULL -- count(response_time_us)
|
|
) WITHOUT ROWID;
|
|
|
|
CREATE TABLE bucket_clients (
|
|
bucket INTEGER NOT NULL,
|
|
client_ip TEXT NOT NULL,
|
|
queries INTEGER NOT NULL,
|
|
PRIMARY KEY (bucket, client_ip)
|
|
) WITHOUT ROWID;
|
|
|
|
CREATE TABLE bucket_types (
|
|
bucket INTEGER NOT NULL,
|
|
qtype INTEGER NOT NULL, -- -1 encodes a NULL qtype, losslessly
|
|
count INTEGER NOT NULL,
|
|
PRIMARY KEY (bucket, qtype)
|
|
) WITHOUT ROWID;
|
|
|
|
CREATE TABLE bucket_routes (
|
|
bucket INTEGER NOT NULL,
|
|
route_kind TEXT NOT NULL,
|
|
source_present INTEGER NOT NULL, -- 0: source NULL; 1: source = source_text
|
|
source_text TEXT NOT NULL, -- '' when source_present = 0
|
|
count INTEGER NOT NULL,
|
|
PRIMARY KEY (bucket, route_kind, source_present, source_text),
|
|
CHECK (source_present IN (0, 1)),
|
|
CHECK (source_present = 1 OR source_text = '')
|
|
) WITHOUT ROWID;
|