95 lines
2.5 KiB
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
|
|
}
|