// Code generated by sqlc. DO NOT EDIT. // versions: // sqlc v1.31.1 // source: discover.sql package dbq import ( "context" "github.com/jackc/pgx/v5/pgtype" ) const listCrossUserLikedTracksForDiscover = `-- name: ListCrossUserLikedTracksForDiscover :many SELECT t.id, t.album_id, t.artist_id FROM general_likes gl JOIN tracks t ON t.id = gl.track_id WHERE gl.user_id != $1 AND NOT EXISTS ( SELECT 1 FROM play_events pe WHERE pe.user_id = $1 AND pe.track_id = t.id AND pe.was_skipped = false ) AND NOT EXISTS ( SELECT 1 FROM general_likes ml WHERE ml.user_id = $1 AND ml.track_id = t.id ) AND NOT EXISTS ( SELECT 1 FROM lidarr_quarantine q WHERE q.user_id = $1 AND q.track_id = t.id ) GROUP BY t.id, t.album_id, t.artist_id ORDER BY md5(t.id::text || $2::text) LIMIT 60 ` type ListCrossUserLikedTracksForDiscoverParams struct { UserID pgtype.UUID Column2 string } type ListCrossUserLikedTracksForDiscoverRow struct { ID pgtype.UUID AlbumID pgtype.UUID ArtistID pgtype.UUID } // Tracks any OTHER user has liked, that this user hasn't played, // liked, or had quarantined. On single-user servers this returns 0 // rows (the caller's slot redistribution rolls the deficit into the // other two buckets). // $1 = user_id, $2 = date string for md5 ordering. // // GROUP BY (not SELECT DISTINCT) so the md5 ORDER BY expression need // not appear in the select list — Postgres rejects DISTINCT + ORDER BY // by-expression at plan time (SQLSTATE 42P10), which previously caused // the entire Discover build to fail silently. func (q *Queries) ListCrossUserLikedTracksForDiscover(ctx context.Context, arg ListCrossUserLikedTracksForDiscoverParams) ([]ListCrossUserLikedTracksForDiscoverRow, error) { rows, err := q.db.Query(ctx, listCrossUserLikedTracksForDiscover, arg.UserID, arg.Column2) if err != nil { return nil, err } defer rows.Close() var items []ListCrossUserLikedTracksForDiscoverRow for rows.Next() { var i ListCrossUserLikedTracksForDiscoverRow if err := rows.Scan(&i.ID, &i.AlbumID, &i.ArtistID); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listDormantArtistTracksForDiscover = `-- name: ListDormantArtistTracksForDiscover :many WITH user_artist_counts AS ( SELECT t.artist_id, COUNT(*) AS play_count FROM play_events pe JOIN tracks t ON t.id = pe.track_id WHERE pe.user_id = $1 AND pe.was_skipped = false GROUP BY t.artist_id ), dormant_artists AS ( SELECT a.id FROM artists a LEFT JOIN user_artist_counts uac ON uac.artist_id = a.id WHERE COALESCE(uac.play_count, 0) < 10 ) SELECT t.id, t.album_id, t.artist_id FROM tracks t JOIN dormant_artists da ON da.id = t.artist_id WHERE NOT EXISTS ( SELECT 1 FROM play_events pe WHERE pe.user_id = $1 AND pe.track_id = t.id AND pe.was_skipped = false ) AND NOT EXISTS ( SELECT 1 FROM general_likes gl WHERE gl.user_id = $1 AND gl.track_id = t.id ) AND NOT EXISTS ( SELECT 1 FROM lidarr_quarantine q WHERE q.user_id = $1 AND q.track_id = t.id ) ORDER BY md5(t.id::text || $2::text) LIMIT 80 ` type ListDormantArtistTracksForDiscoverParams struct { UserID pgtype.UUID Column2 string } type ListDormantArtistTracksForDiscoverRow struct { ID pgtype.UUID AlbumID pgtype.UUID ArtistID pgtype.UUID } // Discover playlist bucket queries. Each returns up to its LIMIT // count of (track_id, album_id, artist_id) triples ordered // daily-deterministically via md5(track_id || dateStr). The Go-side // bucket allocator (internal/playlists/discover.go) applies // per-album (<=2) and per-artist (<=3) caps then redistributes any // bucket deficit equally across the others. LIMIT values are // generous so the caps + redistribution have headroom. // Tracks whose artist this user has played fewer than 10 times total. // The artist threshold defines "dormant" — we want to surface the // long tail of artists in the user's library that they rarely listen // to. Excludes tracks the user has played, liked, or has quarantined. // $1 = user_id, $2 = date string for md5 ordering. func (q *Queries) ListDormantArtistTracksForDiscover(ctx context.Context, arg ListDormantArtistTracksForDiscoverParams) ([]ListDormantArtistTracksForDiscoverRow, error) { rows, err := q.db.Query(ctx, listDormantArtistTracksForDiscover, arg.UserID, arg.Column2) if err != nil { return nil, err } defer rows.Close() var items []ListDormantArtistTracksForDiscoverRow for rows.Next() { var i ListDormantArtistTracksForDiscoverRow if err := rows.Scan(&i.ID, &i.AlbumID, &i.ArtistID); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listRandomUnheardTracksForDiscover = `-- name: ListRandomUnheardTracksForDiscover :many SELECT t.id, t.album_id, t.artist_id FROM tracks t WHERE NOT EXISTS ( SELECT 1 FROM play_events pe WHERE pe.user_id = $1 AND pe.track_id = t.id AND pe.was_skipped = false ) AND NOT EXISTS ( SELECT 1 FROM general_likes gl WHERE gl.user_id = $1 AND gl.track_id = t.id ) AND NOT EXISTS ( SELECT 1 FROM lidarr_quarantine q WHERE q.user_id = $1 AND q.track_id = t.id ) ORDER BY md5(t.id::text || $2::text) LIMIT 200 ` type ListRandomUnheardTracksForDiscoverParams struct { UserID pgtype.UUID Column2 string } type ListRandomUnheardTracksForDiscoverRow struct { ID pgtype.UUID AlbumID pgtype.UUID ArtistID pgtype.UUID } // Random sample from the entire library. Same exclusion filters as // the other two buckets. The generous LIMIT (200) gives the caller // headroom for the per-album/artist caps and the slot redistribution. // $1 = user_id, $2 = date string for md5 ordering. func (q *Queries) ListRandomUnheardTracksForDiscover(ctx context.Context, arg ListRandomUnheardTracksForDiscoverParams) ([]ListRandomUnheardTracksForDiscoverRow, error) { rows, err := q.db.Query(ctx, listRandomUnheardTracksForDiscover, arg.UserID, arg.Column2) if err != nil { return nil, err } defer rows.Close() var items []ListRandomUnheardTracksForDiscoverRow for rows.Next() { var i ListRandomUnheardTracksForDiscoverRow if err := rows.Scan(&i.ID, &i.AlbumID, &i.ArtistID); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listTasteUnheardTracksForDiscover = `-- name: ListTasteUnheardTracksForDiscover :many SELECT t.id, t.album_id, t.artist_id FROM tracks t JOIN LATERAL regexp_split_to_table(coalesce(t.genre, ''), '[;,]') AS g_split(g) ON true JOIN taste_profile_tags nt ON nt.user_id = $1 AND trim(g_split.g) = nt.tag WHERE nt.weight > 0 AND trim(g_split.g) <> '' AND NOT EXISTS ( SELECT 1 FROM play_events pe WHERE pe.user_id = $1 AND pe.track_id = t.id AND pe.was_skipped = false ) AND NOT EXISTS ( SELECT 1 FROM general_likes gl WHERE gl.user_id = $1 AND gl.track_id = t.id ) AND NOT EXISTS ( SELECT 1 FROM lidarr_quarantine q WHERE q.user_id = $1 AND q.track_id = t.id ) GROUP BY t.id, t.album_id, t.artist_id ORDER BY SUM(nt.weight) DESC, md5(t.id::text || $2::text) LIMIT 120 ` type ListTasteUnheardTracksForDiscoverParams struct { UserID pgtype.UUID Column2 string } type ListTasteUnheardTracksForDiscoverRow struct { ID pgtype.UUID AlbumID pgtype.UUID ArtistID pgtype.UUID } // Taste-targeted novelty: unheard tracks whose genres overlap the user's // taste-profile tags (taste_profile_tags, #796), ranked by summed tag // weight — "new to you, but your vibe" rather than the crude random arm. // Genres live inline on tracks.genre as a delimited string, split the // same way the radio tag_overlap arm does (regexp_split_to_table on // [;,]). Same exclusion filters as the other buckets. Returns nothing // when the user has no taste tags yet (cold start), so the caller // redistributes its slots to the other buckets. Stamped 'taste_unheard'. // $1 = user_id, $2 = date string for md5 tiebreak ordering. func (q *Queries) ListTasteUnheardTracksForDiscover(ctx context.Context, arg ListTasteUnheardTracksForDiscoverParams) ([]ListTasteUnheardTracksForDiscoverRow, error) { rows, err := q.db.Query(ctx, listTasteUnheardTracksForDiscover, arg.UserID, arg.Column2) if err != nil { return nil, err } defer rows.Close() var items []ListTasteUnheardTracksForDiscoverRow for rows.Next() { var i ListTasteUnheardTracksForDiscoverRow if err := rows.Scan(&i.ID, &i.AlbumID, &i.ArtistID); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil }