Documentation
¶
Overview ¶
Package libdbexec provides driver-agnostic interfaces (DBManager, Exec, QueryRower) for SQL access, implemented for PostgreSQL (lib/pq) and SQLite. WithTransaction pairs a CommitTx with a ReleaseTx meant for defer, and low-level driver errors are translated to package-level sentinels (ErrNotFound, ErrUniqueViolation, ErrDeadlockDetected, ...).
Index ¶
- Variables
- func SQLiteBusyTimeout(d time.Duration) *time.Duration
- func SQLiteCacheKiB(kib int) *int
- func SQLiteCachePages(pages int) *int
- func SQLiteConns(n int) *int
- func SetupLocalInstance(ctx context.Context, dbName, dbUser, dbPassword string) (string, *postgres.PostgresContainer, func(), error)
- type CommitTx
- type DBManager
- func NewPostgresDBManager(ctx context.Context, dsn string, schema string) (DBManager, error)
- func NewSQLiteDBManager(ctx context.Context, path string, schema string) (DBManager, error)
- func NewSQLiteDBManagerWithOptions(ctx context.Context, path string, schema string, opts SQLiteOptions) (DBManager, error)
- type Exec
- type QueryRower
- type ReleaseTx
- type SQLiteJournalMode
- type SQLiteOptions
- type SQLiteSynchronous
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNotFound is returned by Scan when sql.ErrNoRows is encountered. ErrNotFound = errors.New("libdb: not found") // ErrTxFailed indicates a failure during transaction finalization (Commit or Rollback). ErrTxFailed = errors.New("libdb: transaction failed") // ErrMaxRowsReached indicates a table's configured maximum row count would be exceeded. ErrMaxRowsReached = errors.New("max row count reached") // ErrUniqueViolation corresponds to unique key constraint errors (e.g., PostgreSQL code 23505). ErrUniqueViolation = errors.New("libdb: unique constraint violation") // ErrForeignKeyViolation corresponds to foreign key constraint errors (e.g., PostgreSQL code 23503). ErrForeignKeyViolation = errors.New("libdb: foreign key violation") // ErrNotNullViolation corresponds to not-null constraint errors (e.g., PostgreSQL code 23502). ErrNotNullViolation = errors.New("libdb: not null constraint violation") // ErrCheckViolation corresponds to check constraint errors (e.g., PostgreSQL code 23514). ErrCheckViolation = errors.New("libdb: check constraint violation") // ErrConstraintViolation is a generic error for constraint violations not specifically mapped. ErrConstraintViolation = errors.New("libdb: constraint violation") // ErrDeadlockDetected corresponds to deadlock errors (e.g., PostgreSQL code 40P01). ErrDeadlockDetected = errors.New("libdb: deadlock detected") // ErrSerializationFailure corresponds to serialization failures (e.g., PostgreSQL code 40001). ErrSerializationFailure = errors.New("libdb: serialization failure") // ErrLockNotAvailable corresponds to lock acquisition failures (e.g., PostgreSQL code 55P03). ErrLockNotAvailable = errors.New("libdb: lock not available") // ErrQueryCanceled corresponds to query cancellation (e.g., PostgreSQL code 57014 or context cancellation). ErrQueryCanceled = errors.New("libdb: query canceled") // ErrDataTruncation corresponds to data truncation errors (e.g., PostgreSQL code 22001). ErrDataTruncation = errors.New("libdb: data truncation error") // ErrNumericOutOfRange corresponds to numeric overflow errors (e.g., PostgreSQL code 22003). ErrNumericOutOfRange = errors.New("libdb: numeric value out of range") // ErrInvalidInputSyntax corresponds to syntax errors in data representation (e.g., PostgreSQL code 22P02). ErrInvalidInputSyntax = errors.New("libdb: invalid input syntax") // ErrUndefinedColumn corresponds to referencing an unknown column (e.g., PostgreSQL code 42703). ErrUndefinedColumn = errors.New("libdb: undefined column") // ErrUndefinedTable corresponds to referencing an unknown table (e.g., PostgreSQL code 42P01). ErrUndefinedTable = errors.New("libdb: undefined table") )
Predefined errors, checkable with errors.Is without relying on driver-specific error types or codes.
var ErrInvalidSQLiteOptions = errors.New("libdb: invalid sqlite options")
ErrInvalidSQLiteOptions reports a SQLiteOptions value that is rejected at construction. It covers both malformed values (an unknown journal mode, a negative pool limit) and combinations that are individually valid but unsafe together, notably a reduced synchronous level outside WAL journal mode.
Functions ¶
func SQLiteBusyTimeout ¶ added in v0.38.0
SQLiteBusyTimeout returns a pointer to d for SQLiteOptions.BusyTimeout, which distinguishes an unset timeout from an explicit zero.
func SQLiteCacheKiB ¶ added in v0.38.0
SQLiteCacheKiB returns a CacheSize requesting approximately kib kibibytes of page cache, applying SQLite's negative-means-KiB convention so callers never write the sign themselves. The argument is a magnitude: its sign is normalised away, so SQLiteCacheKiB cannot silently yield a page count. A zero magnitude is refused at construction.
func SQLiteCachePages ¶ added in v0.38.0
SQLiteCachePages returns a CacheSize requesting that many cache pages, whose byte cost depends on the database page size. The argument is a magnitude: its sign is normalised away, so SQLiteCachePages cannot silently yield a size in KiB. A zero magnitude is refused at construction.
func SQLiteConns ¶ added in v0.38.0
SQLiteConns returns a pointer to n for the pool limit fields, which distinguish an unset limit from an explicit zero.
func SetupLocalInstance ¶
func SetupLocalInstance(ctx context.Context, dbName, dbUser, dbPassword string) (string, *postgres.PostgresContainer, func(), error)
SetupLocalInstance starts an ephemeral PostgreSQL container for tests via testcontainers-go. It returns a ready-to-use connection string, the underlying container, and a cleanup func that stops the container. The cleanup func is always safe to call (even on error paths) except when SetupLocalInstance itself fails to start the container, in which case it returns a no-op cleanup.
Types ¶
type CommitTx ¶
CommitTx commits a transaction; call only on the success path. Returns nil, a wrapped ErrTxFailed, or a context error if ctx is done before the attempt.
type DBManager ¶
type DBManager interface {
// WithoutTransaction returns an executor operating directly on the connection
// group, outside any transaction; each operation may run on a different connection.
WithoutTransaction() Exec
// WithTransaction starts a transaction and returns an Exec bound to it, a
// CommitTx, and a ReleaseTx (idempotent, safe for defer, rolls back if not
// committed). onRollback handlers run only after a successful rollback and
// must not touch the transaction.
WithTransaction(ctx context.Context, onRollback ...func()) (Exec, CommitTx, ReleaseTx, error)
// Close terminates the underlying database connection group.
Close() error
}
DBManager is the main entry point for database interactions: obtaining executors and managing the connection lifecycle. Typical usage starts a transaction with WithTransaction, defers the returned ReleaseTx immediately, does work through the returned Exec, then calls CommitTx on the success path.
func NewPostgresDBManager ¶
NewPostgresDBManager creates a new DBManager for PostgreSQL. It opens a connection group using the provided DSN, pings the database to verify connectivity, and optionally executes an initial schema setup query. Note: For production schema management, using dedicated migration tools is recommended over passing a simple schema string here.
func NewSQLiteDBManager ¶
NewSQLiteDBManager creates a new DBManager for SQLite. path is the database file path (e.g. "./.contenox/local.db" or "file:local.db"). The parent directory is created if missing. schema is applied on open (e.g. runtimetypes.SchemaSQLite).
It is NewSQLiteDBManagerWithOptions with a zero SQLiteOptions, which is defined to reproduce this constructor's DSN and pool configuration exactly. Callers needing synchronous, cache_size, or pool limits use that constructor.
func NewSQLiteDBManagerWithOptions ¶ added in v0.38.0
func NewSQLiteDBManagerWithOptions(ctx context.Context, path string, schema string, opts SQLiteOptions) (DBManager, error)
NewSQLiteDBManagerWithOptions creates a DBManager for SQLite tuned by opts. path and schema behave as in NewSQLiteDBManager; a zero opts is byte-for-byte equivalent to it.
opts is validated before anything is opened, so an unsafe combination such as a reduced synchronous level outside WAL fails here rather than silently degrading durability. See SQLiteOptions for what each zero value means.
Capping SQLiteOptions.MaxOpenConns suits a single-writer database on network-attached storage, but database/sql does not distinguish readers from writers: a cap of 1 serialises every query, forfeits WAL's concurrent-reader advantage in-process, lets one slow query stall all others, and turns any code path that issues a query on this DBManager while holding one of its transactions open into a deadlock instead of an error. busy_timeout stays load-bearing regardless of the cap, because it governs contention this process cannot see: another pod during a Recreate rollout, a Litestream sidecar or backup tool on the same file, WAL checkpointing, and any second DBManager over the same path.
type Exec ¶
type Exec interface {
ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
// QueryContext executes a query returning rows. Callers must check rows.Err() after iterating.
QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
// QueryRowContext always returns a non-nil QueryRower; errors surface from its Scan.
QueryRowContext(ctx context.Context, query string, args ...any) QueryRower
// DriverName returns the database driver name ("postgres", "sqlite").
DriverName() string
}
Exec is the common interface for executing database operations, whether within a transaction or directly on the connection group. Implementations must translate driver errors into the package's Err* sentinels.
type QueryRower ¶
type QueryRower interface {
// Scan returns ErrNotFound if no row matched; other errors are translated too.
Scan(dest ...any) error
}
QueryRower wraps *sql.Row so Scan errors (like sql.ErrNoRows) are translated consistently.
type ReleaseTx ¶
type ReleaseTx func() error
ReleaseTx rolls back a transaction if it wasn't committed and is a no-op otherwise; idempotent and meant for defer.
type SQLiteJournalMode ¶ added in v0.38.0
type SQLiteJournalMode string
SQLiteJournalMode selects PRAGMA journal_mode. The zero value selects WAL, which is the only mode the reduced synchronous levels are corruption-safe in.
const ( SQLiteJournalDefault SQLiteJournalMode = "" SQLiteJournalWAL SQLiteJournalMode = "WAL" SQLiteJournalDelete SQLiteJournalMode = "DELETE" SQLiteJournalTruncate SQLiteJournalMode = "TRUNCATE" SQLiteJournalPersist SQLiteJournalMode = "PERSIST" SQLiteJournalMemory SQLiteJournalMode = "MEMORY" SQLiteJournalOff SQLiteJournalMode = "OFF" )
SQLite journal modes. SQLiteJournalDefault is the zero value and resolves to SQLiteJournalWAL.
type SQLiteOptions ¶ added in v0.38.0
type SQLiteOptions struct {
// JournalMode selects PRAGMA journal_mode. The zero value selects WAL.
// Journal mode is a persistent property of the database file, so changing
// it converts the file on open.
JournalMode SQLiteJournalMode
// Synchronous selects PRAGMA synchronous. The zero value emits no pragma
// and leaves SQLite's FULL default. SQLiteSynchronousNormal trades an
// fsync per commit for durability of the most recent transactions across
// a power loss; in WAL mode it cannot corrupt the database, which is why
// it is refused outside WAL.
Synchronous SQLiteSynchronous
// BusyTimeout sets PRAGMA busy_timeout, how long SQLite retries a locked
// database before returning SQLITE_BUSY. Nil means the 5000ms default. A
// pointer to zero disables waiting and fails contended writes immediately.
// Sub-millisecond precision is not representable and is refused.
BusyTimeout *time.Duration
// DisableForeignKeys turns off PRAGMA foreign_keys. The zero value keeps
// foreign keys enforced. The sense is inverted so that the zero value
// matches the enforced default.
DisableForeignKeys bool
// CacheSize sets PRAGMA cache_size. Nil emits no pragma. A negative value
// is a size in KiB, a positive value is a count of pages -- this is
// SQLite's own sign convention and it is easy to invert, so prefer
// SQLiteCacheKiB or SQLiteCachePages over a bare literal. A pointer to
// zero is refused: it would disable the page cache, and it is far more
// often a zero value leaking through than a deliberate choice.
CacheSize *int
// MaxOpenConns caps total open connections via sql.DB.SetMaxOpenConns.
// Nil leaves the pool unbounded, which is Go's default. A pointer to zero
// is also unbounded, stated explicitly. See NewSQLiteDBManagerWithOptions
// for what capping this to 1 costs.
MaxOpenConns *int
// MaxIdleConns caps retained idle connections via
// sql.DB.SetMaxIdleConns. Nil leaves Go's default of 2. A pointer to zero
// retains none, reopening the file on every acquisition. database/sql
// silently clamps this to MaxOpenConns when it is larger.
MaxIdleConns *int
// ConnMaxLifetime bounds how long a connection may be reused via
// sql.DB.SetConnMaxLifetime. The zero value means unlimited, matching
// database/sql, so unset and zero are indistinguishable here.
ConnMaxLifetime time.Duration
// ConnMaxIdleTime bounds how long a connection may sit idle via
// sql.DB.SetConnMaxIdleTime. The zero value means unlimited, matching
// database/sql, so unset and zero are indistinguishable here.
ConnMaxIdleTime time.Duration
}
SQLiteOptions tunes a SQLite DBManager.
The zero value reproduces NewSQLiteDBManager byte for byte: WAL journal mode, a 5000ms busy timeout, foreign keys enforced, no synchronous or cache_size pragma, and Go's default connection pool (unbounded, two idle connections, no lifetime cap).
Numeric fields whose zero is a meaningful setting are pointers, so nil means "leave alone" and a pointer to zero means "set it to zero". Fields whose zero already coincides with the database/sql default (ConnMaxLifetime, ConnMaxIdleTime, both meaning unlimited) are plain values, because there is no observable difference between unset and zero for them.
type SQLiteSynchronous ¶ added in v0.38.0
type SQLiteSynchronous string
SQLiteSynchronous selects PRAGMA synchronous. The zero value emits no pragma at all, leaving the driver default (FULL) in place.
const ( SQLiteSynchronousDefault SQLiteSynchronous = "" SQLiteSynchronousOff SQLiteSynchronous = "OFF" SQLiteSynchronousNormal SQLiteSynchronous = "NORMAL" SQLiteSynchronousFull SQLiteSynchronous = "FULL" SQLiteSynchronousExtra SQLiteSynchronous = "EXTRA" )
SQLite synchronous levels. SQLiteSynchronousDefault is the zero value and emits no pragma. SQLiteSynchronousNormal and SQLiteSynchronousOff are accepted only in WAL journal mode.