1-- Migration to support artists without MusicBrainz IDs
2-- This allows the system to comply with the Teal lexicon where only trackName is required
3
4-- Add a field to plays table to store raw artist names for records without MBIDs
5ALTER TABLE plays ADD COLUMN artist_names_raw JSONB;
6
7-- Create a new artists table that doesn't require MBID as primary key
8CREATE TABLE artists_extended (
9 id SERIAL PRIMARY KEY,
10 mbid UUID UNIQUE, -- Optional MusicBrainz ID
11 name TEXT NOT NULL,
12 name_normalized TEXT GENERATED ALWAYS AS (LOWER(TRIM(name))) STORED,
13 play_count INTEGER DEFAULT 0,
14 created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
15 updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
16);
17
18-- Create index for efficient lookups
19CREATE INDEX idx_artists_extended_mbid ON artists_extended (mbid) WHERE mbid IS NOT NULL;
20CREATE INDEX idx_artists_extended_name_normalized ON artists_extended (name_normalized);
21CREATE UNIQUE INDEX idx_artists_extended_name_unique ON artists_extended (name_normalized) WHERE mbid IS NULL;
22
23-- Create a new junction table that can handle both MBID and non-MBID artists
24CREATE TABLE play_to_artists_extended (
25 play_uri TEXT NOT NULL REFERENCES plays(uri),
26 artist_id INTEGER NOT NULL REFERENCES artists_extended(id),
27 artist_name TEXT NOT NULL, -- Denormalized for performance
28 PRIMARY KEY (play_uri, artist_id)
29);
30
31CREATE INDEX idx_play_to_artists_extended_artist ON play_to_artists_extended (artist_id);
32
33-- Migrate existing data from old tables to new structure
34INSERT INTO artists_extended (mbid, name, play_count)
35SELECT mbid, name, play_count FROM artists;
36
37INSERT INTO play_to_artists_extended (play_uri, artist_id, artist_name)
38SELECT
39 pta.play_uri,
40 ae.id,
41 pta.artist_name
42FROM play_to_artists pta
43JOIN artists_extended ae ON ae.mbid = pta.artist_mbid;
44
45-- Update materialized views to use new structure
46DROP MATERIALIZED VIEW IF EXISTS mv_artist_play_counts;
47CREATE MATERIALIZED VIEW mv_artist_play_counts AS
48SELECT
49 ae.id AS artist_id,
50 ae.mbid AS artist_mbid,
51 ae.name AS artist_name,
52 COUNT(p.uri) AS play_count
53FROM
54 artists_extended ae
55 LEFT JOIN play_to_artists_extended ptae ON ae.id = ptae.artist_id
56 LEFT JOIN plays p ON p.uri = ptae.play_uri
57GROUP BY
58 ae.id, ae.mbid, ae.name;
59
60CREATE UNIQUE INDEX idx_mv_artist_play_counts_new ON mv_artist_play_counts (artist_id);
61
62-- Update other materialized views that reference artists
63DROP MATERIALIZED VIEW IF EXISTS mv_top_artists_30days;
64CREATE MATERIALIZED VIEW mv_top_artists_30days AS
65SELECT
66 ae.id AS artist_id,
67 ae.mbid AS artist_mbid,
68 ae.name AS artist_name,
69 COUNT(p.uri) AS play_count
70FROM artists_extended ae
71INNER JOIN play_to_artists_extended ptae ON ae.id = ptae.artist_id
72INNER JOIN plays p ON p.uri = ptae.play_uri
73WHERE p.played_time >= NOW() - INTERVAL '30 days'
74GROUP BY ae.id, ae.mbid, ae.name
75ORDER BY COUNT(p.uri) DESC;
76
77DROP MATERIALIZED VIEW IF EXISTS mv_top_artists_for_user_30days;
78CREATE MATERIALIZED VIEW mv_top_artists_for_user_30days AS
79SELECT
80 prof.did,
81 ae.id AS artist_id,
82 ae.mbid AS artist_mbid,
83 ae.name AS artist_name,
84 COUNT(p.uri) AS play_count
85FROM artists_extended ae
86INNER JOIN play_to_artists_extended ptae ON ae.id = ptae.artist_id
87INNER JOIN plays p ON p.uri = ptae.play_uri
88INNER JOIN profiles prof ON prof.did = p.did
89WHERE p.played_time >= NOW() - INTERVAL '30 days'
90GROUP BY prof.did, ae.id, ae.mbid, ae.name
91ORDER BY COUNT(p.uri) DESC;
92
93DROP MATERIALIZED VIEW IF EXISTS mv_top_artists_for_user_7days;
94CREATE MATERIALIZED VIEW mv_top_artists_for_user_7days AS
95SELECT
96 prof.did,
97 ae.id AS artist_id,
98 ae.mbid AS artist_mbid,
99 ae.name AS artist_name,
100 COUNT(p.uri) AS play_count
101FROM artists_extended ae
102INNER JOIN play_to_artists_extended ptae ON ae.id = ptae.artist_id
103INNER JOIN plays p ON p.uri = ptae.play_uri
104INNER JOIN profiles prof ON prof.did = p.did
105WHERE p.played_time >= NOW() - INTERVAL '7 days'
106GROUP BY prof.did, ae.id, ae.mbid, ae.name
107ORDER BY COUNT(p.uri) DESC;
108
109-- Comment explaining the migration strategy
110COMMENT ON TABLE artists_extended IS 'Extended artists table that supports both MusicBrainz and non-MusicBrainz artists. Uses serial ID as primary key with optional MBID.';
111COMMENT ON TABLE play_to_artists_extended IS 'Junction table linking plays to artists using the new artists_extended table structure.';
112COMMENT ON COLUMN plays.artist_names_raw IS 'Raw artist names as JSON array for plays without MusicBrainz data, used as fallback when artist relationships cannot be established.';