// Code generated by sqlc. DO NOT EDIT. // versions: // sqlc v1.31.1 // source: gc.sql package dbq import ( "context" ) const gcClosePlaySessionsWithNoRecentEvents = `-- name: GcClosePlaySessionsWithNoRecentEvents :execrows UPDATE play_sessions SET ended_at = COALESCE(last_event_at, started_at) WHERE ended_at IS NULL AND ( (track_count > 0 AND last_event_at < now() - INTERVAL '6 hours') OR (track_count = 0 AND started_at < now() - INTERVAL '1 hour') ) ` // #565: play_sessions.ended_at was added but never populated by any // writer. Close sessions whose last_event_at is older than 6h — // treating that as "user moved on" the same way audio_service does // after grace periods. Sessions with NO events (track_count = 0) // older than 1h are also closed (stale handshakes from clients that // never recorded a play). ended_at is set to last_event_at so the // session's duration reads naturally. func (q *Queries) GcClosePlaySessionsWithNoRecentEvents(ctx context.Context) (int64, error) { result, err := q.db.Exec(ctx, gcClosePlaySessionsWithNoRecentEvents) if err != nil { return 0, err } return result.RowsAffected(), nil } const gcCloseStalePlayEvents = `-- name: GcCloseStalePlayEvents :execrows UPDATE play_events SET ended_at = COALESCE( started_at + (duration_played_ms * INTERVAL '1 millisecond'), now() ) WHERE ended_at IS NULL AND started_at < now() - INTERVAL '24 hours' ` // Background garbage-collector / lifecycle queries. All five address // drift findings from the 2026-06-02 audit (Scribe parent #552): // #565 #566 #567 #574 #575. Sweeper runs on a long tick (default 1h) // so per-query cost is amortised; each is idempotent (re-running on // already-closed/-deleted rows is a no-op). // #566: play_events rows opened more than 24h ago that never got a // play_ended. Client crashed mid-track, network dropped, etc. We // synthesize ended_at = started_at + duration_played_ms when present, // otherwise leave duration_played_ms null and stamp ended_at = now() // so the row stops looking "open" for downstream queries that filter // ended_at IS NULL. Doesn't touch was_skipped (we genuinely don't // know if the user skipped). func (q *Queries) GcCloseStalePlayEvents(ctx context.Context) (int64, error) { result, err := q.db.Exec(ctx, gcCloseStalePlayEvents) if err != nil { return 0, err } return result.RowsAffected(), nil } const gcDeleteExpiredPasswordResets = `-- name: GcDeleteExpiredPasswordResets :execrows DELETE FROM password_resets WHERE (used_at IS NOT NULL AND used_at < now() - INTERVAL '7 days') OR (used_at IS NULL AND expires_at < now() - INTERVAL '1 hour') ` // #575: password_resets accumulates expired + used rows forever. The // validation path already rejects them; this just keeps the table // from growing unbounded. Used rows are kept for 7 days for audit; // unused expired rows go immediately. func (q *Queries) GcDeleteExpiredPasswordResets(ctx context.Context) (int64, error) { result, err := q.db.Exec(ctx, gcDeleteExpiredPasswordResets) if err != nil { return 0, err } return result.RowsAffected(), nil } const gcExpireScrobbleQueueFailedRows = `-- name: GcExpireScrobbleQueueFailedRows :execrows DELETE FROM scrobble_queue WHERE status = 'failed' AND enqueued_at < now() - INTERVAL '14 days' ` // #567: scrobble_queue rows that have been in status='failed' for // more than 14 days. The worker stops retrying after maxAttempts; // failed rows accumulate forever otherwise. CASCADE from play_events // already drops the row when the underlying event is deleted, so this // only handles persistent failures (token revoked, etc.). func (q *Queries) GcExpireScrobbleQueueFailedRows(ctx context.Context) (int64, error) { result, err := q.db.Exec(ctx, gcExpireScrobbleQueueFailedRows) if err != nil { return 0, err } return result.RowsAffected(), nil } const gcResetStuckSystemPlaylistRuns = `-- name: GcResetStuckSystemPlaylistRuns :execrows UPDATE system_playlist_runs SET in_flight = false, last_error = COALESCE(last_error, 'stuck-row auto-reset by gc') WHERE in_flight = true AND last_run_at < now() - INTERVAL '10 minutes' ` // #574: system_playlist_runs.in_flight = true can wedge on a // goroutine panic between SET in_flight=true and SET in_flight=false. // The duplicate-prevention check refuses to start a fresh regen while // in_flight, so a stuck row blocks all future regens for that user. // Reset rows where last_run_at is older than 10 minutes (regens // shouldn't take that long). func (q *Queries) GcResetStuckSystemPlaylistRuns(ctx context.Context) (int64, error) { result, err := q.db.Exec(ctx, gcResetStuckSystemPlaylistRuns) if err != nil { return 0, err } return result.RowsAffected(), nil }