Files
minstrel/internal/db/dbq/discover.sql.go
T
bvandeusenandClaude Opus 5.5 1d4ef1a23f docs: query comments say what each query selects and why, not how it came to be (#6277)
Rewrites the internal/db/queries comments to rule 32 and regenerates
dbq, whose doc comments and embedded SQL carry them. The bugs behind the
tiered mixes, the md5 order seed, the score bands and the round-robin
arms are restated as the constraint each one enforces; counts from one
library and stale cross-references are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 12:31:13 -04:00

316 lines
10 KiB
Go

// 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 t.missing_since IS NULL -- never offer a file that is gone
AND 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 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 would fail the
// entire Discover build.
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 t.missing_since IS NULL -- never offer a file that is gone
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 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 t.missing_since IS NULL -- never offer a file that is gone
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 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
WITH scored AS (
SELECT t.id, t.album_id, t.artist_id,
SUM(nt.weight) AS weight,
md5(t.id::text || $2::text) AS tiebreak
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 = $3 AND trim(g_split.g) = nt.tag
WHERE t.missing_since IS NULL -- never offer a file that is gone
AND nt.weight > 0
AND trim(g_split.g) <> ''
AND NOT EXISTS (
SELECT 1 FROM play_events pe
WHERE pe.user_id = $3
AND pe.track_id = t.id
AND pe.was_skipped = false
)
AND NOT EXISTS (
SELECT 1 FROM general_likes gl
WHERE gl.user_id = $3 AND gl.track_id = t.id
)
AND NOT EXISTS (
SELECT 1 FROM quarantine q
WHERE q.user_id = $3 AND q.track_id = t.id
)
GROUP BY t.id, t.album_id, t.artist_id
),
album_capped AS (
SELECT s.id, s.album_id, s.artist_id, s.weight, s.tiebreak,
row_number() OVER (PARTITION BY s.album_id ORDER BY s.weight DESC, s.tiebreak) AS album_rank
FROM scored s
),
artist_capped AS (
SELECT a.id, a.album_id, a.artist_id, a.weight, a.tiebreak, a.album_rank,
row_number() OVER (PARTITION BY a.artist_id ORDER BY a.weight DESC, a.tiebreak) AS artist_rank
FROM album_capped a
WHERE a.album_rank <= $4::int
)
SELECT c.id, c.album_id, c.artist_id
FROM artist_capped c
WHERE c.artist_rank <= $1::int
ORDER BY c.weight DESC, c.tiebreak
LIMIT 120
`
type ListTasteUnheardTracksForDiscoverParams struct {
MaxPerArtist int32
DateSeed string
UserID pgtype.UUID
MaxPerAlbum int32
}
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), 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'.
//
// The per-album and per-artist caps apply BEFORE the LIMIT. Summed weight
// rewards a track for carrying many of the user's tags, so a few artists
// whose every track is tagged with the whole profile would take every row of
// a plain LIMIT, the caller's caps would then leave a handful, and the arm
// would hand its slots to the others. Ranking within album, then within
// artist over what the album cap kept, is the same walk capByAlbumAndArtist
// makes, so the caller's caps keep everything here.
func (q *Queries) ListTasteUnheardTracksForDiscover(ctx context.Context, arg ListTasteUnheardTracksForDiscoverParams) ([]ListTasteUnheardTracksForDiscoverRow, error) {
rows, err := q.db.Query(ctx, listTasteUnheardTracksForDiscover,
arg.MaxPerArtist,
arg.DateSeed,
arg.UserID,
arg.MaxPerAlbum,
)
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
}