Documentation
¶
Overview ¶
Package database provides the SQL data layer's dialect-neutral core: a lifecycle-integrated wrapper over a database/sql connection pool and the dialect seam its providers implement. The package depends on the standard library and go-core's config package alone; every driver lives in a provider sub-module (postgres) that constructs the pool and supplies the dialect, so a consumer imports its provider once, at the composition root.
Wrapper ¶
New wraps the provider-constructed *sql.DB with the provider's Dialect and a finalized Config, applying the config's pool settings — the dialect-independent half of construction every provider shares. It performs no I/O: an unfinalized Config, nil conn, or nil dialect panics with the fix named, and everything else waits for Start. DB.Conn exposes the pool; DB.Dialect the seam.
Lifecycle wiring ¶
The package registers no lifecycle hooks of its own. DB.Start and DB.Shutdown carry the lifecycle package's hook signature, so a composition root wires the database as bare method values:
lc.OnStartup(db.Start) lc.OnShutdown(db.Shutdown)
Start establishes connectivity with a ping bounded by the configured conn_timeout, so a dead database fails the coordinator's startup rather than serving traffic unready. Shutdown closes the pool and returns the close error; after a failed startup the drain passes through it cleanly.
Readiness ¶
DB.Ready satisfies lifecycle.ReadinessChecker structurally and reports live connectivity: false before Start or after Shutdown, and otherwise a ping bounded by conn_timeout. A readiness probe aggregating the database therefore reflects it now — 503 during an outage, healed when the database returns — at the cost of one bounded round trip per probe. DB.Ping is the same verification under the caller's context and bound.
Sessions and transactions ¶
Session is the querying surface the exec package's read runners take, implemented by both DB and Tx, so the same reads run against the pool or inside a transaction and the dialect travels with the session. The consumer owns the transaction boundary: DB.Begin opens a Tx, write runners take *Tx concretely so the compiler enforces the boundary, and ExecTx wraps one unit of work — commit on success, rollback on the unit's error. Tx.Commit routes its error through the dialect's MapError, the one place a violation deferred to COMMIT can be classified.
Dialect ¶
Dialect is the interface a provider implements for the base: a name, the bind placeholder renderer, and the driver error mapper. The ast package's rendering and the providers' error classification both route through it, which is what keeps the layers above dialect-neutral. Providers are selected by typed construction — a Provider constant and a constructor per provider, no registry — and opt into the ast package's render capabilities per feature.
Configuration ¶
Config holds the connection identity, pool sizing, and timeouts, and implements the config package's Merge and Finalize contract, so it loads as part of an application's configuration. The numeric and duration fields are pointers: nil is unset and takes the default, while an explicit zero survives the load and means what it says. Port defaults in the provider, User and Password stay optional (requiredness varies by provider and auth mode), and Options passes dialect-specific connection keys through to the provider. Finalize composes the standard override names from the prefix it receives (via NewEnv, recorded on Env for introspection); an empty prefix disables the overrides.
Errors ¶
The package owns the error taxonomy consumers match on. ErrNotReady and ErrConnectionFailed classify service conditions, wrapped in the dual form fmt.Errorf("%w: %w", sentinel, err) so errors.Is classifies while the driver's error stays recoverable. The four constraint classes — ErrUniqueViolation, ErrForeignKeyViolation, ErrCheckViolation, ErrNotNullViolation — reach the caller inside a ConstraintError from a provider's MapError, carrying the constraint name when the driver exposes it; ErrVersionMismatch classifies a failed optimistic- concurrency guard. sql.ErrNoRows is never mapped; it flows to the boundary unchanged.
Index ¶
- Variables
- func ExecTx(ctx context.Context, db *DB, fn func(*Tx) error) error
- type Config
- type ConstraintError
- type DB
- func (d *DB) Begin(ctx context.Context) (*Tx, error)
- func (d *DB) Conn() *sql.DB
- func (d *DB) Dialect() Dialect
- func (d *DB) ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
- func (d *DB) Ping(ctx context.Context) error
- func (d *DB) QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
- func (d *DB) QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
- func (d *DB) Ready() bool
- func (d *DB) Shutdown(ctx context.Context) error
- func (d *DB) Start(ctx context.Context) error
- type Dialect
- type Env
- type Provider
- type Session
- type Tx
- func (t *Tx) Commit() error
- func (t *Tx) Dialect() Dialect
- func (t *Tx) ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
- func (t *Tx) QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
- func (t *Tx) QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
- func (t *Tx) Rollback() error
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNotReady reports a call against a [DB] before a successful Start // or after Shutdown. ErrNotReady = errors.New("database not ready") // ErrConnectionFailed classifies a connectivity failure. It is wrapped // alongside the driver's error in the dual form // fmt.Errorf("%w: %w", ErrConnectionFailed, err), so errors.Is matches // the class while the cause stays recoverable. ErrConnectionFailed = errors.New("database connection failed") // ErrUniqueViolation classifies a unique-constraint violation. A // provider's MapError wraps it in a [ConstraintError]; errors.Is // matches the class. ErrUniqueViolation = errors.New("unique constraint violation") // ErrForeignKeyViolation classifies a foreign-key-constraint violation, // wrapped the same way as [ErrUniqueViolation]. ErrForeignKeyViolation = errors.New("foreign key constraint violation") // ErrCheckViolation classifies a check-constraint violation, wrapped // the same way as [ErrUniqueViolation]. ErrCheckViolation = errors.New("check constraint violation") // ErrNotNullViolation classifies a not-null-constraint violation, // wrapped the same way as [ErrUniqueViolation]. ErrNotNullViolation = errors.New("not-null constraint violation") // ErrVersionMismatch reports an optimistic-concurrency guard that // matched its key but not its expected version. The execution layer's // guarded runners return it wrapped with the expected and current // versions; errors.Is matches the class. ErrVersionMismatch = errors.New("version mismatch") )
Functions ¶
Types ¶
type Config ¶
type Config struct {
Host string `json:"host"`
Name string `json:"name"`
User string `json:"user"`
Password string `json:"password"`
Port *int `json:"port"`
MaxOpenConns *int `json:"max_open_conns"`
MaxIdleConns *int `json:"max_idle_conns"`
ConnMaxLifetime *config.Duration `json:"conn_max_lifetime"`
ConnMaxIdleTime *config.Duration `json:"conn_max_idle_time"`
ConnTimeout *config.Duration `json:"conn_timeout"`
Options map[string]string `json:"options"`
Env Env `json:"-"`
}
Config holds the connection identity, pool sizing, and timeouts for a SQL database. The numeric and duration fields are tri-state pointers: nil is unset and takes the default, while an explicit zero survives the load and means what it says — an unlimited pool, or no idle connections. Port has no default here because the default port is a provider fact; each provider supplies its own. User and Password are optional because their requiredness varies by provider and auth mode; the password rides the secrets layer of config.Load rather than a committed file. Options carries dialect-specific connection keys (postgres: sslmode) passed through to the provider. Env records the environment-variable names Finalize composed and read; it is excluded from JSON.
type ConstraintError ¶ added in v0.3.0
ConstraintError is a classified constraint violation: the class sentinel, the driver's own error, and the violated constraint's name when the driver exposes it. Unwrap yields both wrapped errors, so errors.Is matches the class while errors.As still reaches the driver error, and the constraint name survives for a consumer's field mapping.
func (*ConstraintError) Error ¶ added in v0.3.0
func (e *ConstraintError) Error() string
func (*ConstraintError) Unwrap ¶ added in v0.3.0
func (e *ConstraintError) Unwrap() []error
type DB ¶
type DB struct {
// contains filtered or unexported fields
}
DB wraps a provider-constructed connection pool with lifecycle integration and the provider's dialect. It owns one pool for the process: construction performs no I/O, Start establishes connectivity, and Ready reports it live. Start and Shutdown carry the lifecycle hook signature, so the composition root registers the bare method values, and Ready satisfies lifecycle.ReadinessChecker structurally; the package registers no hooks of its own.
func New ¶
New wraps a provider-constructed pool with dialect and applies cfg's pool settings — the dialect-independent half of construction every provider shares. It panics if cfg was not finalized, or on a nil conn or dialect: each is a wiring defect at the composition root, not a runtime condition.
func (*DB) Begin ¶ added in v0.3.0
Begin opens a transaction: ErrNotReady before Start or after Shutdown, and a failure to begin wraps ErrConnectionFailed.
func (*DB) ExecContext ¶ added in v0.3.0
func (*DB) Ping ¶
Ping verifies connectivity under the caller's context: ErrNotReady before Start or after Shutdown, and otherwise a ping whose failure wraps ErrConnectionFailed. Unlike DB.Ready it applies no timeout of its own.
func (*DB) QueryContext ¶ added in v0.3.0
func (*DB) QueryRowContext ¶ added in v0.3.0
QueryRowContext runs a single-row query against the pool.
func (*DB) Ready ¶
Ready reports live connectivity: false before Start or after Shutdown, and otherwise the result of a ping bounded by the configured conn_timeout. The probe reflects the database now — readiness drops during an outage and recovers with it, at the cost of one bounded round trip per call.
func (*DB) Shutdown ¶
Shutdown clears readiness and closes the pool, returning the close error. Closing a pool that never connected is a clean no-op, so the drain-after-failed-startup path is safe. The context exists for the lifecycle hook signature; sql.DB.Close is synchronous.
func (*DB) Start ¶
Start establishes connectivity with a ping bounded by the configured conn_timeout and marks the database started. A failure wraps ErrConnectionFailed around the driver's error. There is no started guard: a repeat Start just pings again.
type Dialect ¶
type Dialect interface {
// Name identifies the dialect; it matches the provider's Provider
// constant.
Name() string
// Placeholder renders the 1-based nth bind parameter ("$1" for
// postgres, "@p1" for a future mssql).
Placeholder(n int) string
// MapError translates a driver error into the package's sentinels. At
// the connectivity slice it is the identity: nothing is classified and
// sql.ErrNoRows always flows through unchanged.
MapError(err error) error
}
Dialect is the seam a provider implements: the whole surface the base package needs from an engine. Query surfaces in later slices route SQL rendering and driver errors through it, so consumers import their provider only at the composition root.
type Env ¶
type Env struct {
Host string
Name string
User string
Password string
Port string
MaxOpenConns string
MaxIdleConns string
ConnMaxLifetime string
ConnMaxIdleTime string
ConnTimeout string
}
Env names the environment variables Config.Finalize reads, composed from the prefix it receives under the "database" segment: DATABASE_HOST, DATABASE_PORT, and the rest, prefixed by whatever config.EnvName produces. An empty name disables that one override; the zero value — an empty prefix — disables all of them. Populated by Finalize and exposed for introspection.
func NewEnv ¶
NewEnv composes the standard override names from a prefix under the "database" segment: DATABASE_HOST, DATABASE_PORT, and the rest, prefixed by whatever config.EnvName produces. An empty prefix returns the zero Env, disabling the overrides.
type Provider ¶
type Provider string
Provider types the constant each provider sub-module declares for typed selection at the composition root; database/postgres declares postgres.Provider.
type Session ¶ added in v0.3.0
type Session interface {
Dialect() Dialect
QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
}
Session is the querying surface higher layers run statements against, implemented by both DB and Tx: the dialect travels with the session, and the same read path works against the pool or inside a transaction. Write runners deliberately take *Tx, not Session, so the compiler enforces the consumer-owned transaction boundary.
type Tx ¶ added in v0.3.0
type Tx struct {
// contains filtered or unexported fields
}
Tx is one transaction over the wrapped pool, carrying the dialect so it satisfies Session and so commit errors can be classified. The consumer owns the boundary: DB.Begin opens it, Tx.Commit or Tx.Rollback closes it, and ExecTx wraps the whole unit.
func (*Tx) Commit ¶ added in v0.3.0
Commit commits the transaction, routing any commit error through the dialect's MapError — the one place a violation deferred to COMMIT can be classified.
func (*Tx) Dialect ¶ added in v0.3.0
Dialect returns the dialect the transaction's database was constructed with.
func (*Tx) ExecContext ¶ added in v0.3.0
ExecContext runs a statement inside the transaction, returning its result.
func (*Tx) QueryContext ¶ added in v0.3.0
QueryContext runs a query inside the transaction.
func (*Tx) QueryRowContext ¶ added in v0.3.0
QueryRowContext runs a single-row query inside the transaction.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package ast is the base module's statement layer: standard SQL composed as Go values — an abstract syntax tree rendered through the provider's dialect.
|
Package ast is the base module's statement layer: standard SQL composed as Go values — an abstract syntax tree rendered through the provider's dialect. |
|
Package exec is the base module's execution layer — the only layer that touches database/sql at runtime.
|
Package exec is the base module's execution layer — the only layer that touches database/sql at runtime. |
|
Package operation is the base module's contract layer: constructors shaped by what a consumer means to do, lowering to rendered statements from the ast package's vocabulary.
|
Package operation is the base module's contract layer: constructors shaped by what a consumer means to do, lowering to rendered statements from the ast package's vocabulary. |
|
postgres
module
|
|
|
Package seed loads reference data into a database from seed files.
|
Package seed loads reference data into a database from seed files. |