Remove dangerous database recovery that deleted journal/WAL files
SQLite handles crash recovery automatically when opening a database. The previous recoverDatabase() function was deleting journal and WAL files BEFORE opening the database, which prevented SQLite from recovering incomplete transactions and caused database corruption after Ctrl+C or crashes. This was causing "database disk image is malformed" errors after interrupting a backup operation.
This commit is contained in:
parent
43a69c2cfb
commit
bbe09ec5b5
@ -36,26 +36,17 @@ type DB struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new database connection at the specified path.
|
// New creates a new database connection at the specified path.
|
||||||
// It automatically handles database recovery, creates the schema if needed,
|
// It creates the schema if needed and configures SQLite with WAL mode for
|
||||||
// and configures SQLite with appropriate settings for performance and reliability.
|
// better concurrency. SQLite handles crash recovery automatically when
|
||||||
// The database uses WAL mode for better concurrency and sets a busy timeout
|
// opening a database with journal/WAL files present.
|
||||||
// to handle concurrent access gracefully.
|
|
||||||
//
|
|
||||||
// If the database appears locked, it will attempt recovery by removing stale
|
|
||||||
// lock files and switching temporarily to TRUNCATE journal mode.
|
|
||||||
//
|
|
||||||
// New creates a new database connection at the specified path.
|
|
||||||
// It automatically handles recovery from stale locks, creates the schema if needed,
|
|
||||||
// and configures SQLite with WAL mode for better concurrency.
|
|
||||||
// The path parameter can be a file path for persistent storage or ":memory:"
|
// The path parameter can be a file path for persistent storage or ":memory:"
|
||||||
// for an in-memory database (useful for testing).
|
// for an in-memory database (useful for testing).
|
||||||
func New(ctx context.Context, path string) (*DB, error) {
|
func New(ctx context.Context, path string) (*DB, error) {
|
||||||
log.Debug("Opening database connection", "path", path)
|
log.Debug("Opening database connection", "path", path)
|
||||||
|
|
||||||
// First, try to recover from any stale locks
|
// Note: We do NOT delete journal/WAL files before opening.
|
||||||
if err := recoverDatabase(ctx, path); err != nil {
|
// SQLite handles crash recovery automatically when the database is opened.
|
||||||
log.Warn("Failed to recover database", "error", err)
|
// Deleting these files would corrupt the database after an unclean shutdown.
|
||||||
}
|
|
||||||
|
|
||||||
// First attempt with standard WAL mode
|
// First attempt with standard WAL mode
|
||||||
log.Debug("Attempting to open database with WAL mode", "path", path)
|
log.Debug("Attempting to open database with WAL mode", "path", path)
|
||||||
@ -156,62 +147,6 @@ func (db *DB) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// recoverDatabase attempts to recover a locked database
|
|
||||||
func recoverDatabase(ctx context.Context, path string) error {
|
|
||||||
// Check if database file exists
|
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
|
||||||
// No database file, nothing to recover
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove stale lock files
|
|
||||||
// SQLite creates -wal and -shm files for WAL mode
|
|
||||||
walPath := path + "-wal"
|
|
||||||
shmPath := path + "-shm"
|
|
||||||
journalPath := path + "-journal"
|
|
||||||
|
|
||||||
log.Info("Attempting database recovery", "path", path)
|
|
||||||
|
|
||||||
// Always remove lock files on startup to ensure clean state
|
|
||||||
removed := false
|
|
||||||
|
|
||||||
// Check for and remove journal file (from non-WAL mode)
|
|
||||||
if _, err := os.Stat(journalPath); err == nil {
|
|
||||||
log.Info("Found journal file, removing", "path", journalPath)
|
|
||||||
if err := os.Remove(journalPath); err != nil {
|
|
||||||
log.Warn("Failed to remove journal file", "error", err)
|
|
||||||
} else {
|
|
||||||
removed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove WAL file
|
|
||||||
if _, err := os.Stat(walPath); err == nil {
|
|
||||||
log.Info("Found WAL file, removing", "path", walPath)
|
|
||||||
if err := os.Remove(walPath); err != nil {
|
|
||||||
log.Warn("Failed to remove WAL file", "error", err)
|
|
||||||
} else {
|
|
||||||
removed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove SHM file
|
|
||||||
if _, err := os.Stat(shmPath); err == nil {
|
|
||||||
log.Info("Found shared memory file, removing", "path", shmPath)
|
|
||||||
if err := os.Remove(shmPath); err != nil {
|
|
||||||
log.Warn("Failed to remove shared memory file", "error", err)
|
|
||||||
} else {
|
|
||||||
removed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if removed {
|
|
||||||
log.Info("Database lock files removed")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Conn returns the underlying *sql.DB connection.
|
// Conn returns the underlying *sql.DB connection.
|
||||||
// This should be used sparingly and primarily for read operations.
|
// This should be used sparingly and primarily for read operations.
|
||||||
// For write operations, prefer using the ExecWithLog method.
|
// For write operations, prefer using the ExecWithLog method.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user