-- Polymorphic influence sources: painting, artist, or movement → a painting CREATE TABLE IF NOT EXISTS painting_influence_sources ( id SERIAL PRIMARY KEY, painting_id INTEGER NOT NULL REFERENCES paintings(id) ON DELETE CASCADE, source_type VARCHAR(20) NOT NULL CHECK (source_type IN ('painting', 'artist', 'movement')), source_painting_id INTEGER REFERENCES paintings(id) ON DELETE CASCADE, source_artist_id INTEGER REFERENCES artists(id) ON DELETE CASCADE, source_movement_id INTEGER REFERENCES art_movements(id) ON DELETE CASCADE, period_note VARCHAR(240), period_start_year INTEGER, period_end_year INTEGER, notes TEXT, source VARCHAR(500), aspects TEXT, quote TEXT, source_author VARCHAR(200), source_url VARCHAR(500), discovered_via VARCHAR(120), confidence VARCHAR(20) NOT NULL DEFAULT 'curated', CONSTRAINT painting_influence_sources_source_shape CHECK ( (source_type = 'painting' AND source_painting_id IS NOT NULL AND source_artist_id IS NULL AND source_movement_id IS NULL) OR (source_type = 'artist' AND source_artist_id IS NOT NULL AND source_painting_id IS NULL AND source_movement_id IS NULL) OR (source_type = 'movement' AND source_movement_id IS NOT NULL AND source_painting_id IS NULL AND source_artist_id IS NULL) ) ); CREATE UNIQUE INDEX IF NOT EXISTS painting_influence_sources_unique ON painting_influence_sources ( painting_id, source_type, COALESCE(source_painting_id, 0), COALESCE(source_artist_id, 0), COALESCE(source_movement_id, 0) ); CREATE INDEX IF NOT EXISTS painting_influence_sources_painting_idx ON painting_influence_sources (painting_id); CREATE INDEX IF NOT EXISTS painting_influence_sources_artist_idx ON painting_influence_sources (source_artist_id); CREATE INDEX IF NOT EXISTS painting_influence_sources_movement_idx ON painting_influence_sources (source_movement_id); -- Backfill legacy painting-to-painting edges INSERT INTO painting_influence_sources ( painting_id, source_type, source_painting_id, notes, source, aspects, quote, source_author, source_url, confidence ) SELECT pi.painting_id, 'painting', pi.influenced_by_painting_id, pi.notes, pi.source, pi.aspects, pi.quote, pi.source_author, pi.source_url, 'curated' FROM painting_influences pi ON CONFLICT DO NOTHING;