Documentation
¶
Overview ¶
Package database provides interface abstractions for interacting with relational data stores
Index ¶
- Variables
- func BlobOrNil(b []byte) any
- func BoolFromNullBool(b sql.NullBool) bool
- func CoerceTime(v any) (time.Time, bool)
- func CursorOrder(descending bool) (direction, comparison string)
- func Float32FromNullString(s sql.NullString) float32
- func Float32FromString(s string) float32
- func Float32PointerFromNullString(f sql.NullString) *float32
- func Float64PointerFromNullString(f sql.NullString) *float64
- func Int32PointerFromNullInt32(i sql.NullInt32) *int32
- func NullBoolFromBool(b bool) sql.NullBool
- func NullBoolFromBoolPointer(b *bool) sql.NullBool
- func NullInt32FromInt32Pointer(i *int32) sql.NullInt32
- func NullInt32FromUint8Pointer(i *uint8) sql.NullInt32
- func NullInt32FromUint16(i uint16) sql.NullInt32
- func NullInt32FromUint16Pointer(i *uint16) sql.NullInt32
- func NullInt32FromUint32Pointer(i *uint32) sql.NullInt32
- func NullInt64FromUint32Pointer(f *uint32) sql.NullInt64
- func NullStringFromFloat32(f float32) sql.NullString
- func NullStringFromFloat32Pointer(f *float32) sql.NullString
- func NullStringFromFloat64Pointer(f *float64) sql.NullString
- func NullStringFromString(s string) sql.NullString
- func NullStringFromStringPointer(s *string) sql.NullString
- func NullTimeFromTime(t time.Time) sql.NullTime
- func NullTimeFromTimePointer(t *time.Time) sql.NullTime
- func RunInTransaction(ctx context.Context, writeDB *sql.DB, ...) error
- func ScanAll[T any](ctx context.Context, q SQLQueryExecutor, subject, query string, args []any, ...) (results []T, err error)
- func ScanStrings(ctx context.Context, q SQLQueryExecutor, subject, query string, args []any) ([]string, error)
- func StringFromFloat32(f float32) string
- func StringFromFloat64(f float64) string
- func StringFromNullString(nt sql.NullString) string
- func StringPointerFromNullString(nt sql.NullString) *string
- func TimeFromNullTime(nt sql.NullTime) time.Time
- func TimePointerFromNullTime(nt sql.NullTime) *time.Time
- func Uint16PointerFromNullInt32(f sql.NullInt32) *uint16
- func Uint32PointerFromNullInt32(f sql.NullInt32) *uint32
- func Uint32PointerFromNullInt64(f sql.NullInt64) *uint32
- type Client
- type ClientConfig
- type Manager
- type Migrator
- type RawAccess
- type ResultIterator
- type SQLQueryExecutor
- type SQLQueryExecutorAndTransactionManager
- type SQLTransactionManager
- type Scanner
Constants ¶
This section is empty.
Variables ¶
var ( // ErrDatabaseNotReady indicates the given database is not ready. ErrDatabaseNotReady = platformerrors.New("database is not ready yet") )
var ErrUserAlreadyExists = platformerrors.New("user already exists")
ErrUserAlreadyExists indicates that a user with that username has already been created.
Functions ¶
func BlobOrNil ¶
BlobOrNil maps an empty encoding to a SQL NULL rather than an empty blob.
"No value" and "an empty value" mean the same thing in every column in this module that holds an encoded payload — no request, no failure map, no snapshot — and storing two renderings of it would make the round trip depend on which call site wrote the row: one reader gets nil back and another gets a zero-length slice, from rows that were written to mean the same thing.
func BoolFromNullBool ¶
func CoerceTime ¶
CoerceTime normalizes whatever a driver hands back for a timestamp read as `any`, reporting whether it recognized one.
Timestamps are scanned as `any` rather than sql.NullTime because the drivers disagree. pgx and go-sql-driver return a time.Time, but modernc's SQLite driver stores a bound time.Time as Go's own String() rendering, and an aggregate over such a column loses the declared DATETIME affinity — so it comes back as a plain string that sql.NullTime refuses outright.
A NULL reports false, and callers treat that as "no value" rather than as the zero time: an empty backlog is not a row created at the epoch.
func CursorOrder ¶
CursorOrder reports the ORDER BY direction and the comparison operator a keyset-paginated read uses for a given sort direction.
It is one function because the two halves have to agree and nothing checks that they do. A descending page that kept "id > cursor" reads the wrong side of the boundary: the first page comes back, and every page after it skips straight past the rows the caller asked for. That failure produces no error and no empty result — just a listing quietly missing its middle.
func Float32FromNullString ¶
func Float32FromNullString(s sql.NullString) float32
func Float32FromString ¶
func Float32PointerFromNullString ¶
func Float32PointerFromNullString(f sql.NullString) *float32
func Float64PointerFromNullString ¶
func Float64PointerFromNullString(f sql.NullString) *float64
func NullBoolFromBool ¶
func NullBoolFromBoolPointer ¶
func NullInt32FromUint16 ¶
func NullStringFromFloat32 ¶
func NullStringFromFloat32(f float32) sql.NullString
func NullStringFromFloat32Pointer ¶
func NullStringFromFloat32Pointer(f *float32) sql.NullString
func NullStringFromFloat64Pointer ¶
func NullStringFromFloat64Pointer(f *float64) sql.NullString
func NullStringFromString ¶
func NullStringFromString(s string) sql.NullString
func NullStringFromStringPointer ¶
func NullStringFromStringPointer(s *string) sql.NullString
func RunInTransaction ¶
func RunInTransaction( ctx context.Context, writeDB *sql.DB, rollback func(ctx context.Context, tx SQLQueryExecutorAndTransactionManager), fn func(tx SQLQueryExecutor) error, ) error
RunInTransaction begins a transaction on writeDB, invokes fn with that transaction as the sole query executor, and commits when fn returns nil. It is the shared engine behind each Client's WithTransaction method — application code should prefer Client.WithTransaction, which wraps this with the implementation's observability.
fn receives the transaction as a bare executor (SQLQueryExecutor), not the transaction handle: it cannot commit or roll back, and its statements cannot accidentally target the read replica or another connection. Lifecycle is managed entirely here:
- rollback is invoked (with the transaction) on any non-nil error from fn, and the error is returned unwrapped.
- a panic inside fn triggers rollback and is then re-raised, so no connection leaks and the caller still observes the failure.
- a nil return from fn commits; commit errors are wrapped and returned.
A failed commit has already released the connection back to the pool, so no second rollback is attempted (it would only surface a spurious ErrTxDone).
func ScanAll ¶
func ScanAll[T any]( ctx context.Context, q SQLQueryExecutor, subject, query string, args []any, scan func(Scanner) (T, error), ) (results []T, err error)
ScanAll runs a query and collects one value per row through scan.
It exists because the loop around a *sql.Rows is four separate obligations and every store in this module had written all four by hand, eighteen times: close the rows whatever happens, surface a close failure only when nothing worse already went wrong, check rows.Err() after the loop rather than trusting Next's false, and return the scan error rather than the close error when both occur. Missing the third silently truncates a result set when the connection drops mid-read — the loop simply ends, and the caller gets a short list with no error to say so. Missing the second masks the real cause behind the cleanup's.
subject names the rows in the close failure's message: "outbox id", "dataprivacy request". It is a noun phrase, not a sentence — " rows" is appended.
The named error return is load-bearing. The deferred close writes to it, which is how a close failure on an otherwise-successful read still reaches the caller; a plain `return results, nil` would discard it.
func ScanStrings ¶
func ScanStrings(ctx context.Context, q SQLQueryExecutor, subject, query string, args []any) ([]string, error)
ScanStrings is ScanAll for the single-column reads that collect identifiers, which is what most of them are.
func StringFromFloat32 ¶
func StringFromFloat64 ¶
func StringFromNullString ¶
func StringFromNullString(nt sql.NullString) string
func StringPointerFromNullString ¶
func StringPointerFromNullString(nt sql.NullString) *string
Types ¶
type Client ¶
type Client interface {
// Dialect reports the SQL dialect this client speaks.
//
// It is on the client because the two always travel together: every package in
// this module that emits SQL holds a Client and a dialect.Dialect side by side,
// and nothing previously stopped the pair disagreeing — a caller could hand
// dialect.MySQL to a store backed by a Postgres client and get syntactically
// valid SQL that the server rejects at runtime. Sourcing the dialect from the
// client makes that mismatch unrepresentable rather than merely unlikely.
Dialect() dialect.Dialect
// Reader returns an executor for the read database. It exposes no transaction
// control by design; use WithTransaction for anything transactional.
Reader() SQLQueryExecutor
// Writer returns an executor for the write database, for single, non-transactional
// statements. Multi-statement work belongs in WithTransaction.
Writer() SQLQueryExecutor
// WithTransaction begins a transaction on the write database, invokes fn with it as
// the sole executor, commits on a nil return, and rolls back on error or panic.
//
// fn receives only an executor, not the transaction handle: it cannot commit or
// roll back. Returning an error (or panicking) is the sole way to abort, and drives
// exactly one rollback — so fn can't roll back and then also return an error, which
// would otherwise trigger a redundant second rollback.
WithTransaction(ctx context.Context, fn func(querier SQLQueryExecutor) error) error
Close() error
CurrentTime() time.Time
}
Client is the safe surface for database access. It deliberately does not expose a raw *sql.DB: reads and single-statement writes go through the narrow executors returned by Reader and Writer (which cannot begin a transaction), and all transactional work goes through WithTransaction. A transaction is therefore unreachable except via WithTransaction, so statements cannot accidentally run outside a transaction or against the read replica.
Callers that genuinely need the concrete pool (migrations, session-pinned advisory locks, driver features off this seam) can obtain it via the RawAccess capability.
type ClientConfig ¶
type ClientConfig interface {
GetReadConnectionString() string
GetWriteConnectionString() string
GetMaxPingAttempts() uint64
GetPingWaitPeriod() time.Duration
GetMaxIdleConns() int
GetMaxOpenConns() int
GetConnMaxLifetime() time.Duration
}
ClientConfig provides the configuration needed by database clients. This interface allows the config package to provide configuration without creating an import cycle.
type Manager ¶
type Manager interface {
CreateUser(ctx context.Context, username, password string) error
DeleteUser(ctx context.Context, username string) error
CreateDatabase(ctx context.Context, dbName, owner string) error
DeleteDatabase(ctx context.Context, dbName string) error
UserExists(ctx context.Context, username string) (bool, error)
DatabaseExists(ctx context.Context, dbName string) (bool, error)
GrantUserAccessToTable(ctx context.Context, username, schema, table, privilege string) error
UserCanAccessDatabase(ctx context.Context, username, dbName string) (bool, error)
}
type Migrator ¶
Migrator is an interface for running database migrations. Implementations handle the specifics of migration execution (e.g., darwin, goose, etc.)
type RawAccess ¶
RawAccess is an optional capability exposing the concrete *sql.DB pools for callers that genuinely need them — schema migrations, session-pinned advisory locks, or driver features outside the executor seam. A caller obtains it by asserting on a Client:
raw, ok := client.(database.RawAccess)
Reaching for RawAccess is a deliberate step outside the safe Client surface; prefer Reader, Writer, and WithTransaction wherever they suffice. Providers may expose further, provider-specific capabilities the same way — e.g. the postgres package's PgxAccess, which exposes the native pgx pools backing these handles.
type ResultIterator ¶
ResultIterator represents any iterable database response (i.e. sql.Rows).
type SQLQueryExecutor ¶
type SQLQueryExecutor interface {
ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
PrepareContext(context.Context, string) (*sql.Stmt, error)
QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
}
SQLQueryExecutor is a subset interface for sql.{DB|Tx} objects.
type SQLQueryExecutorAndTransactionManager ¶
type SQLQueryExecutorAndTransactionManager interface {
SQLQueryExecutor
SQLTransactionManager
}
SQLQueryExecutorAndTransactionManager is a subset interface for sql.{DB|Tx} objects.
type SQLTransactionManager ¶
type SQLTransactionManager interface {
Rollback() error
}
SQLTransactionManager is a subset interface for sql.{DB|Tx} objects.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package databasecfg selects and builds a database.Client — Postgres, MySQL, or SQLite — and owns the connection strings each of them wants.
|
Package databasecfg selects and builds a database.Client — Postgres, MySQL, or SQLite — and owns the connection strings each of them wants. |
|
Package ddl renders a package's embedded schema against a dialect and a table prefix, and vets the prefix against every identifier the schema would create.
|
Package ddl renders a package's embedded schema against a dialect and a table prefix, and vets the prefix against every identifier the schema would create. |
|
Package dialect names the SQL dialects the module's SQL-emitting packages support, and carries the small helpers every one of them otherwise reimplements: bind-marker rendering, identifier vetting, and DDL statement splitting.
|
Package dialect names the SQL dialects the module's SQL-emitting packages support, and carries the small helpers every one of them otherwise reimplements: bind-marker rendering, identifier vetting, and DDL statement splitting. |
|
internal
|
|
|
sqlclient
Package sqlclient holds the parts of a database.Client that do not vary by SQL driver.
|
Package sqlclient holds the parts of a database.Client that do not vary by SQL driver. |
|
Package migrate provides the platform's standard database.Migrator: embedded SQL migrations with the operational discipline consumers otherwise hand-roll — an instance-based provider (no global goose state, so parallel tests never race), and on Postgres a session advisory lock that serializes concurrently booting replicas, with probe timeouts tightened so a waiting replica notices the winner promptly instead of goose's leisurely default.
|
Package migrate provides the platform's standard database.Migrator: embedded SQL migrations with the operational discipline consumers otherwise hand-roll — an instance-based provider (no global goose state, so parallel tests never race), and on Postgres a session advisory lock that serializes concurrently booting replicas, with probe timeouts tightened so a waiting replica notices the winner promptly instead of goose's leisurely default. |
|
Package databasemock provides moq-generated mocks for the database package.
|
Package databasemock provides moq-generated mocks for the database package. |
|
Package mysql provides an interface for writing to a MySQL instance.
|
Package mysql provides an interface for writing to a MySQL instance. |
|
tableaccess
Package tableaccess is the MySQL database.Manager: the administrative surface that creates users and databases and grants table privileges, as distinct from the query path a database.Client serves.
|
Package tableaccess is the MySQL database.Manager: the administrative surface that creates users and databases and grants table privileges, as distinct from the query path a database.Client serves. |
|
Package postgres provides an interface for writing to a Postgres instance.
|
Package postgres provides an interface for writing to a Postgres instance. |
|
pgnotify
Package pgnotify turns Postgres LISTEN/NOTIFY into a wake-up signal for a poller.
|
Package pgnotify turns Postgres LISTEN/NOTIFY into a wake-up signal for a poller. |
|
tableaccess
Package tableaccess is the PostgreSQL database.Manager: the administrative surface that creates roles and databases and grants table privileges, as distinct from the query path a database.Client serves.
|
Package tableaccess is the PostgreSQL database.Manager: the administrative surface that creates roles and databases and grants table privileges, as distinct from the query path a database.Client serves. |
|
Package querygen emits sqlc input for tables shaped the way this module's row conventions expect.
|
Package querygen emits sqlc input for tables shaped the way this module's row conventions expect. |
|
Package sqlite provides an interface for writing to a SQLite database.
|
Package sqlite provides an interface for writing to a SQLite database. |
|
tableaccess
Package tableaccess is the SQLite database.Manager, and every one of its operations refuses.
|
Package tableaccess is the SQLite database.Manager, and every one of its operations refuses. |