fix(recommendation): drop user_id from Rediscover daily hash to preserve sqlc type inference
CI on 8b586c2e caught it: the $1::text cast in the md5 ORDER BY made
sqlc infer the parameter as plain string instead of pgtype.UUID,
generating Column1 string instead of UserID pgtype.UUID on the
ListRediscover*ForUserParams structs. go vet flagged both call
sites in internal/recommendation/home.go where the Go code passes
UserID by name.
Fix: hash on album_id (or artist_id) + current_date only. The
eligibility filter already differs per user (different liked sets),
so the daily-rotation goal is preserved; we just lose per-user salt
in the within-day ordering of overlapping items - acceptable.
Applies to both primary queries AND both fallback queries (all four
had the same cast).
This commit is contained in:
@@ -182,7 +182,7 @@ FROM general_likes_albums gla
|
|||||||
JOIN albums ON albums.id = gla.album_id
|
JOIN albums ON albums.id = gla.album_id
|
||||||
JOIN artists ON artists.id = albums.artist_id
|
JOIN artists ON artists.id = albums.artist_id
|
||||||
WHERE gla.user_id = $1
|
WHERE gla.user_id = $1
|
||||||
ORDER BY md5(albums.id::text || $1::text || current_date::text)
|
ORDER BY md5(albums.id::text || current_date::text)
|
||||||
LIMIT $2
|
LIMIT $2
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -200,7 +200,10 @@ type ListRediscoverAlbumsFallbackForUserRow struct {
|
|||||||
// service uses this to top up the rediscover row when the primary
|
// service uses this to top up the rediscover row when the primary
|
||||||
// eligibility query returns fewer than the inner limit. Same daily
|
// eligibility query returns fewer than the inner limit. Same daily
|
||||||
// hash ordering as the primary so the fallback rows stay stable
|
// hash ordering as the primary so the fallback rows stay stable
|
||||||
// through the day instead of reshuffling on every refresh.
|
// through the day instead of reshuffling on every refresh. Hash
|
||||||
|
// omits user_id so sqlc can keep the user_id parameter UUID-typed
|
||||||
|
// (a $1::text cast would force the param to string); eligibility
|
||||||
|
// already differs per user, so output sets differ regardless.
|
||||||
func (q *Queries) ListRediscoverAlbumsFallbackForUser(ctx context.Context, arg ListRediscoverAlbumsFallbackForUserParams) ([]ListRediscoverAlbumsFallbackForUserRow, error) {
|
func (q *Queries) ListRediscoverAlbumsFallbackForUser(ctx context.Context, arg ListRediscoverAlbumsFallbackForUserParams) ([]ListRediscoverAlbumsFallbackForUserRow, error) {
|
||||||
rows, err := q.db.Query(ctx, listRediscoverAlbumsFallbackForUser, arg.UserID, arg.Limit)
|
rows, err := q.db.Query(ctx, listRediscoverAlbumsFallbackForUser, arg.UserID, arg.Limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -267,13 +270,13 @@ SELECT albums.id, albums.title, albums.sort_title, albums.artist_id, albums.rele
|
|||||||
FROM eligible e
|
FROM eligible e
|
||||||
JOIN albums ON albums.id = e.album_id
|
JOIN albums ON albums.id = e.album_id
|
||||||
JOIN artists ON artists.id = albums.artist_id
|
JOIN artists ON artists.id = albums.artist_id
|
||||||
ORDER BY md5(albums.id::text || $1::text || current_date::text)
|
ORDER BY md5(albums.id::text || current_date::text)
|
||||||
LIMIT $2
|
LIMIT $2
|
||||||
`
|
`
|
||||||
|
|
||||||
type ListRediscoverAlbumsForUserParams struct {
|
type ListRediscoverAlbumsForUserParams struct {
|
||||||
Column1 string
|
UserID pgtype.UUID
|
||||||
Limit int32
|
Limit int32
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListRediscoverAlbumsForUserRow struct {
|
type ListRediscoverAlbumsForUserRow struct {
|
||||||
@@ -294,7 +297,7 @@ type ListRediscoverAlbumsForUserRow struct {
|
|||||||
// Final cap + diversity + cross-section dedup land in the Go layer
|
// Final cap + diversity + cross-section dedup land in the Go layer
|
||||||
// (internal/recommendation/home.go).
|
// (internal/recommendation/home.go).
|
||||||
func (q *Queries) ListRediscoverAlbumsForUser(ctx context.Context, arg ListRediscoverAlbumsForUserParams) ([]ListRediscoverAlbumsForUserRow, error) {
|
func (q *Queries) ListRediscoverAlbumsForUser(ctx context.Context, arg ListRediscoverAlbumsForUserParams) ([]ListRediscoverAlbumsForUserRow, error) {
|
||||||
rows, err := q.db.Query(ctx, listRediscoverAlbumsForUser, arg.Column1, arg.Limit)
|
rows, err := q.db.Query(ctx, listRediscoverAlbumsForUser, arg.UserID, arg.Limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -342,7 +345,7 @@ LEFT JOIN LATERAL (
|
|||||||
FROM albums WHERE artist_id = artists.id
|
FROM albums WHERE artist_id = artists.id
|
||||||
) cnt ON true
|
) cnt ON true
|
||||||
WHERE gla.user_id = $1
|
WHERE gla.user_id = $1
|
||||||
ORDER BY md5(artists.id::text || $1::text || current_date::text)
|
ORDER BY md5(artists.id::text || current_date::text)
|
||||||
LIMIT $2
|
LIMIT $2
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -359,7 +362,8 @@ type ListRediscoverArtistsFallbackForUserRow struct {
|
|||||||
|
|
||||||
// Daily-stable random sample of liked artists, paired with the Go
|
// Daily-stable random sample of liked artists, paired with the Go
|
||||||
// top-up logic in HomeData when the primary eligibility query returns
|
// top-up logic in HomeData when the primary eligibility query returns
|
||||||
// fewer than the inner limit. Daily hash ordering matches the primary.
|
// fewer than the inner limit. Daily hash ordering matches the primary
|
||||||
|
// (omitting user_id from the hash for the same sqlc-typing reason).
|
||||||
func (q *Queries) ListRediscoverArtistsFallbackForUser(ctx context.Context, arg ListRediscoverArtistsFallbackForUserParams) ([]ListRediscoverArtistsFallbackForUserRow, error) {
|
func (q *Queries) ListRediscoverArtistsFallbackForUser(ctx context.Context, arg ListRediscoverArtistsFallbackForUserParams) ([]ListRediscoverArtistsFallbackForUserRow, error) {
|
||||||
rows, err := q.db.Query(ctx, listRediscoverArtistsFallbackForUser, arg.UserID, arg.Limit)
|
rows, err := q.db.Query(ctx, listRediscoverArtistsFallbackForUser, arg.UserID, arg.Limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -434,13 +438,13 @@ LEFT JOIN LATERAL (
|
|||||||
SELECT count(*) AS album_count
|
SELECT count(*) AS album_count
|
||||||
FROM albums WHERE artist_id = artists.id
|
FROM albums WHERE artist_id = artists.id
|
||||||
) cnt ON true
|
) cnt ON true
|
||||||
ORDER BY md5(artists.id::text || $1::text || current_date::text)
|
ORDER BY md5(artists.id::text || current_date::text)
|
||||||
LIMIT $2
|
LIMIT $2
|
||||||
`
|
`
|
||||||
|
|
||||||
type ListRediscoverArtistsForUserParams struct {
|
type ListRediscoverArtistsForUserParams struct {
|
||||||
Column1 string
|
UserID pgtype.UUID
|
||||||
Limit int32
|
Limit int32
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListRediscoverArtistsForUserRow struct {
|
type ListRediscoverArtistsForUserRow struct {
|
||||||
@@ -462,7 +466,7 @@ type ListRediscoverArtistsForUserRow struct {
|
|||||||
// hashes). cover_album_id derived from a representative album (most
|
// hashes). cover_album_id derived from a representative album (most
|
||||||
// recently created album with cover_art_path set).
|
// recently created album with cover_art_path set).
|
||||||
func (q *Queries) ListRediscoverArtistsForUser(ctx context.Context, arg ListRediscoverArtistsForUserParams) ([]ListRediscoverArtistsForUserRow, error) {
|
func (q *Queries) ListRediscoverArtistsForUser(ctx context.Context, arg ListRediscoverArtistsForUserParams) ([]ListRediscoverArtistsForUserRow, error) {
|
||||||
rows, err := q.db.Query(ctx, listRediscoverArtistsForUser, arg.Column1, arg.Limit)
|
rows, err := q.db.Query(ctx, listRediscoverArtistsForUser, arg.UserID, arg.Limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -313,7 +313,7 @@ SELECT sqlc.embed(albums), artists.name AS artist_name
|
|||||||
FROM eligible e
|
FROM eligible e
|
||||||
JOIN albums ON albums.id = e.album_id
|
JOIN albums ON albums.id = e.album_id
|
||||||
JOIN artists ON artists.id = albums.artist_id
|
JOIN artists ON artists.id = albums.artist_id
|
||||||
ORDER BY md5(albums.id::text || $1::text || current_date::text)
|
ORDER BY md5(albums.id::text || current_date::text)
|
||||||
LIMIT $2;
|
LIMIT $2;
|
||||||
|
|
||||||
-- name: ListRediscoverAlbumsFallbackForUser :many
|
-- name: ListRediscoverAlbumsFallbackForUser :many
|
||||||
@@ -321,13 +321,16 @@ LIMIT $2;
|
|||||||
-- service uses this to top up the rediscover row when the primary
|
-- service uses this to top up the rediscover row when the primary
|
||||||
-- eligibility query returns fewer than the inner limit. Same daily
|
-- eligibility query returns fewer than the inner limit. Same daily
|
||||||
-- hash ordering as the primary so the fallback rows stay stable
|
-- hash ordering as the primary so the fallback rows stay stable
|
||||||
-- through the day instead of reshuffling on every refresh.
|
-- through the day instead of reshuffling on every refresh. Hash
|
||||||
|
-- omits user_id so sqlc can keep the user_id parameter UUID-typed
|
||||||
|
-- (a $1::text cast would force the param to string); eligibility
|
||||||
|
-- already differs per user, so output sets differ regardless.
|
||||||
SELECT sqlc.embed(albums), artists.name AS artist_name
|
SELECT sqlc.embed(albums), artists.name AS artist_name
|
||||||
FROM general_likes_albums gla
|
FROM general_likes_albums gla
|
||||||
JOIN albums ON albums.id = gla.album_id
|
JOIN albums ON albums.id = gla.album_id
|
||||||
JOIN artists ON artists.id = albums.artist_id
|
JOIN artists ON artists.id = albums.artist_id
|
||||||
WHERE gla.user_id = $1
|
WHERE gla.user_id = $1
|
||||||
ORDER BY md5(albums.id::text || $1::text || current_date::text)
|
ORDER BY md5(albums.id::text || current_date::text)
|
||||||
LIMIT $2;
|
LIMIT $2;
|
||||||
|
|
||||||
-- name: ListRediscoverArtistsForUser :many
|
-- name: ListRediscoverArtistsForUser :many
|
||||||
@@ -381,13 +384,14 @@ LEFT JOIN LATERAL (
|
|||||||
SELECT count(*) AS album_count
|
SELECT count(*) AS album_count
|
||||||
FROM albums WHERE artist_id = artists.id
|
FROM albums WHERE artist_id = artists.id
|
||||||
) cnt ON true
|
) cnt ON true
|
||||||
ORDER BY md5(artists.id::text || $1::text || current_date::text)
|
ORDER BY md5(artists.id::text || current_date::text)
|
||||||
LIMIT $2;
|
LIMIT $2;
|
||||||
|
|
||||||
-- name: ListRediscoverArtistsFallbackForUser :many
|
-- name: ListRediscoverArtistsFallbackForUser :many
|
||||||
-- Daily-stable random sample of liked artists, paired with the Go
|
-- Daily-stable random sample of liked artists, paired with the Go
|
||||||
-- top-up logic in HomeData when the primary eligibility query returns
|
-- top-up logic in HomeData when the primary eligibility query returns
|
||||||
-- fewer than the inner limit. Daily hash ordering matches the primary.
|
-- fewer than the inner limit. Daily hash ordering matches the primary
|
||||||
|
-- (omitting user_id from the hash for the same sqlc-typing reason).
|
||||||
SELECT sqlc.embed(artists),
|
SELECT sqlc.embed(artists),
|
||||||
cov.id AS cover_album_id,
|
cov.id AS cover_album_id,
|
||||||
cnt.album_count::bigint AS album_count
|
cnt.album_count::bigint AS album_count
|
||||||
@@ -403,5 +407,5 @@ LEFT JOIN LATERAL (
|
|||||||
FROM albums WHERE artist_id = artists.id
|
FROM albums WHERE artist_id = artists.id
|
||||||
) cnt ON true
|
) cnt ON true
|
||||||
WHERE gla.user_id = $1
|
WHERE gla.user_id = $1
|
||||||
ORDER BY md5(artists.id::text || $1::text || current_date::text)
|
ORDER BY md5(artists.id::text || current_date::text)
|
||||||
LIMIT $2;
|
LIMIT $2;
|
||||||
|
|||||||
Reference in New Issue
Block a user