From 1ed6ca03099fd5f2cda71304abe872b9a40ea63f Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Mon, 4 May 2026 20:04:55 -0400 Subject: [PATCH] feat(db/m7-381): sqlc queries for scan_runs --- internal/db/dbq/models.go | 10 +++ internal/db/dbq/scan_runs.sql.go | 141 ++++++++++++++++++++++++++++++ internal/db/queries/scan_runs.sql | 42 +++++++++ 3 files changed, 193 insertions(+) create mode 100644 internal/db/dbq/scan_runs.sql.go create mode 100644 internal/db/queries/scan_runs.sql diff --git a/internal/db/dbq/models.go b/internal/db/dbq/models.go index 18d3eabc..b98c0eb2 100644 --- a/internal/db/dbq/models.go +++ b/internal/db/dbq/models.go @@ -363,6 +363,16 @@ type PlaylistTrack struct { AddedAt pgtype.Timestamptz } +type ScanRun struct { + ID pgtype.UUID + StartedAt pgtype.Timestamptz + FinishedAt pgtype.Timestamptz + Library []byte + MbidBackfill []byte + CoverEnrich []byte + ErrorMessage *string +} + type ScrobbleQueue struct { ID pgtype.UUID UserID pgtype.UUID diff --git a/internal/db/dbq/scan_runs.sql.go b/internal/db/dbq/scan_runs.sql.go new file mode 100644 index 00000000..5a3928e3 --- /dev/null +++ b/internal/db/dbq/scan_runs.sql.go @@ -0,0 +1,141 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: scan_runs.sql + +package dbq + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const finishScanRun = `-- name: FinishScanRun :exec +UPDATE scan_runs + SET finished_at = now(), + error_message = NULLIF($2::text, '') + WHERE id = $1 +` + +type FinishScanRunParams struct { + ID pgtype.UUID + Column2 string +} + +// Closes the row. error_message is non-empty only when a fatal error +// aborted the chain before normal completion. +func (q *Queries) FinishScanRun(ctx context.Context, arg FinishScanRunParams) error { + _, err := q.db.Exec(ctx, finishScanRun, arg.ID, arg.Column2) + return err +} + +const getInFlightScanRun = `-- name: GetInFlightScanRun :one +SELECT id, started_at + FROM scan_runs + WHERE finished_at IS NULL + ORDER BY started_at DESC + LIMIT 1 +` + +type GetInFlightScanRunRow struct { + ID pgtype.UUID + StartedAt pgtype.Timestamptz +} + +// Used by POST /api/admin/scan/run to 409 when a scan is already running. +// "In flight" = finished_at IS NULL. +func (q *Queries) GetInFlightScanRun(ctx context.Context) (GetInFlightScanRunRow, error) { + row := q.db.QueryRow(ctx, getInFlightScanRun) + var i GetInFlightScanRunRow + err := row.Scan(&i.ID, &i.StartedAt) + return i, err +} + +const getLatestScanRun = `-- name: GetLatestScanRun :one +SELECT id, started_at, finished_at, library, mbid_backfill, cover_enrich, error_message + FROM scan_runs + ORDER BY started_at DESC + LIMIT 1 +` + +// Powers GET /api/admin/scan/status. Returns NoRows when no scan has +// ever run; the API layer maps that to a 200 with empty payload. +func (q *Queries) GetLatestScanRun(ctx context.Context) (ScanRun, error) { + row := q.db.QueryRow(ctx, getLatestScanRun) + var i ScanRun + err := row.Scan( + &i.ID, + &i.StartedAt, + &i.FinishedAt, + &i.Library, + &i.MbidBackfill, + &i.CoverEnrich, + &i.ErrorMessage, + ) + return i, err +} + +const insertScanRun = `-- name: InsertScanRun :one + +INSERT INTO scan_runs DEFAULT VALUES RETURNING id, started_at +` + +type InsertScanRunRow struct { + ID pgtype.UUID + StartedAt pgtype.Timestamptz +} + +// M7 #381: scan-run lifecycle queries. +// Creates a new scan_runs row with started_at=now(). Stages are filled in +// as each completes via UpdateScanRunStage; finished_at is set by FinishScanRun. +func (q *Queries) InsertScanRun(ctx context.Context) (InsertScanRunRow, error) { + row := q.db.QueryRow(ctx, insertScanRun) + var i InsertScanRunRow + err := row.Scan(&i.ID, &i.StartedAt) + return i, err +} + +const updateScanRunCoverEnrich = `-- name: UpdateScanRunCoverEnrich :exec +UPDATE scan_runs SET cover_enrich = $2 WHERE id = $1 +` + +type UpdateScanRunCoverEnrichParams struct { + ID pgtype.UUID + CoverEnrich []byte +} + +func (q *Queries) UpdateScanRunCoverEnrich(ctx context.Context, arg UpdateScanRunCoverEnrichParams) error { + _, err := q.db.Exec(ctx, updateScanRunCoverEnrich, arg.ID, arg.CoverEnrich) + return err +} + +const updateScanRunLibrary = `-- name: UpdateScanRunLibrary :exec +UPDATE scan_runs SET library = $2 WHERE id = $1 +` + +type UpdateScanRunLibraryParams struct { + ID pgtype.UUID + Library []byte +} + +// Persists the file-walk stage's tallies. Idempotent — overwrites prior +// value if the orchestrator re-runs the stage on retry. +func (q *Queries) UpdateScanRunLibrary(ctx context.Context, arg UpdateScanRunLibraryParams) error { + _, err := q.db.Exec(ctx, updateScanRunLibrary, arg.ID, arg.Library) + return err +} + +const updateScanRunMbidBackfill = `-- name: UpdateScanRunMbidBackfill :exec +UPDATE scan_runs SET mbid_backfill = $2 WHERE id = $1 +` + +type UpdateScanRunMbidBackfillParams struct { + ID pgtype.UUID + MbidBackfill []byte +} + +func (q *Queries) UpdateScanRunMbidBackfill(ctx context.Context, arg UpdateScanRunMbidBackfillParams) error { + _, err := q.db.Exec(ctx, updateScanRunMbidBackfill, arg.ID, arg.MbidBackfill) + return err +} diff --git a/internal/db/queries/scan_runs.sql b/internal/db/queries/scan_runs.sql new file mode 100644 index 00000000..1ef24825 --- /dev/null +++ b/internal/db/queries/scan_runs.sql @@ -0,0 +1,42 @@ +-- M7 #381: scan-run lifecycle queries. + +-- name: InsertScanRun :one +-- Creates a new scan_runs row with started_at=now(). Stages are filled in +-- as each completes via UpdateScanRunStage; finished_at is set by FinishScanRun. +INSERT INTO scan_runs DEFAULT VALUES RETURNING id, started_at; + +-- name: UpdateScanRunLibrary :exec +-- Persists the file-walk stage's tallies. Idempotent — overwrites prior +-- value if the orchestrator re-runs the stage on retry. +UPDATE scan_runs SET library = $2 WHERE id = $1; + +-- name: UpdateScanRunMbidBackfill :exec +UPDATE scan_runs SET mbid_backfill = $2 WHERE id = $1; + +-- name: UpdateScanRunCoverEnrich :exec +UPDATE scan_runs SET cover_enrich = $2 WHERE id = $1; + +-- name: FinishScanRun :exec +-- Closes the row. error_message is non-empty only when a fatal error +-- aborted the chain before normal completion. +UPDATE scan_runs + SET finished_at = now(), + error_message = NULLIF($2::text, '') + WHERE id = $1; + +-- name: GetLatestScanRun :one +-- Powers GET /api/admin/scan/status. Returns NoRows when no scan has +-- ever run; the API layer maps that to a 200 with empty payload. +SELECT id, started_at, finished_at, library, mbid_backfill, cover_enrich, error_message + FROM scan_runs + ORDER BY started_at DESC + LIMIT 1; + +-- name: GetInFlightScanRun :one +-- Used by POST /api/admin/scan/run to 409 when a scan is already running. +-- "In flight" = finished_at IS NULL. +SELECT id, started_at + FROM scan_runs + WHERE finished_at IS NULL + ORDER BY started_at DESC + LIMIT 1;