diff --git a/internal/db/database.go b/internal/db/database.go index 24ed1be..3d50628 100644 --- a/internal/db/database.go +++ b/internal/db/database.go @@ -3,7 +3,7 @@ package db import ( "context" "database/sql" - _ "embed" // Required for go:embed + _ "embed" "errors" "fmt" "log/slog" @@ -11,7 +11,7 @@ import ( "path/filepath" "time" - "github.com/mattn/go-sqlite3" // SQLite driver + "github.com/mattn/go-sqlite3" ) //go:embed schema.sql @@ -32,12 +32,12 @@ type DBConfig struct { func DefaultDBConfig(dsn string) *DBConfig { return &DBConfig{ DSN: dsn, - MaxOpenConns: 25, // SQLite performs better with limited connections - MaxIdleConns: 10, // Keep some connections idle for reuse - ConnMaxLifetime: 30 * time.Minute, // Rotate connections every 30 minutes - ConnMaxIdleTime: 5 * time.Minute, // Close idle connections after 5 minutes - MaxRetries: 3, // Retry connection attempts up to 3 times - RetryDelay: time.Second, // Wait 1 second between retry attempts + MaxOpenConns: 25, + MaxIdleConns: 10, + ConnMaxLifetime: 30 * time.Minute, + ConnMaxIdleTime: 5 * time.Minute, + MaxRetries: 3, + RetryDelay: time.Second, } } @@ -52,21 +52,19 @@ func isRetryableError(err error) bool { var sqliteErr sqlite3.Error if errors.As(err, &sqliteErr) { switch sqliteErr.Code { - case sqlite3.ErrBusy, // SQLITE_BUSY (5) - database is locked by another process - sqlite3.ErrLocked: // SQLITE_LOCKED (6) - database is locked within same connection + case sqlite3.ErrBusy, + sqlite3.ErrLocked: return true } // Check extended error codes for more specific busy conditions switch sqliteErr.ExtendedCode { - case sqlite3.ErrBusyRecovery, // SQLITE_BUSY_RECOVERY (261) - WAL recovery in progress - sqlite3.ErrBusySnapshot: // SQLITE_BUSY_SNAPSHOT (517) - snapshot conflict + case sqlite3.ErrBusyRecovery, + sqlite3.ErrBusySnapshot: return true } } - // No fallback string matching - if it's not a proper SQLite error with - // the right error codes, we don't retry return false } @@ -79,7 +77,6 @@ func retryOperation(ctx context.Context, logger *slog.Logger, config *DBConfig, slog.Int("attempt", attempt), slog.Int("max_retries", config.MaxRetries)) - // Wait before retrying select { case <-ctx.Done(): return fmt.Errorf("context canceled during %s retry: %w", operationName, ctx.Err()) @@ -99,7 +96,6 @@ func retryOperation(ctx context.Context, logger *slog.Logger, config *DBConfig, return fmt.Errorf("failed to %s: %w", operationName, err) } - // Operation successful return nil } @@ -115,7 +111,7 @@ func openAndConfigureDB(config *DBConfig) (*sql.DB, error) { return nil, fmt.Errorf("failed to create database directory %s: %w", dbDir, err) } - // Open database connection (no retry needed - sql.Open rarely fails with transient errors) + // Open database connection db, err := sql.Open("sqlite3", config.DSN+"?_foreign_keys=on") // Enable foreign key constraints if err != nil { return nil, fmt.Errorf("failed to open database: %w", err) @@ -142,7 +138,7 @@ func NewDB(ctx context.Context, logger *slog.Logger, config *DBConfig) (*sql.DB, return db.PingContext(ctx) }) if err != nil { - db.Close() // Clean up connection on failure + db.Close() return nil, err } @@ -157,7 +153,7 @@ func NewDB(ctx context.Context, logger *slog.Logger, config *DBConfig) (*sql.DB, return err }) if err != nil { - db.Close() // Clean up connection on failure + db.Close() return nil, err }