| 1 | -- 1M users
|
|---|
| 2 | INSERT INTO Users (username, email, password, full_name, last_login)
|
|---|
| 3 | SELECT
|
|---|
| 4 | substr(md5(random()::text), 1, 12),
|
|---|
| 5 | substr(md5(random()::text), 1, 10) || '@gmail.com',
|
|---|
| 6 | 'password123',
|
|---|
| 7 | 'John Doe',
|
|---|
| 8 | now()
|
|---|
| 9 | FROM generate_series(1, 1000000)
|
|---|
| 10 | ON CONFLICT DO NOTHING;
|
|---|
| 11 |
|
|---|
| 12 |
|
|---|
| 13 | INSERT INTO Roles(role_name)
|
|---|
| 14 | VALUES ('ADMIN'),
|
|---|
| 15 | ('FREE_LISTENER'),
|
|---|
| 16 | ('PREMIUM_LISTENER'),
|
|---|
| 17 | ('ARTIST'),
|
|---|
| 18 | ('LABEL_ADMIN');
|
|---|
| 19 |
|
|---|
| 20 |
|
|---|
| 21 | INSERT INTO Permissions (action, resource_type, scope) VALUES
|
|---|
| 22 | ('PLAY', 'SONG', 'PUBLIC'),
|
|---|
| 23 | ('PLAY', 'SONG', 'SHARED'),
|
|---|
| 24 | ('PLAY', 'SONG', 'OWN'),
|
|---|
| 25 | ('VIEW', 'SONG', 'PUBLIC'),
|
|---|
| 26 | ('VIEW', 'SONG', 'SHARED'),
|
|---|
| 27 | ('VIEW', 'SONG', 'OWN'),
|
|---|
| 28 | ('CREATE', 'SONG', 'OWN'),
|
|---|
| 29 | ('EDIT', 'SONG', 'OWN'),
|
|---|
| 30 | ('DELETE', 'SONG', 'OWN'),
|
|---|
| 31 | ('EDIT', 'SONG', 'ANY'),
|
|---|
| 32 | ('DELETE', 'SONG', 'ANY'),
|
|---|
| 33 |
|
|---|
| 34 | ('VIEW', 'ALBUM', 'PUBLIC'),
|
|---|
| 35 | ('VIEW', 'ALBUM', 'SHARED'),
|
|---|
| 36 | ('VIEW', 'ALBUM', 'OWN'),
|
|---|
| 37 | ('CREATE', 'ALBUM', 'OWN'),
|
|---|
| 38 | ('EDIT', 'ALBUM', 'OWN'),
|
|---|
| 39 | ('DELETE', 'ALBUM', 'OWN'),
|
|---|
| 40 | ('EDIT', 'ALBUM', 'ANY'),
|
|---|
| 41 | ('DELETE', 'ALBUM', 'ANY'),
|
|---|
| 42 |
|
|---|
| 43 | ('VIEW', 'PLAYLIST', 'PUBLIC'),
|
|---|
| 44 | ('VIEW', 'PLAYLIST', 'SHARED'),
|
|---|
| 45 | ('VIEW', 'PLAYLIST', 'OWN'),
|
|---|
| 46 | ('CREATE', 'PLAYLIST', 'OWN'),
|
|---|
| 47 | ('EDIT', 'PLAYLIST', 'OWN'),
|
|---|
| 48 | ('DELETE', 'PLAYLIST', 'OWN'),
|
|---|
| 49 | ('ADD_SONG', 'PLAYLIST', 'OWN'),
|
|---|
| 50 | ('REMOVE_SONG', 'PLAYLIST', 'OWN'),
|
|---|
| 51 | ('SHARE', 'PLAYLIST', 'OWN'),
|
|---|
| 52 | ('DELETE', 'PLAYLIST', 'ANY');
|
|---|
| 53 |
|
|---|
| 54 | -- this table stores predefined role permissions
|
|---|
| 55 | -- for example
|
|---|
| 56 | -- (p.action = 'EDIT' AND p.resource_type = 'SONG' AND p.scope = 'ANY' AND r.role_name IN ('ADMIN','LABEL_ADMIN')) OR
|
|---|
| 57 | -- means that only admin and label admin users can edit a song with a scope any
|
|---|
| 58 | INSERT INTO role_permissions (role_id, permission_id)
|
|---|
| 59 | SELECT r.id, p.id
|
|---|
| 60 | FROM roles r
|
|---|
| 61 | JOIN permissions p ON (
|
|---|
| 62 | -- SONG
|
|---|
| 63 | (p.action = 'PLAY' AND p.resource_type = 'SONG' AND p.scope = 'PUBLIC' AND r.role_name IN ('ADMIN','ARTIST','LABEL_ADMIN','FREE_LISTENER','PREMIUM_LISTENER')) OR
|
|---|
| 64 | (p.action = 'PLAY' AND p.resource_type = 'SONG' AND p.scope = 'SHARED' AND r.role_name IN ('ADMIN','ARTIST','LABEL_ADMIN')) OR
|
|---|
| 65 | (p.action = 'PLAY' AND p.resource_type = 'SONG' AND p.scope = 'OWN' AND r.role_name IN ('ADMIN','ARTIST','LABEL_ADMIN')) OR
|
|---|
| 66 |
|
|---|
| 67 | (p.action = 'VIEW' AND p.resource_type = 'SONG' AND p.scope = 'PUBLIC' AND r.role_name IN ('ADMIN','ARTIST','LABEL_ADMIN')) OR
|
|---|
| 68 | (p.action = 'VIEW' AND p.resource_type = 'SONG' AND p.scope = 'SHARED' AND r.role_name IN ('ADMIN','ARTIST','LABEL_ADMIN')) OR
|
|---|
| 69 | (p.action = 'VIEW' AND p.resource_type = 'SONG' AND p.scope = 'OWN' AND r.role_name IN ('ADMIN','ARTIST','LABEL_ADMIN')) OR
|
|---|
| 70 |
|
|---|
| 71 | (p.action = 'CREATE' AND p.resource_type = 'SONG' AND p.scope = 'OWN' AND r.role_name IN ('ADMIN','ARTIST','LABEL_ADMIN')) OR
|
|---|
| 72 | (p.action = 'EDIT' AND p.resource_type = 'SONG' AND p.scope = 'OWN' AND r.role_name IN ('ADMIN','ARTIST','LABEL_ADMIN')) OR
|
|---|
| 73 | (p.action = 'DELETE' AND p.resource_type = 'SONG' AND p.scope = 'OWN' AND r.role_name IN ('ADMIN','ARTIST','LABEL_ADMIN')) OR
|
|---|
| 74 |
|
|---|
| 75 | (p.action = 'EDIT' AND p.resource_type = 'SONG' AND p.scope = 'ANY' AND r.role_name IN ('ADMIN','LABEL_ADMIN')) OR
|
|---|
| 76 | (p.action = 'DELETE' AND p.resource_type = 'SONG' AND p.scope = 'ANY' AND r.role_name IN ('ADMIN','LABEL_ADMIN')) OR
|
|---|
| 77 |
|
|---|
| 78 | -- ALBUM
|
|---|
| 79 | (p.action = 'VIEW' AND p.resource_type = 'ALBUM' AND p.scope = 'PUBLIC' AND r.role_name IN ('ADMIN','ARTIST','LABEL_ADMIN','FREE_LISTENER','PREMIUM_LISTENER')) OR
|
|---|
| 80 | (p.action = 'VIEW' AND p.resource_type = 'ALBUM' AND p.scope = 'SHARED' AND r.role_name IN ('ADMIN','ARTIST','LABEL_ADMIN','FREE_LISTENER','PREMIUM_LISTENER')) OR
|
|---|
| 81 | (p.action = 'VIEW' AND p.resource_type = 'ALBUM' AND p.scope = 'OWN' AND r.role_name IN ('ADMIN','ARTIST','LABEL_ADMIN')) OR
|
|---|
| 82 |
|
|---|
| 83 | (p.action = 'CREATE' AND p.resource_type = 'ALBUM' AND p.scope = 'OWN' AND r.role_name IN ('ADMIN','ARTIST','LABEL_ADMIN')) OR
|
|---|
| 84 | (p.action = 'EDIT' AND p.resource_type = 'ALBUM' AND p.scope = 'OWN' AND r.role_name IN ('ADMIN','ARTIST','LABEL_ADMIN')) OR
|
|---|
| 85 | (p.action = 'DELETE' AND p.resource_type = 'ALBUM' AND p.scope = 'OWN' AND r.role_name IN ('ADMIN','ARTIST','LABEL_ADMIN')) OR
|
|---|
| 86 |
|
|---|
| 87 | (p.action = 'EDIT' AND p.resource_type = 'ALBUM' AND p.scope = 'ANY' AND r.role_name IN ('ADMIN','LABEL_ADMIN')) OR
|
|---|
| 88 | (p.action = 'DELETE' AND p.resource_type = 'ALBUM' AND p.scope = 'ANY' AND r.role_name IN ('ADMIN','LABEL_ADMIN')) OR
|
|---|
| 89 |
|
|---|
| 90 | -- PLAYLIST
|
|---|
| 91 | (p.action = 'VIEW' AND p.resource_type = 'PLAYLIST' AND p.scope = 'PUBLIC' AND r.role_name IN ('ADMIN','ARTIST','LABEL_ADMIN','FREE_LISTENER','PREMIUM_LISTENER')) OR
|
|---|
| 92 | (p.action = 'VIEW' AND p.resource_type = 'PLAYLIST' AND p.scope = 'SHARED' AND r.role_name IN ('ADMIN','ARTIST','LABEL_ADMIN','FREE_LISTENER','PREMIUM_LISTENER')) OR
|
|---|
| 93 | (p.action = 'VIEW' AND p.resource_type = 'PLAYLIST' AND p.scope = 'OWN' AND r.role_name IN ('ADMIN','ARTIST','LABEL_ADMIN','FREE_LISTENER','PREMIUM_LISTENER')) OR
|
|---|
| 94 |
|
|---|
| 95 | (p.action = 'CREATE' AND p.resource_type = 'PLAYLIST' AND p.scope = 'OWN' AND r.role_name IN ('ADMIN','ARTIST','LABEL_ADMIN','FREE_LISTENER','PREMIUM_LISTENER')) OR
|
|---|
| 96 | (p.action = 'EDIT' AND p.resource_type = 'PLAYLIST' AND p.scope = 'OWN' AND r.role_name IN ('ADMIN','ARTIST','LABEL_ADMIN','FREE_LISTENER','PREMIUM_LISTENER')) OR
|
|---|
| 97 | (p.action = 'DELETE' AND p.resource_type = 'PLAYLIST' AND p.scope = 'OWN' AND r.role_name IN ('ADMIN','ARTIST','LABEL_ADMIN','FREE_LISTENER','PREMIUM_LISTENER')) OR
|
|---|
| 98 |
|
|---|
| 99 | (p.action = 'ADD_SONG' AND p.resource_type = 'PLAYLIST' AND p.scope = 'OWN' AND r.role_name IN ('ADMIN','ARTIST','LABEL_ADMIN','FREE_LISTENER','PREMIUM_LISTENER')) OR
|
|---|
| 100 | (p.action = 'REMOVE_SONG' AND p.resource_type = 'PLAYLIST' AND p.scope = 'OWN' AND r.role_name IN ('ADMIN','ARTIST','LABEL_ADMIN','FREE_LISTENER','PREMIUM_LISTENER')) OR
|
|---|
| 101 | (p.action = 'SHARE' AND p.resource_type = 'PLAYLIST' AND p.scope = 'OWN' AND r.role_name IN ('ADMIN','ARTIST','LABEL_ADMIN','FREE_LISTENER','PREMIUM_LISTENER')) OR
|
|---|
| 102 |
|
|---|
| 103 | (p.action = 'DELETE' AND p.resource_type = 'PLAYLIST' AND p.scope = 'ANY' AND r.role_name IN ('ADMIN','LABEL_ADMIN'))
|
|---|
| 104 | );
|
|---|
| 105 |
|
|---|
| 106 |
|
|---|
| 107 | -- 375 labels with a random name
|
|---|
| 108 | INSERT INTO Labels (name)
|
|---|
| 109 | SELECT (
|
|---|
| 110 | substr(md5(random()::text), 1, 12)
|
|---|
| 111 | )
|
|---|
| 112 | FROM generate_series(1, 375);
|
|---|
| 113 |
|
|---|
| 114 |
|
|---|
| 115 | -- 100K artists
|
|---|
| 116 | -- user_id-s 0-100.000 are artists
|
|---|
| 117 | INSERT INTO Artists (user_id, display_name)
|
|---|
| 118 | SELECT
|
|---|
| 119 | id,
|
|---|
| 120 | substr(md5(random()::text), 1, 12)
|
|---|
| 121 | FROM Users
|
|---|
| 122 | ORDER BY id
|
|---|
| 123 | LIMIT 100000
|
|---|
| 124 | ON CONFLICT DO NOTHING;
|
|---|
| 125 |
|
|---|
| 126 |
|
|---|
| 127 | -- users
|
|---|
| 128 | -- user_id-s 100.001-100.375 are label admins
|
|---|
| 129 | WITH selected_users AS (
|
|---|
| 130 | SELECT id, row_number() OVER (ORDER BY id) AS rn
|
|---|
| 131 | FROM Users
|
|---|
| 132 | ORDER BY id
|
|---|
| 133 | OFFSET 100000
|
|---|
| 134 | LIMIT 375
|
|---|
| 135 | )
|
|---|
| 136 | INSERT INTO Label_Admins (label_id, user_id)
|
|---|
| 137 | SELECT
|
|---|
| 138 | rn-100000 AS label_id,
|
|---|
| 139 | id AS user_id
|
|---|
| 140 | FROM selected_users
|
|---|
| 141 | ON CONFLICT DO NOTHING;
|
|---|
| 142 |
|
|---|
| 143 |
|
|---|
| 144 | -- add the ARTIST role to users with ids 1-100K
|
|---|
| 145 | -- add the LABEL_ADMIN role to users with ids 100.001-100.375
|
|---|
| 146 | -- add the ADMIN role to user with user_id 1000000
|
|---|
| 147 | -- all other users (100.376-999.999) have either a PREMIUM_LISTENER or FREE_LISTENER role
|
|---|
| 148 |
|
|---|
| 149 | WITH roles_cte AS (
|
|---|
| 150 | SELECT
|
|---|
| 151 | MAX(CASE WHEN role_name = 'ARTIST' THEN id END) AS artist_id,
|
|---|
| 152 | MAX(CASE WHEN role_name = 'LABEL_ADMIN' THEN id END) AS label_admin_id,
|
|---|
| 153 | MAX(CASE WHEN role_name = 'PREMIUM_LISTENER' THEN id END) AS premium_id,
|
|---|
| 154 | MAX(CASE WHEN role_name = 'FREE_LISTENER' THEN id END) AS free_id
|
|---|
| 155 | FROM Roles
|
|---|
| 156 | )
|
|---|
| 157 |
|
|---|
| 158 | INSERT INTO User_Roles (user_id, role_id)
|
|---|
| 159 | SELECT u.id, r.artist_id
|
|---|
| 160 | FROM Users u
|
|---|
| 161 | CROSS JOIN roles_cte r
|
|---|
| 162 | WHERE u.id BETWEEN 1 AND 100000
|
|---|
| 163 |
|
|---|
| 164 | UNION ALL
|
|---|
| 165 |
|
|---|
| 166 | SELECT u.id, r.label_admin_id
|
|---|
| 167 | FROM Users u
|
|---|
| 168 | CROSS JOIN roles_cte r
|
|---|
| 169 | WHERE u.id BETWEEN 100001 AND 100375
|
|---|
| 170 |
|
|---|
| 171 | UNION ALL
|
|---|
| 172 |
|
|---|
| 173 | SELECT 1000000, 1
|
|---|
| 174 |
|
|---|
| 175 | UNION ALL
|
|---|
| 176 |
|
|---|
| 177 | SELECT
|
|---|
| 178 | u.id,
|
|---|
| 179 | CASE
|
|---|
| 180 | WHEN (row_number() OVER (ORDER BY u.id)) % 3 = 0 THEN r.premium_id
|
|---|
| 181 | ELSE r.free_id
|
|---|
| 182 | END
|
|---|
| 183 | FROM Users u
|
|---|
| 184 | CROSS JOIN roles_cte r
|
|---|
| 185 | WHERE u.id BETWEEN 100376 AND 999999;
|
|---|
| 186 |
|
|---|
| 187 | -- 200K random follower-followed pairs with a bias
|
|---|
| 188 | -- most of the followers are listeners and most follow a group of 40 artists to simulate "more popular artists"
|
|---|
| 189 | WITH generated AS (
|
|---|
| 190 | SELECT
|
|---|
| 191 | -- follower
|
|---|
| 192 | CASE
|
|---|
| 193 | WHEN random() < 0.9 THEN
|
|---|
| 194 | floor(random() * (1000000 - 100375))::bigint + 100376
|
|---|
| 195 | ELSE
|
|---|
| 196 | floor(random() * 100000)::bigint + 1
|
|---|
| 197 | END AS follower_user_id,
|
|---|
| 198 |
|
|---|
| 199 | -- followed
|
|---|
| 200 | CASE
|
|---|
| 201 | WHEN random() < 0.85 THEN -- artists
|
|---|
| 202 | CASE
|
|---|
| 203 | WHEN random() < 0.8 THEN
|
|---|
| 204 | -- top 40 artists
|
|---|
| 205 | floor(random() * 40)::bigint + 1
|
|---|
| 206 | ELSE
|
|---|
| 207 | -- remaining
|
|---|
| 208 | floor(random() * (100000 - 40))::bigint + 41
|
|---|
| 209 | END
|
|---|
| 210 | ELSE
|
|---|
| 211 | -- listeners
|
|---|
| 212 | floor(random() * (1000000 - 100375))::bigint + 100376
|
|---|
| 213 | END AS followed_user_id
|
|---|
| 214 |
|
|---|
| 215 | FROM generate_series(1, 200000)
|
|---|
| 216 | )
|
|---|
| 217 |
|
|---|
| 218 | INSERT INTO Follows (follower_user_id, followed_user_id)
|
|---|
| 219 | SELECT DISTINCT *
|
|---|
| 220 | FROM generated
|
|---|
| 221 | WHERE follower_user_id <> followed_user_id
|
|---|
| 222 | LIMIT 100000;
|
|---|
| 223 |
|
|---|
| 224 |
|
|---|
| 225 |
|
|---|
| 226 | -- assign the 30% top-followed artists to labels
|
|---|
| 227 | WITH artist_followers AS (
|
|---|
| 228 | SELECT
|
|---|
| 229 | a.id AS artist_id,
|
|---|
| 230 | COUNT(f.follower_user_id) AS follower_count
|
|---|
| 231 | FROM Artists a
|
|---|
| 232 | LEFT JOIN Follows f
|
|---|
| 233 | ON f.followed_user_id = a.user_id
|
|---|
| 234 | GROUP BY a.id
|
|---|
| 235 | ),
|
|---|
| 236 | ranked AS (
|
|---|
| 237 | SELECT *,
|
|---|
| 238 | NTILE(10) OVER (ORDER BY follower_count DESC) AS popularity_bucket
|
|---|
| 239 | FROM artist_followers
|
|---|
| 240 | ),
|
|---|
| 241 | selected AS (
|
|---|
| 242 | SELECT *
|
|---|
| 243 | FROM ranked
|
|---|
| 244 | WHERE popularity_bucket <= 3
|
|---|
| 245 | )
|
|---|
| 246 | INSERT INTO Artist_Labels (artist_id, label_id, active, start_date, end_date)
|
|---|
| 247 | SELECT
|
|---|
| 248 | s.artist_id,
|
|---|
| 249 | s.artist_id%375+1,
|
|---|
| 250 | TRUE,
|
|---|
| 251 | CURRENT_DATE - (random() * 3650)::int,
|
|---|
| 252 | NULL
|
|---|
| 253 | FROM selected s;
|
|---|
| 254 |
|
|---|
| 255 |
|
|---|
| 256 | -- first each artist is enriched with popularity and context about the labels
|
|---|
| 257 | WITH artist_followers AS (
|
|---|
| 258 | SELECT
|
|---|
| 259 | a.id AS artist_id,
|
|---|
| 260 | a.user_id,
|
|---|
| 261 | COUNT(f.follower_user_id) AS follower_count
|
|---|
| 262 | FROM Artists a
|
|---|
| 263 | LEFT JOIN Follows f
|
|---|
| 264 | ON f.followed_user_id = a.user_id
|
|---|
| 265 | GROUP BY a.id, a.user_id
|
|---|
| 266 | ),
|
|---|
| 267 | artist_labels_active AS (
|
|---|
| 268 | SELECT al.artist_id, al.label_id
|
|---|
| 269 | FROM Artist_Labels al
|
|---|
| 270 | WHERE al.active = TRUE
|
|---|
| 271 | ),
|
|---|
| 272 | artist_full AS (
|
|---|
| 273 | SELECT
|
|---|
| 274 | af.artist_id,
|
|---|
| 275 | af.follower_count,
|
|---|
| 276 | al.label_id,
|
|---|
| 277 | la.id AS label_admin_id
|
|---|
| 278 | FROM artist_followers af
|
|---|
| 279 | LEFT JOIN artist_labels_active al
|
|---|
| 280 | ON af.artist_id = al.artist_id
|
|---|
| 281 | LEFT JOIN Label_Admins la
|
|---|
| 282 | ON la.label_id = al.label_id
|
|---|
| 283 | ),
|
|---|
| 284 | expanded AS (
|
|---|
| 285 | SELECT
|
|---|
| 286 | af.*,
|
|---|
| 287 | generate_series(1,
|
|---|
| 288 | CASE
|
|---|
| 289 | WHEN af.follower_count > 1200 THEN 100
|
|---|
| 290 | ELSE (floor(random() * 40))::int -- 0-39
|
|---|
| 291 | END
|
|---|
| 292 | ) AS song_num
|
|---|
| 293 | FROM artist_full af
|
|---|
| 294 | ),
|
|---|
| 295 | numbered AS (
|
|---|
| 296 | SELECT
|
|---|
| 297 | *,
|
|---|
| 298 | ROW_NUMBER() OVER (ORDER BY artist_id, song_num) AS rn,
|
|---|
| 299 | COUNT(*) OVER () AS total_cnt
|
|---|
| 300 | FROM expanded
|
|---|
| 301 | )
|
|---|
| 302 | INSERT INTO Songs (
|
|---|
| 303 | title,
|
|---|
| 304 | visibility,
|
|---|
| 305 | owner_artist_id,
|
|---|
| 306 | published_by_artist_id,
|
|---|
| 307 | published_by_label_admin_id,
|
|---|
| 308 | genre
|
|---|
| 309 | )
|
|---|
| 310 | SELECT
|
|---|
| 311 | 'Song_' || artist_id || '_' || song_num,
|
|---|
| 312 | -- first 25% of songs are private, the rest public
|
|---|
| 313 | CASE
|
|---|
| 314 | WHEN rn <= total_cnt * 0.25 THEN 'PRIVATE'
|
|---|
| 315 | ELSE 'PUBLIC'
|
|---|
| 316 | END,
|
|---|
| 317 | artist_id,
|
|---|
| 318 | CASE
|
|---|
| 319 | WHEN label_id IS NULL THEN artist_id
|
|---|
| 320 | ELSE NULL
|
|---|
| 321 | END,
|
|---|
| 322 | CASE
|
|---|
| 323 | WHEN label_id IS NOT NULL THEN label_admin_id
|
|---|
| 324 | ELSE NULL
|
|---|
| 325 | END,
|
|---|
| 326 | NULL
|
|---|
| 327 | FROM numbered;
|
|---|
| 328 |
|
|---|
| 329 | -- assign songs to albums by giving more albums to more popular artists
|
|---|
| 330 | WITH artist_followers AS (
|
|---|
| 331 | SELECT
|
|---|
| 332 | a.id AS artist_id,
|
|---|
| 333 | COUNT(f.follower_user_id) AS follower_count
|
|---|
| 334 | FROM Artists a
|
|---|
| 335 | LEFT JOIN Follows f ON f.followed_user_id = a.user_id
|
|---|
| 336 | GROUP BY a.id
|
|---|
| 337 | ),
|
|---|
| 338 | artist_labels_active AS (
|
|---|
| 339 | SELECT artist_id, label_id
|
|---|
| 340 | FROM Artist_Labels
|
|---|
| 341 | WHERE active = TRUE
|
|---|
| 342 | ),
|
|---|
| 343 | artist_full AS (
|
|---|
| 344 | SELECT
|
|---|
| 345 | af.artist_id,
|
|---|
| 346 | af.follower_count,
|
|---|
| 347 | al.label_id,
|
|---|
| 348 | la.id AS label_admin_id
|
|---|
| 349 | FROM artist_followers af
|
|---|
| 350 | LEFT JOIN artist_labels_active al ON af.artist_id = al.artist_id
|
|---|
| 351 | LEFT JOIN Label_Admins la ON la.label_id = al.label_id
|
|---|
| 352 | ),
|
|---|
| 353 | expanded AS (
|
|---|
| 354 | SELECT
|
|---|
| 355 | af.*,
|
|---|
| 356 | generate_series(
|
|---|
| 357 | 1,
|
|---|
| 358 | CASE
|
|---|
| 359 | WHEN af.follower_count > 1200
|
|---|
| 360 | THEN (5 + floor(random() * 6))::int -- 5–10
|
|---|
| 361 | ELSE (1 + floor(random() * 3))::int -- 1–3
|
|---|
| 362 | END
|
|---|
| 363 | ) AS album_num
|
|---|
| 364 | FROM artist_full af
|
|---|
| 365 | )
|
|---|
| 366 | INSERT INTO Albums (
|
|---|
| 367 | title,
|
|---|
| 368 | visibility,
|
|---|
| 369 | owner_artist_id,
|
|---|
| 370 | published_by_artist_id,
|
|---|
| 371 | published_by_label_admin_id
|
|---|
| 372 | )
|
|---|
| 373 | SELECT
|
|---|
| 374 | 'Album_' || artist_id || '_' || album_num,
|
|---|
| 375 | (ARRAY['PUBLIC','PRIVATE'])[floor(random()*2)+1],
|
|---|
| 376 | artist_id,
|
|---|
| 377 | -- solo artists publish themselves
|
|---|
| 378 | CASE
|
|---|
| 379 | WHEN label_id IS NULL THEN artist_id
|
|---|
| 380 | ELSE NULL
|
|---|
| 381 | END,
|
|---|
| 382 | -- label artists published by label
|
|---|
| 383 | CASE
|
|---|
| 384 | WHEN label_id IS NOT NULL THEN label_admin_id
|
|---|
| 385 | ELSE NULL
|
|---|
| 386 | END
|
|---|
| 387 |
|
|---|
| 388 | FROM expanded
|
|---|
| 389 | WHERE (label_id IS NULL OR label_admin_id IS NOT NULL);
|
|---|
| 390 |
|
|---|
| 391 |
|
|---|
| 392 | WITH album_requirements AS (
|
|---|
| 393 | -- how many tracks each album needs and get a sequence for the artist's albums
|
|---|
| 394 | SELECT
|
|---|
| 395 | id AS album_id,
|
|---|
| 396 | owner_artist_id,
|
|---|
| 397 | (5 + floor(random() * 12))::int AS needed,
|
|---|
| 398 | ROW_NUMBER() OVER (PARTITION BY owner_artist_id ORDER BY id) as album_seq
|
|---|
| 399 | FROM Albums
|
|---|
| 400 | ),
|
|---|
| 401 | album_ranges AS (
|
|---|
| 402 | -- create a start and end song index for each album
|
|---|
| 403 | -- e.g. Album 1: 1-10, Album 2: 11-25, etc.
|
|---|
| 404 | SELECT
|
|---|
| 405 | *,
|
|---|
| 406 | COALESCE(SUM(needed) OVER (PARTITION BY owner_artist_id ORDER BY album_seq ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING), 0) + 1 AS range_start,
|
|---|
| 407 | SUM(needed) OVER (PARTITION BY owner_artist_id ORDER BY album_seq) AS range_end
|
|---|
| 408 | FROM album_requirements
|
|---|
| 409 | ),
|
|---|
| 410 | artist_songs AS (
|
|---|
| 411 | -- rank every song globally per artist
|
|---|
| 412 | SELECT
|
|---|
| 413 | id AS song_id,
|
|---|
| 414 | owner_artist_id,
|
|---|
| 415 | ROW_NUMBER() OVER (PARTITION BY owner_artist_id ORDER BY random()) as global_song_rank
|
|---|
| 416 | FROM Songs
|
|---|
| 417 | ),
|
|---|
| 418 | final_assignment AS (
|
|---|
| 419 | -- match the song rank to the album range
|
|---|
| 420 | -- this ensures a song with rank 5 ONLY fits in the album covering range 1-10
|
|---|
| 421 | SELECT
|
|---|
| 422 | r.album_id,
|
|---|
| 423 | s.song_id,
|
|---|
| 424 | (s.global_song_rank - r.range_start + 1) AS track_number,
|
|---|
| 425 | r.needed
|
|---|
| 426 | FROM album_ranges r
|
|---|
| 427 | JOIN artist_songs s
|
|---|
| 428 | ON s.owner_artist_id = r.owner_artist_id
|
|---|
| 429 | AND s.global_song_rank BETWEEN r.range_start AND r.range_end
|
|---|
| 430 | ),
|
|---|
| 431 | validation AS (
|
|---|
| 432 | -- count how many tracks we actually found for each album
|
|---|
| 433 | SELECT
|
|---|
| 434 | *,
|
|---|
| 435 | COUNT(*) OVER (PARTITION BY album_id) as actual_count
|
|---|
| 436 | FROM final_assignment
|
|---|
| 437 | )
|
|---|
| 438 |
|
|---|
| 439 | INSERT INTO Album_Tracks (album_id, song_id, track_number)
|
|---|
| 440 | SELECT album_id, song_id, track_number
|
|---|
| 441 | FROM validation
|
|---|
| 442 | WHERE actual_count >= 5;
|
|---|
| 443 |
|
|---|
| 444 |
|
|---|
| 445 | -- 10K playlists
|
|---|
| 446 | INSERT INTO Playlists (playlist_name, visibility, creator_user_id)
|
|---|
| 447 | SELECT
|
|---|
| 448 | 'Playlist_' || (floor(random() * 899624)::int + 100376) || '_' || gs,
|
|---|
| 449 | CASE WHEN random() < 0.7 THEN 'PUBLIC' ELSE 'PRIVATE' END,
|
|---|
| 450 | floor(random() * 899624)::int + 100376
|
|---|
| 451 | FROM generate_series(1, 10000) gs;
|
|---|
| 452 |
|
|---|
| 453 | -- insert 5K records into Saved_Playlists
|
|---|
| 454 | -- 1-20 saves per playlist
|
|---|
| 455 | WITH non_artist_users AS (
|
|---|
| 456 | SELECT u.id
|
|---|
| 457 | FROM Users u
|
|---|
| 458 | LEFT JOIN Artists a ON u.id = a.user_id
|
|---|
| 459 | WHERE a.user_id IS NULL
|
|---|
| 460 | ),
|
|---|
| 461 |
|
|---|
| 462 | -- shuffle users once
|
|---|
| 463 | shuffled_users AS (
|
|---|
| 464 | SELECT id, row_number() OVER () AS rn
|
|---|
| 465 | FROM non_artist_users
|
|---|
| 466 | ORDER BY random()
|
|---|
| 467 | ),
|
|---|
| 468 |
|
|---|
| 469 | user_count AS (
|
|---|
| 470 | SELECT count(*) AS cnt FROM shuffled_users
|
|---|
| 471 | ),
|
|---|
| 472 |
|
|---|
| 473 | playlist_sample AS (
|
|---|
| 474 | SELECT id AS playlist_id, creator_user_id
|
|---|
| 475 | FROM Playlists
|
|---|
| 476 | ORDER BY random()
|
|---|
| 477 | LIMIT 5000
|
|---|
| 478 | ),
|
|---|
| 479 |
|
|---|
| 480 |
|
|---|
| 481 | -- todo: check logic!
|
|---|
| 482 | expanded AS (
|
|---|
| 483 | SELECT
|
|---|
| 484 | p.playlist_id,
|
|---|
| 485 | p.creator_user_id,
|
|---|
| 486 | generate_series(
|
|---|
| 487 | 1, GREATEST(
|
|---|
| 488 | 1, LEAST(20, ((random() + random() + random()) * 5)::int
|
|---|
| 489 | )
|
|---|
| 490 | )
|
|---|
| 491 | ) AS save_instance
|
|---|
| 492 | FROM playlist_sample p
|
|---|
| 493 | ),
|
|---|
| 494 |
|
|---|
| 495 | expanded_numbered AS (
|
|---|
| 496 | SELECT
|
|---|
| 497 | e.*,
|
|---|
| 498 | row_number() OVER () AS rn
|
|---|
| 499 | FROM expanded e
|
|---|
| 500 | ),
|
|---|
| 501 | assigned AS (
|
|---|
| 502 | SELECT
|
|---|
| 503 | e.playlist_id,
|
|---|
| 504 | u.id AS saved_by
|
|---|
| 505 | FROM expanded_numbered e
|
|---|
| 506 | JOIN user_count uc ON TRUE
|
|---|
| 507 | JOIN shuffled_users u
|
|---|
| 508 | ON u.rn = ((e.rn % uc.cnt) + 1)
|
|---|
| 509 | WHERE u.id <> e.creator_user_id
|
|---|
| 510 | )
|
|---|
| 511 | INSERT INTO Saved_Playlists (playlist_id, saved_by)
|
|---|
| 512 | SELECT DISTINCT playlist_id, saved_by
|
|---|
| 513 | FROM assigned
|
|---|
| 514 | LIMIT 5000;
|
|---|
| 515 |
|
|---|
| 516 | -- assign ~125K songs to playlists by sampling random songs
|
|---|
| 517 | INSERT INTO playlist_tracks (playlist_id, song_id)
|
|---|
| 518 | SELECT p.id,
|
|---|
| 519 | s.id
|
|---|
| 520 | FROM Playlists p
|
|---|
| 521 | CROSS JOIN LATERAL (
|
|---|
| 522 | SELECT id
|
|---|
| 523 | FROM Songs TABLESAMPLE SYSTEM (0.5) -- sample ~0.5% (~10k rows)
|
|---|
| 524 | ORDER BY random() * (1.0 / sqrt(id)) + p.id * 0 -- p.id * 0 used for forcing postgres to recompute this instead of reusing the same result
|
|---|
| 525 | LIMIT (5 + floor(random() * 16))
|
|---|
| 526 | ) s;
|
|---|
| 527 |
|
|---|
| 528 | -- 1M reviews
|
|---|
| 529 | -- run 10x for 10M reviews
|
|---|
| 530 | WITH song_count AS (
|
|---|
| 531 | SELECT COUNT(*) AS cnt FROM Songs
|
|---|
| 532 | )
|
|---|
| 533 | INSERT INTO Reviews (user_id, song_id, grade)
|
|---|
| 534 | SELECT
|
|---|
| 535 | (100376 + floor(random() * (999999 - 100376 + 1)))::bigint AS user_id,
|
|---|
| 536 | (1 + floor(random() * sc.cnt))::bigint AS song_id,
|
|---|
| 537 | (1 + floor(random() * 5))::int AS grade
|
|---|
| 538 | FROM generate_series(1, 1000000),
|
|---|
| 539 | song_count sc;
|
|---|
| 540 |
|
|---|
| 541 |
|
|---|
| 542 | -- 1M playback sessions
|
|---|
| 543 | -- top ~5% of songs are more favored to simulate "more popular" songs, a medium tier gets moderate attention while the rest receive very few plays
|
|---|
| 544 | -- timestamps are randomly distributed across the last 6 months and durations are 20-50s
|
|---|
| 545 | -- only public songs are eligible for playback sessions
|
|---|
| 546 |
|
|---|
| 547 | -- run 10x for 10M
|
|---|
| 548 |
|
|---|
| 549 | WITH public_songs AS (
|
|---|
| 550 | SELECT id, ROW_NUMBER() OVER (ORDER BY id) AS rn
|
|---|
| 551 | FROM Songs
|
|---|
| 552 | WHERE visibility = 'PUBLIC'
|
|---|
| 553 | ),
|
|---|
| 554 | public_song_count AS (
|
|---|
| 555 | SELECT COUNT(*) AS cnt FROM public_songs
|
|---|
| 556 | ),
|
|---|
| 557 | generated AS (
|
|---|
| 558 | SELECT
|
|---|
| 559 | t.user_id,
|
|---|
| 560 | ps.id AS song_id,
|
|---|
| 561 | t.started_at,
|
|---|
| 562 | t.listened_ms,
|
|---|
| 563 | t.listened_ms AS last_position_ms
|
|---|
| 564 | FROM (
|
|---|
| 565 | SELECT
|
|---|
| 566 | (100376 + floor(random() * (999999 - 100376 + 1)))::bigint AS user_id,
|
|---|
| 567 |
|
|---|
| 568 | CASE
|
|---|
| 569 | WHEN random() < 0.6 THEN
|
|---|
| 570 | floor(random() * (cnt * 0.05))::bigint + 1
|
|---|
| 571 | WHEN random() < 0.85 THEN
|
|---|
| 572 | floor(random() * (cnt * 0.15))::bigint + (cnt * 0.05)::bigint + 1
|
|---|
| 573 | ELSE
|
|---|
| 574 | floor(random() * (cnt * 0.80))::bigint + (cnt * 0.20)::bigint + 1
|
|---|
| 575 | END AS song_rn,
|
|---|
| 576 |
|
|---|
| 577 | NOW() - (random() * INTERVAL '6 months') AS started_at,
|
|---|
| 578 |
|
|---|
| 579 | -- computed once per row
|
|---|
| 580 | (20000 + floor(random() * 31000))::int AS listened_ms
|
|---|
| 581 |
|
|---|
| 582 | FROM generate_series(1, 1000000), public_song_count
|
|---|
| 583 | ) t
|
|---|
| 584 | JOIN public_songs ps ON ps.rn = t.song_rn
|
|---|
| 585 | )
|
|---|
| 586 | INSERT INTO Playback_Sessions (
|
|---|
| 587 | user_id,
|
|---|
| 588 | song_id,
|
|---|
| 589 | started_at,
|
|---|
| 590 | listened_ms,
|
|---|
| 591 | last_position_ms
|
|---|
| 592 | )
|
|---|
| 593 | SELECT *
|
|---|
| 594 | FROM generated;
|
|---|
| 595 |
|
|---|
| 596 |
|
|---|
| 597 | -- all sessions that ran for more than 30s are inserted into the songs stream table
|
|---|
| 598 | -- this is business logic that will later be enforced with a trigger or a job
|
|---|
| 599 | INSERT INTO Song_Streams (
|
|---|
| 600 | playback_session_id,
|
|---|
| 601 | song_id,
|
|---|
| 602 | streamed_at,
|
|---|
| 603 | user_id
|
|---|
| 604 | )
|
|---|
| 605 | SELECT
|
|---|
| 606 | ps.id,
|
|---|
| 607 | ps.song_id,
|
|---|
| 608 | ps.started_at,
|
|---|
| 609 | ps.user_id
|
|---|
| 610 | FROM Playback_Sessions ps
|
|---|
| 611 | WHERE ps.listened_ms >= 30000;
|
|---|
| 612 |
|
|---|
| 613 | -- example song relationship
|
|---|
| 614 | insert into song_relationships (source_song_id, target_song_id, relationship_type)
|
|---|
| 615 | VALUES (1951937, 1951936, 'REMIX'),
|
|---|
| 616 | (1, 2, 'REMIX');
|
|---|
| 617 |
|
|---|
| 618 | -- example song contribution
|
|---|
| 619 | insert into song_contributors (song_id, artist_id, role, credit_order)
|
|---|
| 620 | VALUES
|
|---|
| 621 | (82183, 71535, 'BACK VOCAL', 2),
|
|---|
| 622 | (82183, 84285, 'PRODUCER', 3);
|
|---|
| 623 |
|
|---|
| 624 | -- create shared playlist for user 100002
|
|---|
| 625 | insert into playlists (visibility, creator_user_id, playlist_name, description)
|
|---|
| 626 | values ('SHARED', 100002, 'shared playlist', 'this is a shared playlist');
|
|---|
| 627 |
|
|---|
| 628 | -- add some songs to the new shared playlist
|
|---|
| 629 | insert into playlist_tracks (song_id, playlist_id)
|
|---|
| 630 | VALUES (1, 10001), (2, 10001), (3, 10001);
|
|---|
| 631 |
|
|---|
| 632 | -- give VIEW PLAYLIST access to user 100003
|
|---|
| 633 | insert into resource_shares (playlist_id, user_id, permission_id)
|
|---|
| 634 | values (10001, 100003, 21);
|
|---|