Help us shape the future of Go! Take the 2026 Go Developer Survey

dbaccess

package
v0.0.0-...-2ee5e45 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 22, 2026 License: AGPL-3.0 Imports: 14 Imported by: 0

Documentation

Overview

Package dbaccess is the Postgres data-access layer abstraction (NFR-ARC-2): a typed query layer (pgx + sqlc) and versioned migrations (goose) behind a small Config, instead of raw provider-specific calls scattered across services. See ../README.md for a worked example of switching endpoints, and ./sqlc for the sample migration + typed queries this package ships as a reference for future services to pattern-match.

Index

Constants

View Source
const MigrationTimeout = 2 * time.Minute

MigrationTimeout bounds one deploy-time migration run. Each service's runMigrate applies it to the context it hands to Migrate.

WHY THE BOUND LIVES IN THE PROCESS (#551). The migrate Job used to be bounded only by its `activeDeadlineSeconds: 300`. Kubernetes starts that clock when the Job is created, so it pays for scheduling and image pull as well as the migration — and on the first deploy of a new version every Deployment in the release is rolling at once, so the node is pulling the whole image set while this Job queues behind it. On staging's v0.0.1-rc9 that was enough: `activities-migrate` died with DeadlineExceeded and failed the Helm release, Flux retried, and the same job passed in 36s — while `apiaries-migrate`, same SQL with the image already cached, took 6s. None of the 300s was ever spent migrating.

Kubernetes offers no way to exclude pull time from that clock, so the fix is not a larger number — it is to bound the thing we actually mean to bound. This deadline starts when the migration starts, so a cold deploy pays for its pull outside the budget, while a migration that hangs still fails fast and still fails the release. That last property is not incidental: #541 made a failed migration a loud, deploy-blocking event precisely because the previous design let one hide behind a healthy older ReplicaSet for 25 days.

Two minutes is roughly twenty times the slowest migration this codebase has produced (they run in seconds). Raise it deliberately, for a specific migration that genuinely needs longer — it is not a knob to widen when a deploy feels slow, and widening it no longer buys tolerance for a slow pull, which is what the old number was really being asked for.

The deadline reaches the server, not just the Go code: pgx forwards cancellation as a Postgres cancel request, so a migration stuck waiting on a lock it will never get — the realistic shape of "stuck" here — is aborted rather than left to be killed with the pod.

Variables

This section is empty.

Functions

func Connect

func Connect(ctx context.Context, cfg Config) (*pgxpool.Pool, error)

Connect builds a pgx connection pool from cfg and fails fast: it pings the database before returning, so misconfiguration surfaces immediately rather than on the first query.

func Migrate

func Migrate(ctx context.Context, dsn string, migrations fs.FS) error

Migrate applies all pending "up" migrations found in migrations (an os.DirFS or embed.FS rooted at a migrations directory) against dsn.

It goes through database/sql (not pgxpool) because that's what goose's Provider expects; the app's own queries still go through the pgxpool pool returned by Connect.

func MigrationsFS

func MigrationsFS() fs.FS

MigrationsFS returns this package's own demo migrations (see ./sqlc), rooted at the migrations directory as goose.NewProvider expects. It exists so the integration test — and any caller wanting to see the pattern in action — doesn't need to know this package's internal layout.

func UnscopedTables

func UnscopedTables(ctx context.Context, pool *pgxpool.Pool, schema string, exemptTables ...string) ([]string, error)

UnscopedTables inspects every base table in schema and returns the ones that do NOT carry an organization_id column, excluding exemptTables (the documented tenancy exceptions — ADR-0002 "tenancy exception": a global identity table, or the organizations table itself, which IS the tenant root rather than something owned BY a tenant) and goose's own version table (never domain data).

This is the automated form of FR-TEN-2 / #30's AC "every owned row carries an organization_id": rather than a one-time manual audit, a service's own test suite can call this against its migrated schema so a future migration that adds an owned table without organization_id fails CI immediately, instead of depending on someone remembering to check (data-model.md §5).

Types

type Config

type Config struct {
	Host     string
	Port     string // defaults to "5432" if empty
	User     string
	Password string
	Database string
	SSLMode  string // e.g. "require", "disable"; defaults to "require"
	// SearchPath, when set, is applied as the connection's schema search path.
	// A least-privilege per-service role (schema-per-service, D-6) has no rights
	// on `public`, so this points it at its own schema — where its tables live
	// and where goose creates its version table.
	SearchPath string
}

Config holds the connection details for a Postgres database. Populate it from environment/config/secrets — never hardcode credentials.

func (Config) DSN

func (c Config) DSN() string

DSN renders cfg as a connection string consumable by both pgxpool (Connect) and goose's database/sql driver (Migrate).

func (Config) Validate

func (c Config) Validate() error

Validate rejects a Config that must never reach DSN(). Exported because the migrate admin process (#541) needs it: it calls Migrate and then EXITS, never calling Connect, so the validation Connect performs would otherwise never run for that path at all.

That gap matters more there than in a serving process, not less — the migrate job authenticates as the database OWNER, so an injected connection option carries owner privileges. Before #541 a bad SearchPath was at least caught by Connect moments after Migrate in the same process; relatedly, see #285 on Migrate itself still taking a raw DSN rather than a Config it could validate.

Directories

Path Synopsis
sqlc
gen

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL