Your music, beautifully tracked. All yours. (coming soon) teal.fm
teal-fm atproto
at main 4.6 kB view raw
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.';