database

package
v0.58.0 Latest Latest
Warning

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

Go to latest
Published: Aug 8, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package database provides cross-database query building utilities

Package database provides performance tracking for database operations

Index

Constants

View Source
const (
	DefaultSlowQueryThreshold = tracking.DefaultSlowQueryThreshold
	DefaultMaxQueryLength     = tracking.DefaultMaxQueryLength
)

Re-export internal constants

View Source
const (
	PostgreSQL = types.PostgreSQL
	Oracle     = types.Oracle
)

Re-export database vendor identifiers so existing callers using the database package continue to compile while the single source of truth lives in types.

Variables

View Source
var (
	NewTrackedDB                  = tracking.NewDB
	NewTrackedConnection          = tracking.NewConnection
	TrackDBOperation              = tracking.TrackDBOperation
	NewTrackingSettings           = tracking.NewSettings
	RegisterConnectionPoolMetrics = tracking.RegisterConnectionPoolMetrics

	// SetObservabilityEnabled gates DB-operation OpenTelemetry span/metric emission.
	// Called once at app bootstrap from the resolved observability.enabled value so
	// that, when observability is disabled, the tracking layer builds no span/metric
	// attributes (honoring the no-op provider's zero-overhead contract).
	SetObservabilityEnabled = tracking.SetObservabilityEnabled

	// WithRepositoryMethod records the business-operation (repository) method name
	// on ctx so the tracking layer emits it as the `repository.method` attribute on
	// the db.client.operation.duration metric. Pass the resulting context to the
	// database call:
	//
	//	ctx = database.WithRepositoryMethod(ctx, "GetCustomer")
	//	rows, err := db.Query(ctx, query, args...)
	//
	// The method name must be a static, low-cardinality identifier.
	WithRepositoryMethod = tracking.WithRepositoryMethod
	// RepositoryMethodFromContext returns the repository method name stored on ctx
	// by WithRepositoryMethod, and whether one was set.
	RepositoryMethodFromContext = tracking.RepositoryMethodFromContext
)

Re-export internal functions as public API

View Source
var ErrNoRows = fmt.Errorf("database: %w", sql.ErrNoRows)

ErrNoRows reports a zero-row outcome: a SELECT that matched nothing (ExecuteQuerySingle) or an UPDATE/DELETE expected to match exactly one row but matched none (ExecuteUpdateOne). It wraps sql.ErrNoRows so errors.Is(err, sql.ErrNoRows) and IsNotFound(err) also match.

Functions

func ConstraintName added in v0.40.0

func ConstraintName(err error) (string, bool)

ConstraintName returns the violated constraint name when the driver exposes it. PostgreSQL populates this for constraint violations; Oracle does not, so ConstraintName returns ("", false) for Oracle errors.

func ExecuteInsert added in v0.55.0

func ExecuteInsert(ctx context.Context, exec Executor, q SQLProvider, op string) error

ExecuteInsert runs an INSERT. It does not inspect RowsAffected: a successful INSERT is success, and vendor differences around LastInsertId/RETURNING are out of scope here.

func ExecuteQueryMany added in v0.55.0

func ExecuteQueryMany(ctx context.Context, exec Executor, q SQLProvider, op string, scan func(rows *sql.Rows) error) error

