release / govulncheck (push) Successful in 14s
release / web (push) Successful in 1m20s
release / go (push) Successful in 1m44s
release / android (push) Successful in 4m54s
release / Build signed APK (push) Successful in 5m10s
release / integration (push) Successful in 5m19s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 15s
release / Verify release artifacts (tag releases only) (push) Skipped
The change feed dropped every row older than 30 days, and an app syncing from cursor 0 builds its library from the feed, so a fresh install, cleared data or a 410 reset got only what had changed lately. The compactor now keeps each entity's latest change however old. It drops a row once a newer one for the same entity exists, and a delete once it is 30 days old, raising a stored mark (library_changes_trimmed) past it; an app whose cursor is below the mark is sent back to 0, as before. Migration 0101 adds the index the compactor needs, seeds the mark where the old compactor left off, and gives every artist, album and track whose latest row is not an upsert a fresh one, so the rows the old compactor dropped come back. The backup carries the mark with the feed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
166 lines
4.9 KiB
Go
166 lines
4.9 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.31.1
|
|
// source: library_changes.sql
|
|
|
|
package dbq
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
)
|
|
|
|
const deleteOldLibraryDeletes = `-- name: DeleteOldLibraryDeletes :one
|
|
WITH gone AS (
|
|
DELETE FROM library_changes WHERE op = 'delete' AND changed_at < $1 RETURNING id
|
|
), mark AS (
|
|
UPDATE library_changes_trimmed
|
|
SET through = GREATEST(through, (SELECT COALESCE(max(id), 0) FROM gone))
|
|
)
|
|
SELECT count(*)::BIGINT FROM gone
|
|
`
|
|
|
|
// Removes deletes older than the cutoff and raises the trimmed mark past the
|
|
// highest one, so an app whose cursor is below it starts over. Returns how
|
|
// many went.
|
|
func (q *Queries) DeleteOldLibraryDeletes(ctx context.Context, changedAt pgtype.Timestamptz) (int64, error) {
|
|
row := q.db.QueryRow(ctx, deleteOldLibraryDeletes, changedAt)
|
|
var column_1 int64
|
|
err := row.Scan(&column_1)
|
|
return column_1, err
|
|
}
|
|
|
|
const deleteSupersededLibraryChanges = `-- name: DeleteSupersededLibraryChanges :execrows
|
|
DELETE FROM library_changes old
|
|
WHERE EXISTS (SELECT 1 FROM library_changes newer
|
|
WHERE newer.entity_type = old.entity_type
|
|
AND newer.entity_id = old.entity_id
|
|
AND newer.id > old.id)
|
|
`
|
|
|
|
// Removes every row a newer row for the same entity has superseded. An app
|
|
// reading from any cursor still meets the newer row, so it loses nothing.
|
|
func (q *Queries) DeleteSupersededLibraryChanges(ctx context.Context) (int64, error) {
|
|
result, err := q.db.Exec(ctx, deleteSupersededLibraryChanges)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return result.RowsAffected(), nil
|
|
}
|
|
|
|
const getLastIssuedLibraryChangeCursor = `-- name: GetLastIssuedLibraryChangeCursor :one
|
|
SELECT COALESCE(pg_sequence_last_value(pg_get_serial_sequence('library_changes', 'id')::regclass), 0)::BIGINT
|
|
`
|
|
|
|
// The highest cursor this server has handed out, even once the compactor has
|
|
// trimmed the rows: the id sequence's last value. A restored server's
|
|
// sequence stands at the backup's last change.
|
|
func (q *Queries) GetLastIssuedLibraryChangeCursor(ctx context.Context) (int64, error) {
|
|
row := q.db.QueryRow(ctx, getLastIssuedLibraryChangeCursor)
|
|
var column_1 int64
|
|
err := row.Scan(&column_1)
|
|
return column_1, err
|
|
}
|
|
|
|
const getLibraryChangesSince = `-- name: GetLibraryChangesSince :many
|
|
SELECT id, entity_type, entity_id, op, changed_at
|
|
FROM library_changes
|
|
WHERE id > $1
|
|
ORDER BY id ASC
|
|
LIMIT $2
|
|
`
|
|
|
|
type GetLibraryChangesSinceParams struct {
|
|
ID int64
|
|
Limit int32
|
|
}
|
|
|
|
func (q *Queries) GetLibraryChangesSince(ctx context.Context, arg GetLibraryChangesSinceParams) ([]LibraryChange, error) {
|
|
rows, err := q.db.Query(ctx, getLibraryChangesSince, arg.ID, arg.Limit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []LibraryChange
|
|
for rows.Next() {
|
|
var i LibraryChange
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.EntityType,
|
|
&i.EntityID,
|
|
&i.Op,
|
|
&i.ChangedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const getLibraryChangesTrimmedThrough = `-- name: GetLibraryChangesTrimmedThrough :one
|
|
SELECT through FROM library_changes_trimmed
|
|
`
|
|
|
|
// The highest id of a delete the compactor has dropped. A cursor below it may
|
|
// have missed that delete.
|
|
func (q *Queries) GetLibraryChangesTrimmedThrough(ctx context.Context) (int64, error) {
|
|
row := q.db.QueryRow(ctx, getLibraryChangesTrimmedThrough)
|
|
var through int64
|
|
err := row.Scan(&through)
|
|
return through, err
|
|
}
|
|
|
|
const getMaxLibraryChangeCursor = `-- name: GetMaxLibraryChangeCursor :one
|
|
SELECT COALESCE(MAX(id), 0)::BIGINT FROM library_changes
|
|
`
|
|
|
|
func (q *Queries) GetMaxLibraryChangeCursor(ctx context.Context) (int64, error) {
|
|
row := q.db.QueryRow(ctx, getMaxLibraryChangeCursor)
|
|
var column_1 int64
|
|
err := row.Scan(&column_1)
|
|
return column_1, err
|
|
}
|
|
|
|
const insertLibraryChange = `-- name: InsertLibraryChange :exec
|
|
INSERT INTO library_changes (entity_type, entity_id, op)
|
|
VALUES ($1, $2, $3)
|
|
`
|
|
|
|
type InsertLibraryChangeParams struct {
|
|
EntityType string
|
|
EntityID string
|
|
Op string
|
|
}
|
|
|
|
func (q *Queries) InsertLibraryChange(ctx context.Context, arg InsertLibraryChangeParams) error {
|
|
_, err := q.db.Exec(ctx, insertLibraryChange, arg.EntityType, arg.EntityID, arg.Op)
|
|
return err
|
|
}
|
|
|
|
const insertLibraryChanges = `-- name: InsertLibraryChanges :exec
|
|
INSERT INTO library_changes (entity_type, entity_id, op)
|
|
SELECT $1::text,
|
|
unnest($2::text[]),
|
|
$3::text
|
|
`
|
|
|
|
type InsertLibraryChangesParams struct {
|
|
EntityType string
|
|
EntityIds []string
|
|
Op string
|
|
}
|
|
|
|
// Batch form, for a mutation that touches many rows at once (the scan's
|
|
// reconcile pass can mark up to a quarter of a library missing in one go). A
|
|
// loop of single inserts would be thousands of round-trips inside a scan that
|
|
// is already the slow path.
|
|
func (q *Queries) InsertLibraryChanges(ctx context.Context, arg InsertLibraryChangesParams) error {
|
|
_, err := q.db.Exec(ctx, insertLibraryChanges, arg.EntityType, arg.EntityIds, arg.Op)
|
|
return err
|
|
}
|