import { BaseSchema } from '@adonisjs/lucid/schema' /** * The junk judge by what changed (LOG.md 2026-10-19 (83)): a full round over every release took 35–38 min of each * hour, longer with every inspection the prober added. Triggers note each release whose facts a rule reads changed * (judge_dirty: its probes' statuses, inspections, file hashes, passwords, spots, par2 sets, merges, its own judged * columns); the judge judges those every few minutes, and every release in a daily full round (judge_state: when the * last one was, by which rule set), its matches gathered in judge_full. judge_dirty is unlogged: what a crash loses * the next full round judges. */ export default class extends BaseSchema { async up() { this.schema.raw(`CREATE UNLOGGED TABLE IF NOT EXISTS judge_dirty (release_id bigint NOT NULL)`) this.schema.raw(`CREATE UNLOGGED TABLE IF NOT EXISTS judge_full (release_id bigint PRIMARY KEY, rule varchar(16) NOT NULL)`) this.schema.raw( `CREATE TABLE IF NOT EXISTS judge_state (id smallint PRIMARY KEY DEFAULT 1 CHECK (id = 1), full_at timestamptz NOT NULL, version integer NOT NULL)` ) // The verdicts are an hourly full round's: the next full one a day from now. this.schema.raw(`INSERT INTO judge_state (full_at, version) VALUES (now(), 1) ON CONFLICT DO NOTHING`) // Rows of a table with release_id: the releases of the rows inserted, deleted, or updated (both, when moved). this.schema.raw(` CREATE OR REPLACE FUNCTION judge_dirty_by_release_id() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN IF TG_OP IN ('INSERT', 'UPDATE') THEN INSERT INTO judge_dirty (release_id) SELECT DISTINCT release_id FROM new_rows WHERE release_id IS NOT NULL; END IF; IF TG_OP IN ('UPDATE', 'DELETE') THEN INSERT INTO judge_dirty (release_id) SELECT DISTINCT release_id FROM old_rows WHERE release_id IS NOT NULL; END IF; RETURN NULL; END $$`) // probe_candidates: only a status that counts for the dead rule, or a candidate moved by a merge (not the claims' // 'taken', the settler's ready). this.schema.raw(` CREATE OR REPLACE FUNCTION judge_dirty_probes() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN INSERT INTO judge_dirty (release_id) SELECT DISTINCT unnest(ARRAY[n.release_id, o.release_id]) FROM new_rows n JOIN old_rows o ON o.id = n.id WHERE n.release_id IS DISTINCT FROM o.release_id OR (n.status IS DISTINCT FROM o.status AND (n.status IN ('done', 'missing', 'error') OR o.status IN ('done', 'missing', 'error'))); RETURN NULL; END $$`) this.schema.raw(` CREATE OR REPLACE FUNCTION judge_dirty_merges() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN INSERT INTO judge_dirty (release_id) SELECT DISTINCT into_id FROM new_rows; RETURN NULL; END $$`) this.schema.raw(` CREATE OR REPLACE FUNCTION judge_dirty_releases() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN IF TG_OP = 'INSERT' THEN INSERT INTO judge_dirty (release_id) SELECT id FROM new_rows; ELSIF TG_OP = 'DELETE' THEN INSERT INTO judge_dirty (release_id) SELECT id FROM old_rows; ELSE INSERT INTO judge_dirty (release_id) VALUES (NEW.id); END IF; RETURN NULL; END $$`) for (const table of ['release_inspections', 'file_hashes', 'release_passwords', 'release_spots', 'par2_sets']) { this.schema.raw(`DROP TRIGGER IF EXISTS judge_dirty_insert ON ${table}`) this.schema.raw(`DROP TRIGGER IF EXISTS judge_dirty_update ON ${table}`) this.schema.raw(`DROP TRIGGER IF EXISTS judge_dirty_delete ON ${table}`) this.schema.raw( `CREATE TRIGGER judge_dirty_insert AFTER INSERT ON ${table} REFERENCING NEW TABLE AS new_rows FOR EACH STATEMENT EXECUTE FUNCTION judge_dirty_by_release_id()` ) this.schema.raw( `CREATE TRIGGER judge_dirty_update AFTER UPDATE ON ${table} REFERENCING OLD TABLE AS old_rows NEW TABLE AS new_rows FOR EACH STATEMENT EXECUTE FUNCTION judge_dirty_by_release_id()` ) this.schema.raw( `CREATE TRIGGER judge_dirty_delete AFTER DELETE ON ${table} REFERENCING OLD TABLE AS old_rows FOR EACH STATEMENT EXECUTE FUNCTION judge_dirty_by_release_id()` ) } this.schema.raw(`DROP TRIGGER IF EXISTS judge_dirty_update ON probe_candidates`) this.schema.raw(`DROP TRIGGER IF EXISTS judge_dirty_delete ON probe_candidates`) this.schema.raw( `CREATE TRIGGER judge_dirty_update AFTER UPDATE ON probe_candidates REFERENCING OLD TABLE AS old_rows NEW TABLE AS new_rows FOR EACH STATEMENT EXECUTE FUNCTION judge_dirty_probes()` ) this.schema.raw( `CREATE TRIGGER judge_dirty_delete AFTER DELETE ON probe_candidates REFERENCING OLD TABLE AS old_rows FOR EACH STATEMENT EXECUTE FUNCTION judge_dirty_by_release_id()` ) this.schema.raw(`DROP TRIGGER IF EXISTS judge_dirty_insert ON release_merges`) this.schema.raw( `CREATE TRIGGER judge_dirty_insert AFTER INSERT ON release_merges REFERENCING NEW TABLE AS new_rows FOR EACH STATEMENT EXECUTE FUNCTION judge_dirty_merges()` ) this.schema.raw(`DROP TRIGGER IF EXISTS judge_dirty_insert ON releases`) this.schema.raw(`DROP TRIGGER IF EXISTS judge_dirty_update ON releases`) this.schema.raw(`DROP TRIGGER IF EXISTS judge_dirty_delete ON releases`) this.schema.raw( `CREATE TRIGGER judge_dirty_insert AFTER INSERT ON releases REFERENCING NEW TABLE AS new_rows FOR EACH STATEMENT EXECUTE FUNCTION judge_dirty_releases()` ) this.schema.raw( `CREATE TRIGGER judge_dirty_delete AFTER DELETE ON releases REFERENCING OLD TABLE AS old_rows FOR EACH STATEMENT EXECUTE FUNCTION judge_dirty_releases()` ) // Per row, and only when a column a rule reads changed: the tail and the settler update releases all the time. this.schema.raw( `CREATE TRIGGER judge_dirty_update AFTER UPDATE ON releases FOR EACH ROW WHEN (OLD.name IS DISTINCT FROM NEW.name OR OLD.poster IS DISTINCT FROM NEW.poster OR OLD.source IS DISTINCT FROM NEW.source OR OLD.file_total IS DISTINCT FROM NEW.file_total OR OLD.passworded IS DISTINCT FROM NEW.passworded OR OLD.settled_at IS DISTINCT FROM NEW.settled_at OR OLD.readable IS DISTINCT FROM NEW.readable OR OLD.inner_name IS DISTINCT FROM NEW.inner_name OR OLD.group_id IS DISTINCT FROM NEW.group_id OR OLD.first_article IS DISTINCT FROM NEW.first_article OR OLD.last_article IS DISTINCT FROM NEW.last_article OR OLD.posted_at IS DISTINCT FROM NEW.posted_at OR OLD.last_posted_at IS DISTINCT FROM NEW.last_posted_at OR OLD.complete IS DISTINCT FROM NEW.complete) EXECUTE FUNCTION judge_dirty_releases()` ) } async down() { for (const table of ['release_inspections', 'file_hashes', 'release_passwords', 'release_spots', 'par2_sets', 'probe_candidates', 'release_merges', 'releases']) { for (const event of ['insert', 'update', 'delete']) this.schema.raw(`DROP TRIGGER IF EXISTS judge_dirty_${event} ON ${table}`) } this.schema.raw(`DROP FUNCTION IF EXISTS judge_dirty_by_release_id(), judge_dirty_probes(), judge_dirty_merges(), judge_dirty_releases()`) this.schema.raw(`DROP TABLE IF EXISTS judge_dirty, judge_full, judge_state`) } }