Files
pixa/internal/db/migrations/001_schema.sql
T
sneak 5d78e62ae9
check / check (push) Waiting to run
Store a source the way a variant is stored, one row and one file per source URL (closes #308)
A source is now one source_content row and one file named by its
SourceKey, the SHA-256 of its host, path and query, made as the variant
key is. A store writes the file, then upserts the row, and removes the
file when the row cannot be written; an eviction deletes the row, then
the file, with no lock. source_metadata, the JSON files under
cache/metadata/, MetadataStorage, the content lock, the eviction
transaction and its test hook are gone, as is the reconciliation pass
that dropped a source row whose file is missing. Two source URLs
serving the same bytes store them twice.

Model: opus-5-5
2026-10-21 14:20:28 +00:00

167 lines
6.1 KiB
SQL

-- Migration 001: Initial schema
-- Creates all tables for the pixa caching image proxy
-- Source images, one row per source URL
-- Files stored at: cache/sources/<ab>/<cd>/<source_key>, where source_key
-- is the SHA-256 of the source's host, path and query
CREATE TABLE IF NOT EXISTS source_content (
source_key TEXT PRIMARY KEY,
source_host TEXT NOT NULL,
source_path TEXT NOT NULL,
source_query TEXT NOT NULL DEFAULT '',
content_type TEXT NOT NULL,
size_bytes INTEGER NOT NULL,
fetched_at DATETIME DEFAULT CURRENT_TIMESTAMP,
last_accessed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_source_content_last_accessed
ON source_content(last_accessed_at);
-- Processed variant blobs
-- Files stored at: cache/variants/<ab>/<cd>/<cache_key> (plus a .meta
-- sidecar with the content type). Tracked here (like source content
-- blobs above) so total cache usage is known without a directory scan,
-- and so LRU eviction has a timestamp to order on.
CREATE TABLE IF NOT EXISTS variant_content (
cache_key TEXT PRIMARY KEY,
size_bytes INTEGER NOT NULL,
content_type TEXT NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
last_accessed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_variant_content_last_accessed
ON variant_content(last_accessed_at);
-- Total cache usage: the sum of size_bytes over source_content and
-- variant_content, kept by the triggers below in the same statement
-- that adds, removes or resizes a row, so eviction reads this one row
-- instead of summing both tables. Each trigger also adds one to
-- change_count; the reconciliation pass, which sums both tables a page
-- at a time, corrects total_size_bytes only if change_count did not
-- move while it summed.
CREATE TABLE IF NOT EXISTS cache_usage (
id INTEGER PRIMARY KEY CHECK (id = 1),
total_size_bytes INTEGER NOT NULL DEFAULT 0,
change_count INTEGER NOT NULL DEFAULT 0
);
INSERT OR IGNORE INTO cache_usage (id) VALUES (1);
CREATE TRIGGER IF NOT EXISTS source_content_usage_insert
AFTER INSERT ON source_content
BEGIN
UPDATE cache_usage
SET total_size_bytes = total_size_bytes + NEW.size_bytes,
change_count = change_count + 1
WHERE id = 1;
END;
CREATE TRIGGER IF NOT EXISTS source_content_usage_delete
AFTER DELETE ON source_content
BEGIN
UPDATE cache_usage
SET total_size_bytes = total_size_bytes - OLD.size_bytes,
change_count = change_count + 1
WHERE id = 1;
END;
CREATE TRIGGER IF NOT EXISTS source_content_usage_update
AFTER UPDATE OF size_bytes ON source_content
BEGIN
UPDATE cache_usage
SET total_size_bytes = total_size_bytes - OLD.size_bytes + NEW.size_bytes,
change_count = change_count + 1
WHERE id = 1;
END;
CREATE TRIGGER IF NOT EXISTS variant_content_usage_insert
AFTER INSERT ON variant_content
BEGIN
UPDATE cache_usage
SET total_size_bytes = total_size_bytes + NEW.size_bytes,
change_count = change_count + 1
WHERE id = 1;
END;
CREATE TRIGGER IF NOT EXISTS variant_content_usage_delete
AFTER DELETE ON variant_content
BEGIN
UPDATE cache_usage
SET total_size_bytes = total_size_bytes - OLD.size_bytes,
change_count = change_count + 1
WHERE id = 1;
END;
CREATE TRIGGER IF NOT EXISTS variant_content_usage_update
AFTER UPDATE OF size_bytes ON variant_content
BEGIN
UPDATE cache_usage
SET total_size_bytes = total_size_bytes - OLD.size_bytes + NEW.size_bytes,
change_count = change_count + 1
WHERE id = 1;
END;
-- Output/transformed content blobs
-- Not written: transformed images are stored in cache/variants and
-- tracked in variant_content above.
CREATE TABLE IF NOT EXISTS output_content (
content_hash TEXT PRIMARY KEY,
content_type TEXT NOT NULL,
size_bytes INTEGER NOT NULL,
fetched_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- Request cache - maps full request params to output content
CREATE TABLE IF NOT EXISTS request_cache (
id INTEGER PRIMARY KEY AUTOINCREMENT,
cache_key TEXT NOT NULL UNIQUE,
source_metadata_id INTEGER NOT NULL,
output_hash TEXT NOT NULL,
width INTEGER NOT NULL,
height INTEGER NOT NULL,
format TEXT NOT NULL,
quality INTEGER NOT NULL DEFAULT 85,
fit_mode TEXT NOT NULL DEFAULT 'cover',
fetched_at DATETIME DEFAULT CURRENT_TIMESTAMP,
access_count INTEGER NOT NULL DEFAULT 1,
FOREIGN KEY (source_metadata_id) REFERENCES source_metadata(id),
FOREIGN KEY (output_hash) REFERENCES output_content(content_hash)
);
CREATE INDEX IF NOT EXISTS idx_request_cache_key ON request_cache(cache_key);
CREATE INDEX IF NOT EXISTS idx_request_cache_source ON request_cache(source_metadata_id);
CREATE INDEX IF NOT EXISTS idx_request_cache_output ON request_cache(output_hash);
CREATE INDEX IF NOT EXISTS idx_request_cache_fetched ON request_cache(fetched_at);
-- Negative cache for failed fetches (404s, timeouts, etc.)
CREATE TABLE IF NOT EXISTS negative_cache (
id INTEGER PRIMARY KEY AUTOINCREMENT,
source_host TEXT NOT NULL,
source_path TEXT NOT NULL,
source_query TEXT NOT NULL DEFAULT '',
status_code INTEGER NOT NULL,
error_message TEXT,
fetched_at DATETIME DEFAULT CURRENT_TIMESTAMP,
expires_at DATETIME NOT NULL,
UNIQUE(source_host, source_path, source_query)
);
CREATE INDEX IF NOT EXISTS idx_negative_cache_expires ON negative_cache(expires_at);
-- Cache statistics for monitoring
CREATE TABLE IF NOT EXISTS cache_stats (
id INTEGER PRIMARY KEY CHECK (id = 1),
hit_count INTEGER NOT NULL DEFAULT 0,
miss_count INTEGER NOT NULL DEFAULT 0,
upstream_fetch_count INTEGER NOT NULL DEFAULT 0,
upstream_fetch_bytes INTEGER NOT NULL DEFAULT 0,
transform_count INTEGER NOT NULL DEFAULT 0,
last_updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
INSERT OR IGNORE INTO cache_stats (id) VALUES (1);
-- A random value that a login session carries from its login. pixa makes it
-- when the table has no row, and each logout replaces it; a login session
-- carrying another value is refused, so logging out ends every login session.
CREATE TABLE IF NOT EXISTS logout (
id INTEGER PRIMARY KEY CHECK (id = 1),
logout_value TEXT NOT NULL
);