65 lines
1.8 KiB
Go
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
|
|
}
|