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 }