member-console/internal/db/database.go

77 lines
2.4 KiB
Go

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
}