From fc2d35d33c2baf9275b085f8efaeadc6752b47a0 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Tue, 28 Apr 2026 19:57:02 -0400 Subject: [PATCH] feat(db): add migration 0009 for similarity (track_similarity + artist_similarity) --- .../db/migrations/0009_similarity.down.sql | 2 ++ internal/db/migrations/0009_similarity.up.sql | 33 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 internal/db/migrations/0009_similarity.down.sql create mode 100644 internal/db/migrations/0009_similarity.up.sql 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);