// Code generated by sqlc. DO NOT EDIT. // versions: // sqlc v1.31.1 // source: duplicates.sql package dbq import ( "context" "github.com/jackc/pgx/v5/pgtype" ) const addDuplicateGroupMember = `-- name: AddDuplicateGroupMember :exec INSERT INTO duplicate_group_members (group_id, track_id) VALUES ($1, $2) ON CONFLICT DO NOTHING ` type AddDuplicateGroupMemberParams struct { GroupID pgtype.UUID TrackID pgtype.UUID } func (q *Queries) AddDuplicateGroupMember(ctx context.Context, arg AddDuplicateGroupMemberParams) error { _, err := q.db.Exec(ctx, addDuplicateGroupMember, arg.GroupID, arg.TrackID) return err } const countPendingDuplicateGroups = `-- name: CountPendingDuplicateGroups :one SELECT count(*)::bigint FROM duplicate_groups g WHERE g.status = 'pending' AND (SELECT count(*) FROM duplicate_group_members m WHERE m.group_id = g.id) >= 2 ` // Proposals awaiting review. A group left with one member — its other tracks // deleted since the sweep — is no proposal at all and is not counted; the next // sweep retires it. func (q *Queries) CountPendingDuplicateGroups(ctx context.Context) (int64, error) { row := q.db.QueryRow(ctx, countPendingDuplicateGroups) var column_1 int64 err := row.Scan(&column_1) return column_1, err } const countPendingDuplicateGroupsInView = `-- name: CountPendingDuplicateGroupsInView :one SELECT count(*)::bigint FROM duplicate_groups g WHERE g.status = 'pending' AND (SELECT count(*) FROM duplicate_group_members m WHERE m.group_id = g.id) >= 2 AND CASE $1::text WHEN 'cross_release' THEN g.class = 'cross_release' ELSE g.class IS DISTINCT FROM 'cross_release' END ` // CountPendingDuplicateGroups narrowed to one view of the report (M498): // // review what the operator still has to decide: every class but // cross_release, and groups not yet classified // cross_release the same song on different releases, which is kept, not merged func (q *Queries) CountPendingDuplicateGroupsInView(ctx context.Context, view string) (int64, error) { row := q.db.QueryRow(ctx, countPendingDuplicateGroupsInView, view) var column_1 int64 err := row.Scan(&column_1) return column_1, err } const deleteStalePendingDuplicateGroups = `-- name: DeleteStalePendingDuplicateGroups :execrows DELETE FROM duplicate_groups g WHERE g.status = 'pending' AND g.last_seen_sweep_id IS DISTINCT FROM $1 AND (g.last_seen_sweep_id IS NULL OR (SELECT s.started_at FROM duplicate_sweeps s WHERE s.id = g.last_seen_sweep_id) < (SELECT s.started_at FROM duplicate_sweeps s WHERE s.id = $1)) ` // A pending proposal this sweep did not find again no longer describes the // library: a member was re-fingerprinted, merged away or went missing. Dismissed // groups are kept regardless — they are the memory of a decision. // // Only proposals last confirmed by an EARLIER sweep go. Should two sweeps ever // overlap (a manual trigger racing the worker), neither may delete what the other // has just found. func (q *Queries) DeleteStalePendingDuplicateGroups(ctx context.Context, sweepID pgtype.UUID) (int64, error) { result, err := q.db.Exec(ctx, deleteStalePendingDuplicateGroups, sweepID) if err != nil { return 0, err } return result.RowsAffected(), nil } const dismissDuplicateGroup = `-- name: DismissDuplicateGroup :execrows UPDATE duplicate_groups SET status = 'dismissed', resolved_at = now() WHERE id = $1 AND status = 'pending' ` // "These are not duplicates." Only a pending group can be dismissed; zero rows // means it was already resolved or no longer exists. func (q *Queries) DismissDuplicateGroup(ctx context.Context, id pgtype.UUID) (int64, error) { result, err := q.db.Exec(ctx, dismissDuplicateGroup, id) if err != nil { return 0, err } return result.RowsAffected(), nil } const finishDuplicateSweep = `-- name: FinishDuplicateSweep :exec UPDATE duplicate_sweeps SET finished_at = now(), candidates = $1, groups_found = $2, oversize_clusters = $3, error_message = NULLIF($4::text, '') WHERE id = $5 ` type FinishDuplicateSweepParams struct { Candidates *int32 GroupsFound *int32 OversizeClusters *int32 ErrorMessage string ID pgtype.UUID } func (q *Queries) FinishDuplicateSweep(ctx context.Context, arg FinishDuplicateSweepParams) error { _, err := q.db.Exec(ctx, finishDuplicateSweep, arg.Candidates, arg.GroupsFound, arg.OversizeClusters, arg.ErrorMessage, arg.ID, ) return err } const getInFlightDuplicateSweep = `-- name: GetInFlightDuplicateSweep :one SELECT id, started_at FROM duplicate_sweeps WHERE finished_at IS NULL ORDER BY started_at DESC LIMIT 1 ` type GetInFlightDuplicateSweepRow struct { ID pgtype.UUID StartedAt pgtype.Timestamptz } // The guard against two sweeps at once: "in flight" is finished_at IS NULL. func (q *Queries) GetInFlightDuplicateSweep(ctx context.Context) (GetInFlightDuplicateSweepRow, error) { row := q.db.QueryRow(ctx, getInFlightDuplicateSweep) var i GetInFlightDuplicateSweepRow err := row.Scan(&i.ID, &i.StartedAt) return i, err } const getLatestDuplicateSweep = `-- name: GetLatestDuplicateSweep :one SELECT id, started_at, finished_at, candidates, groups_found, oversize_clusters, error_message FROM duplicate_sweeps ORDER BY started_at DESC LIMIT 1 ` func (q *Queries) GetLatestDuplicateSweep(ctx context.Context) (DuplicateSweep, error) { row := q.db.QueryRow(ctx, getLatestDuplicateSweep) var i DuplicateSweep err := row.Scan( &i.ID, &i.StartedAt, &i.FinishedAt, &i.Candidates, &i.GroupsFound, &i.OversizeClusters, &i.ErrorMessage, ) return i, err } const getLatestFingerprintComputedAt = `-- name: GetLatestFingerprintComputedAt :one SELECT max(computed_at)::timestamptz AS latest FROM track_fingerprints ` // Whether a sweep has anything new to look at: fingerprints written since the // last sweep started. func (q *Queries) GetLatestFingerprintComputedAt(ctx context.Context) (pgtype.Timestamptz, error) { row := q.db.QueryRow(ctx, getLatestFingerprintComputedAt) var latest pgtype.Timestamptz err := row.Scan(&latest) return latest, err } const listDismissedDuplicateMemberSets = `-- name: ListDismissedDuplicateMemberSets :many SELECT g.id, array_agg(m.track_id ORDER BY m.track_id)::uuid[] AS track_ids FROM duplicate_groups g JOIN duplicate_group_members m ON m.group_id = g.id WHERE g.status = 'dismissed' GROUP BY g.id ` type ListDismissedDuplicateMemberSetsRow struct { ID pgtype.UUID TrackIds []pgtype.UUID } // What the operator has already said are not duplicates. A new proposal whose // every member sat together in one of these is not proposed again. func (q *Queries) ListDismissedDuplicateMemberSets(ctx context.Context) ([]ListDismissedDuplicateMemberSetsRow, error) { rows, err := q.db.Query(ctx, listDismissedDuplicateMemberSets) if err != nil { return nil, err } defer rows.Close() var items []ListDismissedDuplicateMemberSetsRow for rows.Next() { var i ListDismissedDuplicateMemberSetsRow if err := rows.Scan(&i.ID, &i.TrackIds); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listDuplicateCandidates = `-- name: ListDuplicateCandidates :many SELECT t.id, t.duration_ms, f.audio_stream_sha256, f.chromaprint FROM tracks t JOIN track_fingerprints f ON f.track_id = t.id WHERE t.missing_since IS NULL AND f.fingerprint_version >= $1 AND f.chromaprint IS NOT NULL -- Only chromaprints taken at the current length: prints at two lengths are not -- comparable, and after a length change the backfill is still re-deriving the -- rest (#3913). AND f.chromaprint_length_sec = $2 AND (t.duration_ms, t.id) > ($3::integer, $4::uuid) ORDER BY t.duration_ms, t.id LIMIT $5 ` type ListDuplicateCandidatesParams struct { CurrentVersion int16 ChromaprintLengthSec int32 AfterDurationMs int32 AfterID pgtype.UUID PageLimit int32 } type ListDuplicateCandidatesRow struct { ID pgtype.UUID DurationMs int32 AudioStreamSha256 []byte Chromaprint []int32 } // The acoustic tier's input, one page at a time in (duration_ms, id) order so the // sweep holds only a sliding window of durations. Tracks without a chromaprint // cannot be compared acoustically and are left out; any exact duplicates among // them come from ListExactDuplicateHashes. func (q *Queries) ListDuplicateCandidates(ctx context.Context, arg ListDuplicateCandidatesParams) ([]ListDuplicateCandidatesRow, error) { rows, err := q.db.Query(ctx, listDuplicateCandidates, arg.CurrentVersion, arg.ChromaprintLengthSec, arg.AfterDurationMs, arg.AfterID, arg.PageLimit, ) if err != nil { return nil, err } defer rows.Close() var items []ListDuplicateCandidatesRow for rows.Next() { var i ListDuplicateCandidatesRow if err := rows.Scan( &i.ID, &i.DurationMs, &i.AudioStreamSha256, &i.Chromaprint, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listDuplicateGroupsForResolve = `-- name: ListDuplicateGroupsForResolve :many SELECT g.id AS group_id, g.tier, g.worst_bit_error_rate, g.class AS prior_class, t.id AS track_id, t.album_id, t.title, t.source_verdict, artists.name AS artist_name, t.file_path, t.file_format, t.file_size, t.duration_ms, t.added_at, t.disc_number, t.track_number, (t.mbid IS NOT NULL)::boolean AS has_mbid, EXISTS (SELECT 1 FROM tracks o WHERE o.album_id = t.album_id AND o.id <> t.id AND o.missing_since IS NULL AND t.track_number IS NOT NULL AND o.disc_number IS NOT DISTINCT FROM t.disc_number AND o.track_number = t.track_number)::boolean AS position_clash, COALESCE(NULLIF(al.release_group_mbid, ''), lam.release_group_mbid, '')::text AS release_group_mbid, l.state AS acoustid_state, l.recording_mbid AS acoustid_recording FROM duplicate_groups g JOIN duplicate_group_members m ON m.group_id = g.id JOIN tracks t ON t.id = m.track_id JOIN artists ON artists.id = t.artist_id JOIN albums al ON al.id = t.album_id LEFT JOIN library_album_matches lam ON lam.album_id = t.album_id AND lam.outcome = 'listed' LEFT JOIN track_acoustid_lookups l ON l.track_id = t.id WHERE g.status = 'pending' AND t.missing_since IS NULL ORDER BY g.id, t.id ` type ListDuplicateGroupsForResolveRow struct { GroupID pgtype.UUID Tier string WorstBitErrorRate *float32 PriorClass *string TrackID pgtype.UUID AlbumID pgtype.UUID Title string SourceVerdict *string ArtistName string FilePath string FileFormat string FileSize int64 DurationMs int32 AddedAt pgtype.Timestamptz DiscNumber *int32 TrackNumber *int32 HasMbid bool PositionClash bool ReleaseGroupMbid string AcoustidState *string AcoustidRecording *string } // Every pending group's present copies, for the resolver (M498 #5435): what it // needs to classify the group and to choose the copy to keep. One row per copy, // grouped by group. position_clash is true when another present track on the // same album claims the same disc and track number: tags that do not fit the // album, which the survivor rule weighs against a copy. source_verdict says // whether the copy is a video rip ('suspect') or one the operator kept ('fine'). // prior_class is the class the last pass gave the group, NULL for a group no // pass has seen. release_group_mbid is the album's MusicBrainz group, from its // tags or the library listing's match; ” when neither names one. // acoustid_state and acoustid_recording are what AcoustID said of the file, // when it was looked up (#6169: which song one recording cut to several slots // is). func (q *Queries) ListDuplicateGroupsForResolve(ctx context.Context) ([]ListDuplicateGroupsForResolveRow, error) { rows, err := q.db.Query(ctx, listDuplicateGroupsForResolve) if err != nil { return nil, err } defer rows.Close() var items []ListDuplicateGroupsForResolveRow for rows.Next() { var i ListDuplicateGroupsForResolveRow if err := rows.Scan( &i.GroupID, &i.Tier, &i.WorstBitErrorRate, &i.PriorClass, &i.TrackID, &i.AlbumID, &i.Title, &i.SourceVerdict, &i.ArtistName, &i.FilePath, &i.FileFormat, &i.FileSize, &i.DurationMs, &i.AddedAt, &i.DiscNumber, &i.TrackNumber, &i.HasMbid, &i.PositionClash, &i.ReleaseGroupMbid, &i.AcoustidState, &i.AcoustidRecording, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listExactDuplicateHashes = `-- name: ListExactDuplicateHashes :many SELECT f.audio_stream_sha256, array_agg(t.id ORDER BY t.id)::uuid[] AS track_ids FROM track_fingerprints f JOIN tracks t ON t.id = f.track_id WHERE t.missing_since IS NULL AND f.fingerprint_version >= $1 AND f.audio_stream_sha256 IS NOT NULL GROUP BY f.audio_stream_sha256 HAVING count(*) > 1 ` type ListExactDuplicateHashesRow struct { AudioStreamSha256 []byte TrackIds []pgtype.UUID } // The exact tier, library-wide in one pass: identical encoded audio shared by // more than one present track. func (q *Queries) ListExactDuplicateHashes(ctx context.Context, currentVersion int16) ([]ListExactDuplicateHashesRow, error) { rows, err := q.db.Query(ctx, listExactDuplicateHashes, currentVersion) if err != nil { return nil, err } defer rows.Close() var items []ListExactDuplicateHashesRow for rows.Next() { var i ListExactDuplicateHashesRow if err := rows.Scan(&i.AudioStreamSha256, &i.TrackIds); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listPendingDuplicateGroupMembers = `-- name: ListPendingDuplicateGroupMembers :many WITH page AS ( SELECT g.id, g.tier, g.worst_bit_error_rate, g.detected_at, g.class, g.resolve_note FROM duplicate_groups g WHERE g.status = 'pending' AND (SELECT count(*) FROM duplicate_group_members m WHERE m.group_id = g.id) >= 2 AND CASE $1::text WHEN 'cross_release' THEN g.class = 'cross_release' ELSE g.class IS DISTINCT FROM 'cross_release' END ORDER BY g.detected_at DESC, g.id LIMIT $3 OFFSET $2 ) SELECT p.id AS group_id, p.tier, p.worst_bit_error_rate, p.detected_at, p.class, p.resolve_note, t.disc_number, t.track_number, (t.mbid IS NOT NULL)::boolean AS has_mbid, EXISTS (SELECT 1 FROM tracks o WHERE o.album_id = t.album_id AND o.id <> t.id AND o.missing_since IS NULL AND t.track_number IS NOT NULL AND o.disc_number IS NOT DISTINCT FROM t.disc_number AND o.track_number = t.track_number)::boolean AS position_clash, t.id AS track_id, t.title, artists.id AS artist_id, artists.name AS artist_name, albums.id AS album_id, albums.title AS album_title, t.file_path, t.file_format, t.file_size, t.duration_ms, t.added_at, (SELECT count(*) FROM general_likes l WHERE l.track_id = t.id)::bigint AS like_count, (SELECT count(*) FROM play_events e WHERE e.track_id = t.id)::bigint AS play_count FROM page p JOIN duplicate_group_members m ON m.group_id = p.id JOIN tracks t ON t.id = m.track_id JOIN albums ON albums.id = t.album_id JOIN artists ON artists.id = t.artist_id ORDER BY p.detected_at DESC, p.id, t.id ` type ListPendingDuplicateGroupMembersParams struct { View string PageOffset int32 PageLimit int32 } type ListPendingDuplicateGroupMembersRow struct { GroupID pgtype.UUID Tier string WorstBitErrorRate *float32 DetectedAt pgtype.Timestamptz Class *string ResolveNote *string DiscNumber *int32 TrackNumber *int32 HasMbid bool PositionClash bool TrackID pgtype.UUID Title string ArtistID pgtype.UUID ArtistName string AlbumID pgtype.UUID AlbumTitle string FilePath string FileFormat string FileSize int64 DurationMs int32 AddedAt pgtype.Timestamptz LikeCount int64 PlayCount int64 } // One page of proposals in one view (see CountPendingDuplicateGroupsInView), // newest first, flattened to one row per member so the handler folds them // without a query per group. What each copy carries — likes and plays from // every user — is here because it is what the operator weighs when deciding // which copy to keep. func (q *Queries) ListPendingDuplicateGroupMembers(ctx context.Context, arg ListPendingDuplicateGroupMembersParams) ([]ListPendingDuplicateGroupMembersRow, error) { rows, err := q.db.Query(ctx, listPendingDuplicateGroupMembers, arg.View, arg.PageOffset, arg.PageLimit) if err != nil { return nil, err } defer rows.Close() var items []ListPendingDuplicateGroupMembersRow for rows.Next() { var i ListPendingDuplicateGroupMembersRow if err := rows.Scan( &i.GroupID, &i.Tier, &i.WorstBitErrorRate, &i.DetectedAt, &i.Class, &i.ResolveNote, &i.DiscNumber, &i.TrackNumber, &i.HasMbid, &i.PositionClash, &i.TrackID, &i.Title, &i.ArtistID, &i.ArtistName, &i.AlbumID, &i.AlbumTitle, &i.FilePath, &i.FileFormat, &i.FileSize, &i.DurationMs, &i.AddedAt, &i.LikeCount, &i.PlayCount, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const markDuplicateGroupResolvedAutomatically = `-- name: MarkDuplicateGroupResolvedAutomatically :exec UPDATE duplicate_groups SET resolved_automatically = true WHERE id = $1 ` func (q *Queries) MarkDuplicateGroupResolvedAutomatically(ctx context.Context, id pgtype.UUID) error { _, err := q.db.Exec(ctx, markDuplicateGroupResolvedAutomatically, id) return err } const setDuplicateGroupClasses = `-- name: SetDuplicateGroupClasses :exec UPDATE duplicate_groups g SET class = v.class, resolve_note = NULLIF(v.note, '') FROM (SELECT unnest($1::uuid[]) AS id, unnest($2::text[]) AS class, unnest($3::text[]) AS note) v WHERE g.id = v.id AND g.status = 'pending' AND (g.class IS DISTINCT FROM v.class OR g.resolve_note IS DISTINCT FROM NULLIF(v.note, '')) ` type SetDuplicateGroupClassesParams struct { GroupIds []pgtype.UUID Classes []string Notes []string } // The resolver's verdicts, written in one statement. Only rows whose class or // note actually changed are touched, so an hourly pass over thousands of // unchanged groups writes nothing. An empty note clears it. func (q *Queries) SetDuplicateGroupClasses(ctx context.Context, arg SetDuplicateGroupClassesParams) error { _, err := q.db.Exec(ctx, setDuplicateGroupClasses, arg.GroupIds, arg.Classes, arg.Notes) return err } const settleDuplicateGroupAutomatically = `-- name: SettleDuplicateGroupAutomatically :execrows UPDATE duplicate_groups SET status = $1, resolved_at = now(), resolved_automatically = true WHERE id = $2 AND status = 'pending' ` type SettleDuplicateGroupAutomaticallyParams struct { Status string ID pgtype.UUID } // The resolver closing a group itself: 'dismissed' for versions that are not // duplicates, 'merged' when it removed the wrong copies. Only a pending group. func (q *Queries) SettleDuplicateGroupAutomatically(ctx context.Context, arg SettleDuplicateGroupAutomaticallyParams) (int64, error) { result, err := q.db.Exec(ctx, settleDuplicateGroupAutomatically, arg.Status, arg.ID) if err != nil { return 0, err } return result.RowsAffected(), nil } const startDuplicateSweep = `-- name: StartDuplicateSweep :one INSERT INTO duplicate_sweeps DEFAULT VALUES RETURNING id, started_at ` type StartDuplicateSweepRow struct { ID pgtype.UUID StartedAt pgtype.Timestamptz } func (q *Queries) StartDuplicateSweep(ctx context.Context) (StartDuplicateSweepRow, error) { row := q.db.QueryRow(ctx, startDuplicateSweep) var i StartDuplicateSweepRow err := row.Scan(&i.ID, &i.StartedAt) return i, err } const upsertDuplicateGroup = `-- name: UpsertDuplicateGroup :one INSERT INTO duplicate_groups (member_key, tier, worst_bit_error_rate, last_seen_sweep_id) VALUES ($1, $2, $3, $4) ON CONFLICT (member_key) DO UPDATE SET tier = EXCLUDED.tier, worst_bit_error_rate = EXCLUDED.worst_bit_error_rate, last_seen_sweep_id = EXCLUDED.last_seen_sweep_id WHERE duplicate_groups.status = 'pending' RETURNING id ` type UpsertDuplicateGroupParams struct { MemberKey string Tier string WorstBitErrorRate *float32 SweepID pgtype.UUID } // Proposes a group, or refreshes one already pending. A group already dismissed // or merged is left exactly as it is: the WHERE on the update makes the conflict // a no-op, and the caller sees no row. func (q *Queries) UpsertDuplicateGroup(ctx context.Context, arg UpsertDuplicateGroupParams) (pgtype.UUID, error) { row := q.db.QueryRow(ctx, upsertDuplicateGroup, arg.MemberKey, arg.Tier, arg.WorstBitErrorRate, arg.SweepID, ) var id pgtype.UUID err := row.Scan(&id) return id, err }