diff --git a/internal/db/migrations/0009_similarity.down.sql b/internal/db/migrations/0009_similarity.down.sql new file mode 100644 index 00000000..b6718a77 --- /dev/null +++ b/internal/db/migrations/0009_similarity.down.sql @@ -0,0 +1,2 @@ +DROP TABLE IF EXISTS artist_similarity; +DROP TABLE IF EXISTS track_similarity; diff --git a/internal/db/migrations/0009_similarity.up.sql b/internal/db/migrations/0009_similarity.up.sql new file mode 100644 index 00000000..8110a2bd --- /dev/null +++ b/internal/db/migrations/0009_similarity.up.sql @@ -0,0 +1,33 @@ +-- M4b: ListenBrainz similarity ingest. Two symmetric tables — tracks and +-- artists — both keyed by (a_id, b_id, source) so the schema reserves room +-- for additional similarity sources (musicbrainz_tag, user_cooccurrence) +-- alongside listenbrainz. M4b only writes 'listenbrainz' rows. +-- +-- The (a_id, score DESC) index satisfies M4c's hot path: "for seed S, give +-- me top-N similar tracks/artists descending by score." + +CREATE TABLE track_similarity ( + track_a_id uuid NOT NULL REFERENCES tracks(id) ON DELETE CASCADE, + track_b_id uuid NOT NULL REFERENCES tracks(id) ON DELETE CASCADE, + score DOUBLE PRECISION NOT NULL, + source TEXT NOT NULL CHECK (source IN ('listenbrainz', 'musicbrainz_tag', 'user_cooccurrence')), + fetched_at timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY (track_a_id, track_b_id, source), + CHECK (track_a_id <> track_b_id) +); + +CREATE INDEX track_similarity_a_score_idx + ON track_similarity (track_a_id, score DESC); + +CREATE TABLE artist_similarity ( + artist_a_id uuid NOT NULL REFERENCES artists(id) ON DELETE CASCADE, + artist_b_id uuid NOT NULL REFERENCES artists(id) ON DELETE CASCADE, + score DOUBLE PRECISION NOT NULL, + source TEXT NOT NULL CHECK (source IN ('listenbrainz', 'musicbrainz_tag', 'user_cooccurrence')), + fetched_at timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY (artist_a_id, artist_b_id, source), + CHECK (artist_a_id <> artist_b_id) +); + +CREATE INDEX artist_similarity_a_score_idx + ON artist_similarity (artist_a_id, score DESC);