// Package postgres opens the backend's primary Postgres pool and applies the // embedded migrations. // // The package is a thin wrapper around galaxy/postgres: it adapts the backend // configuration shape to galaxy/postgres.Config, plumbs the OpenTelemetry // tracer and meter providers from the telemetry runtime, instruments the // pool, and verifies connectivity with a bounded Ping. package postgres import ( "context" "database/sql" "fmt" "time" "galaxy/backend/internal/config" "galaxy/backend/internal/postgres/migrations" "galaxy/backend/internal/telemetry" pgshared "galaxy/postgres" ) // connMaxLifetime caps the lifetime of an individual pooled connection. Kept // in sync with galaxy/postgres.DefaultConnMaxLifetime so behaviour matches // the helper's defaults until backend has reason to deviate. const connMaxLifetime = 30 * time.Minute // Open constructs the primary Postgres pool, instruments it, pings it, and // returns the *sql.DB. Closing the database is the caller's responsibility. func Open(ctx context.Context, cfg config.PostgresConfig, runtime *telemetry.Runtime) (*sql.DB, error) { pgCfg := pgshared.Config{ PrimaryDSN: cfg.DSN, OperationTimeout: cfg.OperationTimeout, MaxOpenConns: cfg.MaxConns, MaxIdleConns: cfg.MinConns, ConnMaxLifetime: connMaxLifetime, } db, err := pgshared.OpenPrimary( ctx, pgCfg, pgshared.WithTracerProvider(runtime.TracerProvider()), pgshared.WithMeterProvider(runtime.MeterProvider()), ) if err != nil { return nil, fmt.Errorf("open backend postgres pool: %w", err) } if _, err := pgshared.InstrumentDBStats( db, pgshared.WithTracerProvider(runtime.TracerProvider()), pgshared.WithMeterProvider(runtime.MeterProvider()), ); err != nil { _ = db.Close() return nil, fmt.Errorf("instrument backend postgres pool: %w", err) } if err := pgshared.Ping(ctx, db, cfg.OperationTimeout); err != nil { _ = db.Close() return nil, fmt.Errorf("ping backend postgres pool: %w", err) } return db, nil } // schemaName is the Postgres schema owned by the backend service. Every // backend table lives here. const schemaName = "backend" // ApplyMigrations runs every pending Up migration embedded in the backend // binary against db. The schema is created upfront so goose's bookkeeping // table (`goose_db_version`, scoped to the DSN `search_path = backend`) // has somewhere to land before the first migration runs; migration // `00001_init.sql` re-asserts the schema with `IF NOT EXISTS`, so the // double-create is idempotent. func ApplyMigrations(ctx context.Context, db *sql.DB) error { if _, err := db.ExecContext(ctx, "CREATE SCHEMA IF NOT EXISTS "+schemaName); err != nil { return fmt.Errorf("ensure backend schema: %w", err) } if err := pgshared.RunMigrations(ctx, db, migrations.Migrations(), "."); err != nil { return fmt.Errorf("apply backend migrations: %w", err) } return nil }