ExecuteQueryMany runs q and invokes scan once per row. Zero rows is not an error (the caller sees an empty result). A scan callback error aborts iteration and is wrapped at StageScan. A nil scan is rejected up front at StageBuild rather than left to panic once a row arrives: unlike a nil q or exec (which fail loudly at the call site, in the caller's own frame), a nil scan is data-dependent — it only panics once the query returns a row, so a zero-row result would let the mistake pass silently in dev and blow up later in prod.

func ExecuteQuerySingle added in v0.55.0

func ExecuteQuerySingle(ctx context.Context, exec Executor, q SQLProvider, op string, dest ...any) error

ExecuteQuerySingle runs q and scans the first row into dest. It returns an op-labeled ErrNoRows wrap when the query matches no rows, and *ExecError for infrastructure failures. Extra rows beyond the first are ignored (sql.Row parity): after a successful scan, the rows are closed explicitly (mirroring sql.Row.Scan) so a driver error that only surfaces at Close — a truncated result, a connection fault mid-statement — is not silently swallowed; that failure is reported at StageClose. op labels errors only — it does not feed metrics or tracing; use database.WithRepositoryMethod(ctx, ...) for attribution. Typical mapping in an app:

err := database.ExecuteQuerySingle(ctx, tx, q, "ownership", &row.ID, &row.Name)
switch {
case errors.Is(err, database.ErrNoRows):
	return domain.ErrResourceNotFound // business 404
case err != nil:
	return fmt.Errorf("load ownership: %w", err) // infra 500
}

func ExecuteUpdate added in v0.55.0

func ExecuteUpdate(ctx context.Context, exec Executor, q SQLProvider, op string) (int64, error)

ExecuteUpdate runs an UPDATE/DELETE and returns the affected-row count. It does not interpret zero: a legitimately-zero write (e.g. an UPDATE guarded by a WHERE clause that may match nothing, such as an idempotent state transition, or a bulk statement with no matching rows) returns (0, nil). Use ExecuteUpdateOne when zero affected rows should surface as ErrNoRows.

func ExecuteUpdateOne added in v0.55.0

func ExecuteUpdateOne(ctx context.Context, exec Executor, q SQLProvider, op string) error

ExecuteUpdateOne runs an UPDATE/DELETE expected to match exactly one row and enforces that cardinality: zero rows affected returns an op-labeled ErrNoRows wrap, so "not found" surfaces uniformly with ExecuteQuerySingle; more than one row affected returns *ExecError at StageRowsAffected instead of silently reporting success, because a broader-than-intended WHERE predicate updating several rows is a data-integrity failure, not a "found it" outcome; exactly one row affected returns nil. Use ExecuteUpdate when a non-singular match is legitimate (bulk or idempotent writes), or when only the caller can tell "absent" from "already in the target state".

func GetSupportedDatabaseTypes

func GetSupportedDatabaseTypes() []string

GetSupportedDatabaseTypes returns a list of supported database types

func IsForeignKeyViolation added in v0.40.0

func IsForeignKeyViolation(err error) bool

IsForeignKeyViolation reports whether err is a foreign-key constraint violation (PostgreSQL SQLSTATE 23503, Oracle ORA-02291).

func IsNotFound added in v0.40.0

func IsNotFound(err error) bool

IsNotFound reports whether err is a no-rows result (sql.ErrNoRows). Scan paths produce it natively; on the write path, ExecuteUpdateOne surfaces it via ErrNoRows (which wraps sql.ErrNoRows) when an UPDATE/DELETE expected to match exactly one row matches none, so IsNotFound matches that too. ExecuteUpdate does not produce it — zero rows affected is (0, nil) there.

func IsUniqueViolation added in v0.40.0

func IsUniqueViolation(err error) bool

IsUniqueViolation reports whether err is a unique or primary-key constraint violation (PostgreSQL SQLSTATE 23505, Oracle ORA-00001). It traverses the framework's error wrap chain via errors.As, so callers must wrap driver errors with %w (not %v) for it to work.

func ValidateDatabaseType

func ValidateDatabaseType(dbType string) error

ValidateDatabaseType reports an error when dbType is not among the supported database types.

func WithTx added in v0.40.0

func WithTx(ctx context.Context, db Interface, fn func(ctx context.Context, tx Tx) error) error

WithTx runs fn inside a database transaction. It commits when fn returns nil, rolls back and returns fn's original error when fn returns an error, and rolls back then re-panics if fn panics. After a successful commit the deferred rollback is skipped (a committed flag guards it), so there is no post-commit rollback noise on the happy path.

fn must use the provided tx for all database work; using the outer db handle inside fn escapes the transaction.

func WithTxOptions added in v0.40.0

func WithTxOptions(ctx context.Context, db Interface, opts *sql.TxOptions, fn func(ctx context.Context, tx Tx) error) error

WithTxOptions behaves like WithTx but begins the transaction with the given options (isolation level, read-only mode) via BeginTx. A nil opts is equivalent to WithTx.

Types

type Connector added in v0.9.0

type Connector func(*config.DatabaseConfig, logger.Logger) (Interface, error)

Connector creates database connections from configuration

type DBConfigProvider added in v0.26.0

type DBConfigProvider interface {
	// DBConfig returns the database configuration for the given key.
	// For single-tenant apps, key will be "". For multi-tenant, key will be the tenant ID.
	DBConfig(ctx context.Context, key string) (*config.DatabaseConfig, error)
}

DBConfigProvider provides per-key database configurations. This interface abstracts where tenant-specific database configs come from.

type DbManager added in v0.9.0

type DbManager struct {
	// contains filtered or unexported fields
}

DbManager manages database connections by string keys. It provides lazy initialization, LRU eviction, and cleanup for database connections. The manager is key-agnostic - it doesn't know about tenants, just manages named connections.

It is a thin adapter over internal/resourcepool.Pool, which owns the ADR-032 lease/evict/close protocol (seed leases, LRU eviction, idle cleanup, and the closed-pool guard). The manager keeps only the database-specific config resolution.

func NewDbManager added in v0.9.0

func NewDbManager(resourceSource DBConfigProvider, log logger.Logger, opts DbManagerOptions, connector Connector) *DbManager

NewDbManager creates a new database manager

func (*DbManager) Close added in v0.9.0

func (m *DbManager) Close() error

Close closes all database connections and stops cleanup

func (*DbManager) Get added in v0.9.0

func (m *DbManager) Get(ctx context.Context, key string) (Interface, ReleaseFunc, error)

Get returns a database connection for the given key plus a ReleaseFunc the caller must invoke when finished with it for the current unit of work (typically deferred). For single-tenant, use key "". For multi-tenant, use the tenant ID. Connections are created lazily and cached with LRU eviction; the lease prevents a connection that is evicted while in use from being closed under an active caller (the #606 race). Once Close has run, Get fails closed rather than resurrecting a connection (F22). On error the returned ReleaseFunc is nil — check err first.

func (*DbManager) Size added in v0.9.0

func (m *DbManager) Size() int

Size returns the number of active connections

func (*DbManager) StartCleanup added in v0.9.0

func (m *DbManager) StartCleanup(interval time.Duration)

StartCleanup starts the background cleanup routine for idle connections. A non-positive interval substitutes the documented 5-minute default.

func (*DbManager) Stats added in v0.9.0

func (m *DbManager) Stats() map[string]any

Stats returns statistics about the connection pool

func (*DbManager) StopCleanup added in v0.9.0

func (m *DbManager) StopCleanup()

StopCleanup stops the background cleanup routine

type DbManagerOptions added in v0.9.0

type DbManagerOptions struct {
	MaxSize int           // Cached-connection cap; <=0 uses a default (not unlimited).
	IdleTTL time.Duration // Idle-connection lifetime; <=0 uses a default (not disabled).
}

DbManagerOptions configures the DbManager

type ExecError added in v0.55.0

type ExecError struct {
	Op    string // caller-supplied operation label
	Stage ExecStage
	Err   error
}

ExecError is an infrastructure failure from an Execute* helper: the SQL could not be built, executed, scanned, or iterated. Zero-row outcomes are NOT ExecErrors — they are returned as op-labeled wraps of ErrNoRows.

func (*ExecError) Error added in v0.55.0

func (e *ExecError) Error() string

func (*ExecError) Unwrap added in v0.55.0

func (e *ExecError) Unwrap() error

type ExecStage added in v0.55.0

type ExecStage string

ExecStage identifies where inside a helper an infrastructure failure occurred.

const (
	StageBuild   ExecStage = "build"
	StageExec    ExecStage = "exec"
	StageScan    ExecStage = "scan"
	StageIterate ExecStage = "iterate"
	StageClose   ExecStage = "close"
	// StageRowsAffected covers two distinct failures at the same point in the
	// pipeline: the driver's RowsAffected() call itself erroring, or (in
	// ExecuteUpdateOne only) RowsAffected() succeeding with a count that isn't
	// the exactly-one cardinality the helper promises.
	StageRowsAffected ExecStage = "rows_affected"
)

type Executor added in v0.55.0

type Executor interface {
	Query(ctx context.Context, query string, args ...any) (*sql.Rows, error)
	Exec(ctx context.Context, query string, args ...any) (sql.Result, error)
}

Executor is the minimal query/exec surface shared by a database connection (Interface) and a transaction (Tx), so the Execute* helpers run identically inside or outside a transaction. Executor is a closed 2-method seam: future capabilities (batch, prepared statements) arrive as separate interfaces, never as additional methods here — adding a method to a shipped exported interface is apidiff-INCOMPATIBLE.

type Interface

type Interface = types.Interface

Interface defines the common database operations supported by the framework. This type alias maintains backward compatibility while the actual interfaces are now defined in the database/types package to avoid import cycles.

func NewConnection

func NewConnection(cfg *config.DatabaseConfig, log logger.Logger) (Interface, error)

NewConnection creates a tracked database connection based on the provided configuration.

It initializes a concrete driver connection for the configured database type, wraps it with performance/tracing tracking, and attaches server metadata (host, port and an OTel namespace) to the tracking wrapper when available.

Errors are returned if cfg is nil, cfg.Type is not supported (supported: "postgresql", "oracle"), or if the underlying driver initialization fails.

Every unrecognized type is an error here, including the empty string. Classifying a database as intentionally absent is a config-layer verdict, made by the resolver that knows WHICH database it is resolving (config.TenantStore.DBConfig) — the factory sees no key, so it cannot tell a deliberately database-free service from a half-provisioned tenant, where absence is never legitimate. See ADR-047.

type QueryBuilder

type QueryBuilder struct {
	*builder.QueryBuilder
}

QueryBuilder provides vendor-specific SQL query building. This is a compatibility wrapper around the internal implementation.

func NewQueryBuilder

func NewQueryBuilder(vendor string) *QueryBuilder

NewQueryBuilder creates a new query builder for the specified database vendor. This function maintains backward compatibility while using the improved internal implementation.

func (*QueryBuilder) Delete

func (qb *QueryBuilder) Delete(table string) types.DeleteQueryBuilder

Delete creates a DELETE query builder that returns the interface type. This method overrides the embedded builder to provide the correct interface.

func (*QueryBuilder) Filter added in v0.13.0

func (qb *QueryBuilder) Filter() types.FilterFactory

Filter returns a FilterFactory for creating composable WHERE clause filters. This method overrides the embedded builder to provide the correct interface.

func (*QueryBuilder) Select

func (qb *QueryBuilder) Select(columns ...any) types.SelectQueryBuilder

Select creates a SELECT query builder that returns the interface type. This method overrides the embedded builder to provide the correct interface.

func (*QueryBuilder) Update

func (qb *QueryBuilder) Update(table string) types.UpdateQueryBuilder

Update creates an UPDATE query builder that returns the interface type. This method overrides the embedded builder to provide the correct interface.

type QueryBuilderInterface added in v0.8.1

type QueryBuilderInterface = types.QueryBuilderInterface

QueryBuilderInterface defines the interface for vendor-specific SQL query building. This type alias maintains backward compatibility while enabling dependency injection.

type ReleaseFunc added in v0.43.0

type ReleaseFunc func()

ReleaseFunc releases a lease obtained from Get. Callers must invoke it (typically deferred) when they are finished with the connection for the current unit of work. It is idempotent: calling it more than once is a safe no-op. The connection itself is a long-lived, shared pool — Release does NOT close it; it only signals that this borrower is done, so a connection evicted while leased can be closed once its last lease is released. See ADR-032.

type SQLProvider added in v0.55.0

type SQLProvider interface {
	ToSQL() (string, []any, error)
}

SQLProvider is the contract of a complete, executable statement: anything that can render itself to SQL + args. Every query builder returned by QueryBuilder (Select/Insert/Update/Delete) already satisfies it; Raw adapts hand-written SQL. A types.Filter/types.JoinFilter WHERE fragment also satisfies SQLProvider structurally (it embeds squirrel.Sqlizer, which declares the same ToSQL()-shaped ToSql()) but is NOT a complete statement and must never be passed to the Execute* helpers directly — buildQuery detects and rejects it at StageBuild instead of letting the driver fail on a bare WHERE fragment.

func Raw added in v0.55.0

func Raw(query string, args ...any) SQLProvider

Raw adapts hand-written SQL (UNION, FOR UPDATE, vendor tricks) to SQLProvider so it reuses the same Execute* helpers as builder output.

SECURITY: Raw is an escape hatch on par with Filter.Raw, and broader — the SQL string replaces the whole statement, bypassing the builder's identifier validation entirely. Never concatenate user input into the SQL; carry values in args. Every call site requires an adjacent "// SECURITY: Manual SQL review completed - <what was verified>" comment, exactly as f.Raw()/jf.Raw() do.

type Statement added in v0.8.0

type Statement = types.Statement

Statement defines the interface for prepared statements. This type alias maintains backward compatibility.

type TrackedConnection added in v0.2.0

type TrackedConnection = tracking.Connection

Re-export the internal tracking implementation as the public API

type TrackedDB

type TrackedDB = tracking.DB

Re-export the internal tracking implementation as the public API

type TrackedStatement added in v0.2.0

type TrackedStatement = tracking.Statement

Re-export the internal tracking implementation as the public API

type TrackedStmt

type TrackedStmt = tracking.Statement

Re-export the internal tracking implementation as the public API

type TrackedTransaction added in v0.2.0

type TrackedTransaction = tracking.Transaction

Re-export the internal tracking implementation as the public API

type TrackedTx

type TrackedTx = tracking.Transaction

Re-export the internal tracking implementation as the public API

type TrackingContext added in v0.5.0

type TrackingContext = tracking.Context

Re-export the internal tracking implementation as the public API

type Tx added in v0.8.0

type Tx = types.Tx

Tx defines the interface for database transactions. This type alias maintains backward compatibility.

Directories

Path Synopsis
internal
builder
Package builder provides cross-database query building utilities.
Package builder provides cross-database query building utilities.
dbtestlog
Package dbtestlog provides shared test-logger helpers for the database vendor packages.
Package dbtestlog provides shared test-logger helpers for the database vendor packages.
tracking
Package tracking provides performance tracking for database operations.
Package tracking provides performance tracking for database operations.
wrapper
Package wrapper provides driver-agnostic wrappers around database/sql.Stmt and database/sql.Tx that implement the types.Statement and types.Tx interfaces.
Package wrapper provides driver-agnostic wrappers around database/sql.Stmt and database/sql.Tx that implement the types.Statement and types.Tx interfaces.
Package testing provides utilities for testing database logic in go-bricks applications.
Package testing provides utilities for testing database logic in go-bricks applications.
Package types contains the core database interface definitions for go-bricks.
Package types contains the core database interface definitions for go-bricks.

Jump to

Keyboard shortcuts

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