fix(db): drop redundant quarantine user index + tighten ListQuarantineForUser
- Composite PK (user_id, track_id) already serves WHERE user_id queries;
the secondary (user_id, created_at DESC) index just amplified writes.
- ListQuarantineForUser now selects only the joined fields the SPA card
actually renders (~10 columns) rather than embedding three full structs
(~35 columns); halves wire/DB bandwidth before consumers exist.
- max(q.created_at)::timestamptz cast emits pgtype.Timestamptz instead of
interface{} so handlers can read latest_at without a type-assert.
This commit is contained in:
@@ -69,8 +69,8 @@ SELECT
|
||||
al.title AS album_title,
|
||||
al.id AS album_id,
|
||||
al.mbid AS lidarr_album_mbid,
|
||||
count(q.user_id)::int AS report_count,
|
||||
max(q.created_at) AS latest_at
|
||||
count(q.user_id)::int AS report_count,
|
||||
max(q.created_at)::timestamptz AS latest_at
|
||||
FROM lidarr_quarantine q
|
||||
JOIN tracks t ON t.id = q.track_id
|
||||
JOIN albums al ON al.id = t.album_id
|
||||
@@ -87,7 +87,7 @@ type ListAdminQuarantineQueueRow struct {
|
||||
AlbumID pgtype.UUID
|
||||
LidarrAlbumMbid *string
|
||||
ReportCount int32
|
||||
LatestAt interface{}
|
||||
LatestAt pgtype.Timestamptz
|
||||
}
|
||||
|
||||
// Aggregated admin queue. One row per track. The handler post-processes
|
||||
@@ -162,9 +162,14 @@ func (q *Queries) ListQuarantineActions(ctx context.Context, limit int32) ([]Lid
|
||||
const listQuarantineForUser = `-- name: ListQuarantineForUser :many
|
||||
SELECT
|
||||
q.user_id, q.track_id, q.reason, q.notes, q.created_at,
|
||||
t.id, t.title, t.album_id, t.artist_id, t.track_number, t.disc_number, t.duration_ms, t.file_path, t.file_size, t.file_format, t.bitrate, t.mbid, t.genre, t.added_at, t.updated_at,
|
||||
al.id, al.title, al.sort_title, al.artist_id, al.release_date, al.mbid, al.cover_art_path, al.created_at, al.updated_at,
|
||||
ar.id, ar.name, ar.sort_name, ar.mbid, ar.created_at, ar.updated_at
|
||||
t.id AS track_id_join,
|
||||
t.title AS track_title,
|
||||
t.duration_ms AS track_duration_ms,
|
||||
al.id AS album_id,
|
||||
al.title AS album_title,
|
||||
al.cover_art_path AS album_cover_art_path,
|
||||
ar.id AS artist_id,
|
||||
ar.name AS artist_name
|
||||
FROM lidarr_quarantine q
|
||||
JOIN tracks t ON t.id = q.track_id
|
||||
JOIN albums al ON al.id = t.album_id
|
||||
@@ -174,14 +179,21 @@ ORDER BY q.created_at DESC
|
||||
`
|
||||
|
||||
type ListQuarantineForUserRow struct {
|
||||
LidarrQuarantine LidarrQuarantine
|
||||
Track Track
|
||||
Album Album
|
||||
Artist Artist
|
||||
LidarrQuarantine LidarrQuarantine
|
||||
TrackIDJoin pgtype.UUID
|
||||
TrackTitle string
|
||||
TrackDurationMs int32
|
||||
AlbumID pgtype.UUID
|
||||
AlbumTitle string
|
||||
AlbumCoverArtPath *string
|
||||
ArtistID pgtype.UUID
|
||||
ArtistName string
|
||||
}
|
||||
|
||||
// Caller's own quarantines joined with track + album + artist for full
|
||||
// detail. Drives /library/hidden.
|
||||
// Caller's own quarantines plus just the joined fields the SPA's
|
||||
// /library/hidden actually renders. Embedding the full tracks/albums/artists
|
||||
// structs would drag ~28 columns of file_path / sort_* / mbid / etc. that
|
||||
// the row card never uses.
|
||||
func (q *Queries) ListQuarantineForUser(ctx context.Context, userID pgtype.UUID) ([]ListQuarantineForUserRow, error) {
|
||||
rows, err := q.db.Query(ctx, listQuarantineForUser, userID)
|
||||
if err != nil {
|
||||
@@ -197,36 +209,14 @@ func (q *Queries) ListQuarantineForUser(ctx context.Context, userID pgtype.UUID)
|
||||
&i.LidarrQuarantine.Reason,
|
||||
&i.LidarrQuarantine.Notes,
|
||||
&i.LidarrQuarantine.CreatedAt,
|
||||
&i.Track.ID,
|
||||
&i.Track.Title,
|
||||
&i.Track.AlbumID,
|
||||
&i.Track.ArtistID,
|
||||
&i.Track.TrackNumber,
|
||||
&i.Track.DiscNumber,
|
||||
&i.Track.DurationMs,
|
||||
&i.Track.FilePath,
|
||||
&i.Track.FileSize,
|
||||
&i.Track.FileFormat,
|
||||
&i.Track.Bitrate,
|
||||
&i.Track.Mbid,
|
||||
&i.Track.Genre,
|
||||
&i.Track.AddedAt,
|
||||
&i.Track.UpdatedAt,
|
||||
&i.Album.ID,
|
||||
&i.Album.Title,
|
||||
&i.Album.SortTitle,
|
||||
&i.Album.ArtistID,
|
||||
&i.Album.ReleaseDate,
|
||||
&i.Album.Mbid,
|
||||
&i.Album.CoverArtPath,
|
||||
&i.Album.CreatedAt,
|
||||
&i.Album.UpdatedAt,
|
||||
&i.Artist.ID,
|
||||
&i.Artist.Name,
|
||||
&i.Artist.SortName,
|
||||
&i.Artist.Mbid,
|
||||
&i.Artist.CreatedAt,
|
||||
&i.Artist.UpdatedAt,
|
||||
&i.TrackIDJoin,
|
||||
&i.TrackTitle,
|
||||
&i.TrackDurationMs,
|
||||
&i.AlbumID,
|
||||
&i.AlbumTitle,
|
||||
&i.AlbumCoverArtPath,
|
||||
&i.ArtistID,
|
||||
&i.ArtistName,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ DROP INDEX IF EXISTS lidarr_quarantine_actions_created_idx;
|
||||
DROP INDEX IF EXISTS lidarr_quarantine_actions_track_idx;
|
||||
DROP TABLE IF EXISTS lidarr_quarantine_actions;
|
||||
DROP TYPE IF EXISTS lidarr_quarantine_action_kind;
|
||||
DROP INDEX IF EXISTS lidarr_quarantine_user_idx;
|
||||
DROP INDEX IF EXISTS lidarr_quarantine_track_idx;
|
||||
DROP TABLE IF EXISTS lidarr_quarantine;
|
||||
DROP TYPE IF EXISTS lidarr_quarantine_reason;
|
||||
|
||||
@@ -21,8 +21,11 @@ CREATE TABLE lidarr_quarantine (
|
||||
PRIMARY KEY (user_id, track_id)
|
||||
);
|
||||
|
||||
-- Only the track_id index is non-redundant: the composite PK
|
||||
-- (user_id, track_id) already serves WHERE user_id = $1 lookups, so a
|
||||
-- separate (user_id, created_at DESC) index would just amplify writes
|
||||
-- without paying for itself at household-scale row counts.
|
||||
CREATE INDEX lidarr_quarantine_track_idx ON lidarr_quarantine (track_id);
|
||||
CREATE INDEX lidarr_quarantine_user_idx ON lidarr_quarantine (user_id, created_at DESC);
|
||||
|
||||
CREATE TYPE lidarr_quarantine_action_kind AS ENUM (
|
||||
'resolved', 'deleted_file', 'deleted_via_lidarr'
|
||||
|
||||
@@ -17,13 +17,20 @@ DELETE FROM lidarr_quarantine
|
||||
RETURNING user_id, track_id, reason, notes, created_at;
|
||||
|
||||
-- name: ListQuarantineForUser :many
|
||||
-- Caller's own quarantines joined with track + album + artist for full
|
||||
-- detail. Drives /library/hidden.
|
||||
-- Caller's own quarantines plus just the joined fields the SPA's
|
||||
-- /library/hidden actually renders. Embedding the full tracks/albums/artists
|
||||
-- structs would drag ~28 columns of file_path / sort_* / mbid / etc. that
|
||||
-- the row card never uses.
|
||||
SELECT
|
||||
sqlc.embed(q),
|
||||
sqlc.embed(t),
|
||||
sqlc.embed(al),
|
||||
sqlc.embed(ar)
|
||||
t.id AS track_id_join,
|
||||
t.title AS track_title,
|
||||
t.duration_ms AS track_duration_ms,
|
||||
al.id AS album_id,
|
||||
al.title AS album_title,
|
||||
al.cover_art_path AS album_cover_art_path,
|
||||
ar.id AS artist_id,
|
||||
ar.name AS artist_name
|
||||
FROM lidarr_quarantine q
|
||||
JOIN tracks t ON t.id = q.track_id
|
||||
JOIN albums al ON al.id = t.album_id
|
||||
@@ -42,8 +49,8 @@ SELECT
|
||||
al.title AS album_title,
|
||||
al.id AS album_id,
|
||||
al.mbid AS lidarr_album_mbid,
|
||||
count(q.user_id)::int AS report_count,
|
||||
max(q.created_at) AS latest_at
|
||||
count(q.user_id)::int AS report_count,
|
||||
max(q.created_at)::timestamptz AS latest_at
|
||||
FROM lidarr_quarantine q
|
||||
JOIN tracks t ON t.id = q.track_id
|
||||
JOIN albums al ON al.id = t.album_id
|
||||
|
||||
Reference in New Issue
Block a user