Files
kandev/apps/backend/internal/db/postgres.go
T
2026-09-07 19:28:31 +01:00

65 lines
1.8 KiB
Go

package db
import (
"context"
"database/sql"
"fmt"
"time"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/stdlib"
)
// OpenPostgres opens a PostgreSQL database connection using pgx.
// If maxConns or minConns are 0, they default to 25 and 5 respectively.
func OpenPostgres(dsn string, maxConns, minConns int) (*sql.DB, error) {
config, err := pgx.ParseConfig(dsn)
if err != nil {
return nil, fmt.Errorf("failed to parse postgres configuration: %w", err)
}
db := sql.OpenDB(stdlib.GetConnector(*config, stdlib.OptionAfterConnect(func(_ context.Context, conn *pgx.Conn) error {
return configurePostgresTimeTypes(conn.TypeMap())
})))
if db == nil {
return nil, fmt.Errorf("failed to open postgres database")
}
if maxConns <= 0 {
maxConns = 25
}
if minConns <= 0 {
minConns = 5
}
db.SetMaxOpenConns(maxConns)
db.SetMaxIdleConns(minConns)
if err := db.Ping(); err != nil {
_ = db.Close()
return nil, fmt.Errorf("failed to ping postgres database: %w", err)
}
return db, nil
}
// configurePostgresTimeTypes makes database/sql scans deterministic across
// hosts. pgx's default timestamptz and timestamp codecs use time.Local when no
// scan location is configured, even though Kandev stores both forms as UTC.
func configurePostgresTimeTypes(typeMap *pgtype.Map) error {
for _, typeName := range []string{"timestamp", "timestamptz"} {
registered, ok := typeMap.TypeForName(typeName)
if !ok {
return fmt.Errorf("postgres type map has no %s type", typeName)
}
replacement := *registered
if typeName == "timestamp" {
replacement.Codec = &pgtype.TimestampCodec{ScanLocation: time.UTC}
} else {
replacement.Codec = &pgtype.TimestamptzCodec{ScanLocation: time.UTC}
}
typeMap.RegisterType(&replacement)
}
return nil
}