Skip to content

Commit a6562a6

Browse files
committed
Use upsert for notes to prevent UNIQUE constraint failure
InsertNote now uses ON CONFLICT(purl, namespace) DO UPDATE instead of a plain INSERT. The cmd layer still checks for duplicates and shows the "use --force" message, but this prevents failures from race conditions or direct database API usage.
1 parent 4f13883 commit a6562a6

File tree

2 files changed

+51
-0
lines changed

2 files changed

+51
-0
lines changed

internal/database/database_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,52 @@ func TestBatchWriterSharedCommitsMultiManifest(t *testing.T) {
655655
}
656656
}
657657

658+
func TestInsertNoteUpsert(t *testing.T) {
659+
tmpDir := t.TempDir()
660+
dbPath := filepath.Join(tmpDir, "pkgs.sqlite3")
661+
662+
db, err := database.Create(dbPath)
663+
if err != nil {
664+
t.Fatalf("failed to create database: %v", err)
665+
}
666+
defer func() { _ = db.Close() }()
667+
668+
note := database.Note{
669+
PURL: "pkg:npm/[email protected]",
670+
Namespace: "default",
671+
Message: "first message",
672+
}
673+
674+
// First insert should work
675+
if err := db.InsertNote(note); err != nil {
676+
t.Fatalf("first InsertNote failed: %v", err)
677+
}
678+
679+
// Second insert with same purl+namespace should upsert, not fail
680+
note.Message = "updated message"
681+
if err := db.InsertNote(note); err != nil {
682+
t.Fatalf("second InsertNote failed (should upsert): %v", err)
683+
}
684+
685+
// Verify the note was updated
686+
got, err := db.GetNote(note.PURL, note.Namespace)
687+
if err != nil {
688+
t.Fatalf("GetNote failed: %v", err)
689+
}
690+
if got.Message != "updated message" {
691+
t.Errorf("expected 'updated message', got %q", got.Message)
692+
}
693+
694+
// Verify only one note exists
695+
notes, err := db.ListNotes("", "")
696+
if err != nil {
697+
t.Fatalf("ListNotes failed: %v", err)
698+
}
699+
if len(notes) != 1 {
700+
t.Errorf("expected 1 note, got %d", len(notes))
701+
}
702+
}
703+
658704
func TestNewWriterClose(t *testing.T) {
659705
tmpDir := t.TempDir()
660706
dbPath := filepath.Join(tmpDir, "pkgs.sqlite3")

internal/database/queries.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2023,6 +2023,11 @@ func (db *DB) InsertNote(note Note) error {
20232023
_, err := db.Exec(`
20242024
INSERT INTO notes (purl, namespace, origin, message, metadata, created_at, updated_at)
20252025
VALUES (?, ?, ?, ?, ?, ?, ?)
2026+
ON CONFLICT(purl, namespace) DO UPDATE SET
2027+
message = excluded.message,
2028+
metadata = excluded.metadata,
2029+
origin = excluded.origin,
2030+
updated_at = excluded.updated_at
20262031
`, note.PURL, note.Namespace, origin, note.Message, metadataJSON, now, now)
20272032
return err
20282033
}

0 commit comments

Comments
 (0)