fix(store): pin connection pool so foreign_keys pragma sticks
SQLite PRAGMAs are connection-scoped, but database/sql uses a connection pool. Without pinning to one connection, new pooled connections won't have foreign_keys enabled, breaking ON DELETE CASCADE enforcement. Also mark modernc.org/sqlite as a direct dependency in go.mod. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,10 @@ func Open(path string, key []byte) (*Store, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Pin pool to a single connection so PRAGMA foreign_keys = ON sticks.
|
||||
// SQLite PRAGMAs are connection-scoped; pool would otherwise create
|
||||
// new connections without the pragma set.
|
||||
db.SetMaxOpenConns(1)
|
||||
if _, err := db.Exec("PRAGMA foreign_keys = ON;"); err != nil {
|
||||
db.Close()
|
||||
return nil, err
|
||||
|
||||
@@ -63,3 +63,48 @@ func TestSettingsRoundTrip(t *testing.T) {
|
||||
t.Fatalf("upsert failed: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestForeignKeyCascade(t *testing.T) {
|
||||
s := openTemp(t)
|
||||
|
||||
// Insert an account directly via raw SQL.
|
||||
_, err := s.db.Exec(`
|
||||
INSERT INTO accounts(name, mode, imap_host, imap_port, imap_security, auth_type, username)
|
||||
VALUES('test_account', 'RO', 'imap.example.com', 993, 'tls', 'password', 'user@example.com')
|
||||
`)
|
||||
if err != nil {
|
||||
t.Fatalf("insert account: %v", err)
|
||||
}
|
||||
|
||||
// Get the inserted account ID.
|
||||
var accountID int64
|
||||
err = s.db.QueryRow("SELECT id FROM accounts WHERE name = 'test_account'").Scan(&accountID)
|
||||
if err != nil {
|
||||
t.Fatalf("query account id: %v", err)
|
||||
}
|
||||
|
||||
// Insert a whitelist_in row referencing the account.
|
||||
_, err = s.db.Exec("INSERT INTO whitelist_in(account_id, address) VALUES(?, 'test@example.com')", accountID)
|
||||
if err != nil {
|
||||
t.Fatalf("insert whitelist_in: %v", err)
|
||||
}
|
||||
|
||||
// Verify the whitelist_in row exists.
|
||||
var count int
|
||||
err = s.db.QueryRow("SELECT COUNT(*) FROM whitelist_in WHERE account_id = ?", accountID).Scan(&count)
|
||||
if err != nil || count != 1 {
|
||||
t.Fatalf("whitelist_in row not found: count=%d err=%v", count, err)
|
||||
}
|
||||
|
||||
// Delete the account (should cascade and delete whitelist_in row).
|
||||
_, err = s.db.Exec("DELETE FROM accounts WHERE name = 'test_account'")
|
||||
if err != nil {
|
||||
t.Fatalf("delete account: %v", err)
|
||||
}
|
||||
|
||||
// Verify the whitelist_in row was cascade-deleted.
|
||||
err = s.db.QueryRow("SELECT COUNT(*) FROM whitelist_in WHERE account_id = ?", accountID).Scan(&count)
|
||||
if err != nil || count != 0 {
|
||||
t.Fatalf("whitelist_in row not cascade-deleted: count=%d err=%v", count, err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user