-- Art-movement predecessor → successor edges for timeline / Tree of Art. -- Curators manage these with a required proof URL (literature or web essay). CREATE TABLE IF NOT EXISTS movement_lineage_links ( id SERIAL PRIMARY KEY, source_movement_id INTEGER NOT NULL REFERENCES art_movements(id) ON DELETE CASCADE, target_movement_id INTEGER NOT NULL REFERENCES art_movements(id) ON DELETE CASCADE, link_year_approx INTEGER, proof_url TEXT NOT NULL, proof_label TEXT, notes TEXT, created_by INTEGER REFERENCES users(id) ON DELETE SET NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), CONSTRAINT movement_lineage_links_distinct CHECK (source_movement_id <> target_movement_id), CONSTRAINT movement_lineage_links_unique UNIQUE (source_movement_id, target_movement_id) ); CREATE INDEX IF NOT EXISTS movement_lineage_links_source_idx ON movement_lineage_links (source_movement_id); CREATE INDEX IF NOT EXISTS movement_lineage_links_target_idx ON movement_lineage_links (target_movement_id); CREATE INDEX IF NOT EXISTS movement_lineage_links_year_idx ON movement_lineage_links (link_year_approx); CREATE OR REPLACE FUNCTION movement_lineage_links_touch_updated_at() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = now(); RETURN NEW; END; $$ LANGUAGE plpgsql; DROP TRIGGER IF EXISTS movement_lineage_links_updated_at ON movement_lineage_links; CREATE TRIGGER movement_lineage_links_updated_at BEFORE UPDATE ON movement_lineage_links FOR EACH ROW EXECUTE PROCEDURE movement_lineage_links_touch_updated_at();