Files
at-container-registry/pkg/appview/db/annotations.go

95 lines
2.5 KiB
Go

package db
import (
"strings"
"time"
)
// GetRepositoryAnnotations retrieves all annotations for a repository
func GetRepositoryAnnotations(db DBTX, did, repository string) (map[string]string, error) {
rows, err := db.Query(`
SELECT key, value
FROM repository_annotations
WHERE did = ? AND repository = ?
`, did, repository)
if err != nil {
return nil, err
}
defer rows.Close()
annotations := make(map[string]string)
for rows.Next() {
var key, value string
if err := rows.Scan(&key, &value); err != nil {
return nil, err
}
annotations[key] = value
}
return annotations, rows.Err()
}
// UpsertRepositoryAnnotations upserts annotations for a repository.
// Stale keys not present in the new map are deleted.
// Unchanged values are skipped to avoid unnecessary writes.
// Only called when manifest has at least one non-empty annotation.
// Atomicity is provided by the caller's transaction when used during backfill.
func UpsertRepositoryAnnotations(db DBTX, did, repository string, annotations map[string]string) error {
// Delete keys that are no longer in the annotation set
if len(annotations) == 0 {
_, err := db.Exec(`
DELETE FROM repository_annotations
WHERE did = ? AND repository = ?
`, did, repository)
return err
}
// Build placeholders for the NOT IN clause
placeholders := make([]string, 0, len(annotations))
args := []any{did, repository}
for key := range annotations {
placeholders = append(placeholders, "?")
args = append(args, key)
}
_, err := db.Exec(`
DELETE FROM repository_annotations
WHERE did = ? AND repository = ? AND key NOT IN (`+strings.Join(placeholders, ",")+`)
`, args...)
if err != nil {
return err
}
// Upsert each annotation, only writing when value changed
stmt, err := db.Prepare(`
INSERT INTO repository_annotations (did, repository, key, value, updated_at)
VALUES (?, ?, ?, ?, ?)
ON CONFLICT(did, repository, key) DO UPDATE SET
value = excluded.value,
updated_at = excluded.updated_at
WHERE excluded.value != repository_annotations.value
`)
if err != nil {
return err
}
defer stmt.Close()
now := time.Now()
for key, value := range annotations {
_, err = stmt.Exec(did, repository, key, value, now)
if err != nil {
return err
}
}
return nil
}
// DeleteRepositoryAnnotations removes all annotations for a repository
func DeleteRepositoryAnnotations(db DBTX, did, repository string) error {
_, err := db.Exec(`
DELETE FROM repository_annotations
WHERE did = ? AND repository = ?
`, did, repository)
return err
}