Files
goyomi/internal/db/db.go
T
2026-05-11 06:48:23 +00:00

114 lines
2.5 KiB
Go
Executable File

package db
import (
"context"
"embed"
"fmt"
"log"
"os"
"strconv"
"time"
"github.com/golang-migrate/migrate/v4"
_ "github.com/golang-migrate/migrate/v4/database/pgx/v5"
"github.com/golang-migrate/migrate/v4/source/iofs"
"github.com/jackc/pgx/v5/pgxpool"
"goyomi/internal/db/queries"
)
//go:embed migrations/*.sql
var migrationFS embed.FS
// DB holds the connection pool and sqlc query clients.
type DB struct {
Pool *pgxpool.Pool
Queries *queries.Queries
}
func Open(ctx context.Context) (*DB, error) {
dsn := os.Getenv("DATABASE_URL")
if dsn == "" {
return nil, fmt.Errorf("DATABASE_URL not set")
}
cfg, err := pgxpool.ParseConfig(dsn)
if err != nil {
return nil, fmt.Errorf("parse DATABASE_URL: %w", err)
}
cfg.MaxConns = int32(envInt("DB_MAX_CONNS", 10))
cfg.MinConns = int32(envInt("DB_MIN_CONNS", 2))
cfg.HealthCheckPeriod = 30 * time.Second
pool, err := pgxpool.NewWithConfig(ctx, cfg)
if err != nil {
return nil, fmt.Errorf("open pool: %w", err)
}
if err := pool.Ping(ctx); err != nil {
pool.Close()
return nil, fmt.Errorf("ping db: %w", err)
}
if err := runMigrations(dsn); err != nil {
pool.Close()
return nil, err
}
return &DB{
Pool: pool,
Queries: queries.New(pool),
}, nil
}
func (d *DB) Close() {
d.Pool.Close()
}
func runMigrations(dsn string) error {
src, err := iofs.New(migrationFS, "migrations")
if err != nil {
return fmt.Errorf("migration source: %w", err)
}
// golang-migrate pgx/v5 driver expects pgx5:// scheme
driverDSN := dsnToMigrateScheme(dsn)
m, err := migrate.NewWithSourceInstance("iofs", src, driverDSN)
if err != nil {
return fmt.Errorf("migrate init: %w", err)
}
defer m.Close()
before, _, _ := m.Version()
if err := m.Up(); err != nil && err != migrate.ErrNoChange {
return fmt.Errorf("migrate up: %w", err)
}
after, _, _ := m.Version()
if before != after {
log.Printf("db: migrated from version %d to %d", before, after)
} else {
log.Printf("db: schema up to date at version %d", after)
}
return nil
}
// dsnToMigrateScheme converts a postgres:// or postgresql:// DSN to pgx5://
// as required by the golang-migrate pgx/v5 driver.
func dsnToMigrateScheme(dsn string) string {
for _, prefix := range []string{"postgresql://", "postgres://"} {
if len(dsn) >= len(prefix) && dsn[:len(prefix)] == prefix {
return "pgx5://" + dsn[len(prefix):]
}
}
return dsn
}
func envInt(key string, def int) int {
if v := os.Getenv(key); v != "" {
if n, err := strconv.Atoi(v); err == nil {
return n
}
}
return def
}