4cf3471398
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
59 lines
1.4 KiB
Go
59 lines
1.4 KiB
Go
// Package db owns the Controller's durable state in Postgres. It exposes
|
|
// a small typed surface (UpsertAgent, InsertInstance, ListInstances, …)
|
|
// built on pgx/v5 with a connection pool. Migrations are embedded so the
|
|
// binary is self-contained.
|
|
package db
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
// DB wraps a pgx pool with our typed query methods.
|
|
type DB struct {
|
|
pool *pgxpool.Pool
|
|
}
|
|
|
|
// Open connects to Postgres using dsn, pings once to fail fast on bad
|
|
// credentials or unreachable server, and runs any pending migrations.
|
|
func Open(ctx context.Context, dsn string) (*DB, error) {
|
|
cfg, err := pgxpool.ParseConfig(dsn)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parse db dsn: %w", err)
|
|
}
|
|
cfg.MaxConns = 10
|
|
cfg.MaxConnLifetime = 30 * time.Minute
|
|
|
|
pool, err := pgxpool.NewWithConfig(ctx, cfg)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("new pool: %w", err)
|
|
}
|
|
|
|
pingCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
|
defer cancel()
|
|
if err := pool.Ping(pingCtx); err != nil {
|
|
pool.Close()
|
|
return nil, fmt.Errorf("ping db: %w", err)
|
|
}
|
|
|
|
if err := Migrate(ctx, pool); err != nil {
|
|
pool.Close()
|
|
return nil, fmt.Errorf("migrate: %w", err)
|
|
}
|
|
|
|
return &DB{pool: pool}, nil
|
|
}
|
|
|
|
// Close releases pool resources.
|
|
func (db *DB) Close() {
|
|
if db != nil && db.pool != nil {
|
|
db.pool.Close()
|
|
}
|
|
}
|
|
|
|
// Pool exposes the underlying pool for tests / advanced callers.
|
|
func (db *DB) Pool() *pgxpool.Pool { return db.pool }
|