package db import ( "context" "database/sql" _ "embed" // Required for go:embed "fmt" "log/slog" "os" "path/filepath" "time" _ "github.com/mattn/go-sqlite3" // SQLite driver ) //go:embed schema.sql var ddl string // DBConfig holds database configuration. type DBConfig struct { DSN string // Data Source Name for SQLite MaxOpenConns int // Maximum number of open connections MaxIdleConns int // Maximum number of idle connections ConnMaxLifetime time.Duration // Maximum lifetime of connections ConnMaxIdleTime time.Duration // Maximum idle time for connections } // DefaultDBConfig returns a DBConfig with sensible defaults for SQLite. 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 } } // NewDB initializes and returns a new database connection pool and runs migrations. func NewDB(ctx context.Context, logger *slog.Logger, config *DBConfig) (*sql.DB, error) { // Ensure the directory for the SQLite file exists dbDir := filepath.Dir(config.DSN) if err := os.MkdirAll(dbDir, 0755); err != nil { return nil, fmt.Errorf("failed to create database directory %s: %w", dbDir, err) } 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) } // Configure connection pool db.SetMaxOpenConns(config.MaxOpenConns) db.SetMaxIdleConns(config.MaxIdleConns) db.SetConnMaxLifetime(config.ConnMaxLifetime) db.SetConnMaxIdleTime(config.ConnMaxIdleTime) if err = db.PingContext(ctx); err != nil { db.Close() // Clean up connection on failure return nil, fmt.Errorf("failed to ping database: %w", err) } logger.Info("database connection established", slog.String("dsn", config.DSN), slog.Int("max_open_conns", config.MaxOpenConns), slog.Int("max_idle_conns", config.MaxIdleConns)) // Execute schema. if _, err := db.ExecContext(ctx, ddl); err != nil { db.Close() // Clean up connection on failure return nil, fmt.Errorf("failed to execute DDL: %w", err) } logger.Info("database schema applied") return db, nil }