fix SQLite database locking issues

- Use tx.QueryRow instead of db.QueryRow within transaction for media table check
- Switch to WAL mode instead of OFF for better concurrency handling
- Add busy_timeout pragma to wait up to 5 seconds if database is locked
- Clear transaction variable after commit to prevent defer rollback issues
This commit is contained in:
Jeffrey Paul 2026-02-12 13:40:58 -08:00
parent 8e3a868b7a
commit 05b5c2b528

View File

@ -23,7 +23,8 @@ func ExtractDay(srcDBPath, dstDBPath string, targetDay time.Time) error {
slog.Info("extracting day", "from", dayStart, "until", dayEnd) slog.Info("extracting day", "from", dayStart, "until", dayEnd)
// Maximum performance pragmas - we don't care about crash safety for temp files // Maximum performance pragmas - we don't care about crash safety for temp files
pragmas := fmt.Sprintf("?_pragma=journal_mode(OFF)&_pragma=synchronous(OFF)&_pragma=cache_size(%d)&_pragma=foreign_keys(OFF)&_pragma=locking_mode(EXCLUSIVE)&_pragma=temp_store(MEMORY)", sqliteCacheSizeKB) // Use WAL mode for the source attachment to avoid locking issues
pragmas := fmt.Sprintf("?_pragma=journal_mode(WAL)&_pragma=synchronous(OFF)&_pragma=cache_size(%d)&_pragma=foreign_keys(OFF)&_pragma=temp_store(MEMORY)&_pragma=busy_timeout(5000)", sqliteCacheSizeKB)
db, err := sql.Open("sqlite", dstDBPath+pragmas) db, err := sql.Open("sqlite", dstDBPath+pragmas)
if err != nil { if err != nil {
return fmt.Errorf("opening destination database: %w", err) return fmt.Errorf("opening destination database: %w", err)
@ -109,7 +110,7 @@ func ExtractDay(srcDBPath, dstDBPath string, targetDay time.Time) error {
// Check if media table exists in source and copy if present // Check if media table exists in source and copy if present
var mediaTableExists int var mediaTableExists int
if err := db.QueryRow("SELECT COUNT(*) FROM src.sqlite_master WHERE type='table' AND name='media'").Scan(&mediaTableExists); err != nil { if err := tx.QueryRow("SELECT COUNT(*) FROM src.sqlite_master WHERE type='table' AND name='media'").Scan(&mediaTableExists); err != nil {
slog.Warn("checking for media table", "error", err) slog.Warn("checking for media table", "error", err)
} else if mediaTableExists > 0 { } else if mediaTableExists > 0 {
slog.Info("inserting media entries") slog.Info("inserting media entries")
@ -119,10 +120,11 @@ func ExtractDay(srcDBPath, dstDBPath string, targetDay time.Time) error {
} }
} }
// Commit the transaction // Commit the transaction before any further database operations
if err := tx.Commit(); err != nil { if err := tx.Commit(); err != nil {
return fmt.Errorf("committing transaction: %w", err) return fmt.Errorf("committing transaction: %w", err)
} }
tx = nil // Clear tx to ensure defer doesn't try to rollback
// Create indexes after bulk insert for speed // Create indexes after bulk insert for speed
slog.Info("creating indexes") slog.Info("creating indexes")