Documentation
¶
Overview ¶
Package types contains the core database interface definitions for go-bricks. These interfaces are separate from the main database package to avoid import cycles and to make them easily accessible for mocking and testing.
Package types contains the core database interface definitions for go-bricks.
Package types contains the core database interface definitions for go-bricks.
Package types contains the core database interface definitions for go-bricks.
Index ¶
- Variables
- func MustValidateSubquery(subquery SelectQueryBuilder)
- func ValidateSubquery(subquery SelectQueryBuilder) error
- type Columns
- type DeleteQueryBuilder
- type Filter
- type FilterFactory
- type InsertQueryBuilder
- type Interface
- type InvalidAliasError
- type JoinFilter
- type JoinFilterFactory
- type Querier
- type QueryBuilderInterface
- type RawExpression
- type Row
- type SelectQueryBuilder
- type Session
- type Statement
- type TableRef
- type Transactor
- type Tx
- type UpdateQueryBuilder
- type Vendor
Constants ¶
This section is empty.
Variables ¶
var ( // ErrEmptyTableName is returned when Table() is called with an empty name. ErrEmptyTableName = errors.New("table name cannot be empty") // ErrEmptyTableAlias is returned when TableRef.As() is called with an empty alias. ErrEmptyTableAlias = errors.New("table alias cannot be empty") ErrNilTableRef = errors.New("table reference cannot be nil") // ErrOrderingOperandNotComparable is returned when an ordering comparison // (<, <=, >, >=) is given an operand that is not a scalar. There is no // rendering for one — `col < NULL` is never true whatever the data, and a set // has no ordinal meaning — so the door refuses instead of emitting SQL that // silently matches nothing. // // The refused forms are judged AFTER the operand is resolved, so they are // wider than their surface spelling: nil; a slice; an array (`[3]int{…}`, // which the builder classifies as a list exactly as it does a slice); a typed // nil pointer (`(*int)(nil)`, the ordinary spelling of an optional field), // which is dereferenced to nil; and a `driver.Valuer` whose Value() resolves // to NULL (`sql.NullString{}`). A `[]byte` and a Valuer HOLDING a value stay // scalars and are compared normally. A Valuer whose Value() FAILS is a // different error: that cause is reported wrapped, not as this sentinel. // // It lives here rather than in the builder because the builder is an internal // package: a sentinel a consumer cannot import is a sentinel a consumer // cannot match. ErrOrderingOperandNotComparable = errors.New("ordering comparison requires a scalar operand") // ErrEmptyExpressionSQL is returned when Expr() is called with empty SQL. ErrEmptyExpressionSQL = errors.New("expression SQL cannot be empty") // ErrTooManyAliases is returned when Expr() is called with more than 1 alias. ErrTooManyAliases = errors.New("expression accepts maximum 1 alias") // ErrInvalidAlias is returned when a RawExpression alias is not a bare // unquoted identifier. It replaces the former ErrDangerousAlias substring // denylist, which accepted everything it did not list. ErrInvalidAlias = errors.New("alias must be an unquoted identifier: a letter or underscore followed by letters, digits, underscore, $ or #") // ErrAliasInHaving is returned when a RawExpression carrying an alias is passed // to Having. HAVING takes a predicate, not a projected column, so an alias has // nowhere to render; accepting one silently would drop it. ErrAliasInHaving = errors.New("alias is not allowed in a HAVING predicate") // ErrAliasInValue is returned when a RawExpression carrying an alias is passed // to Set, SetMap or Values. A SET or VALUES cell projects nothing, so an alias // has nowhere to render; accepting one silently would drop it. ErrAliasInValue = errors.New("alias is not allowed in a SET or VALUES value") // ErrRowLockWithPagination is returned by ToSQL on Oracle when a SELECT // carries both a row lock (ForUpdate/ForUpdateNoWait) and a Limit/Offset: // the Oracle SQL Language Reference, "Restrictions on the row_limiting_clause", // states "You cannot specify this clause with the for_update_clause". The // statement is refused here rather than rejected by the database at run time. ErrRowLockWithPagination = errors.New("a row lock cannot be combined with Limit/Offset on Oracle (row_limiting_clause restriction)") // ErrUpsertConflictColumnsRequired is returned when BuildUpsert gets no conflict columns. ErrUpsertConflictColumnsRequired = errors.New("conflict columns required for upsert") // ErrUpsertConflictColumnNotInserted is returned when a BuildUpsert conflict column is not an insert column. ErrUpsertConflictColumnNotInserted = errors.New("conflict column must be present in insert columns for upsert") // ErrUpsertConflictColumnInUpdateSet is returned when a BuildUpsert conflict column is also an update column. ErrUpsertConflictColumnInUpdateSet = errors.New("update column collides with conflict column (Oracle MERGE forbids updating ON-clause columns, ORA-38104; rejected on all vendors for parity)") // ErrNilSubquery is returned when ValidateSubquery() is called with nil subquery. ErrNilSubquery = errors.New("subquery cannot be nil") // ErrInvalidSubquery is returned when subquery validation fails. ErrInvalidSubquery = errors.New("invalid subquery") // ErrEmptySubquerySQL is returned when subquery produces empty SQL. ErrEmptySubquerySQL = errors.New("subquery produced empty SQL") )
Sentinel errors for validation failures in type constructors. These can be used with errors.Is() for programmatic error checking.
Functions ¶
func MustValidateSubquery ¶ added in v0.19.0
func MustValidateSubquery(subquery SelectQueryBuilder)
MustValidateSubquery is like ValidateSubquery but panics on error. Use this only when errors indicate programming bugs that should crash immediately.
func ValidateSubquery ¶ added in v0.14.2
func ValidateSubquery(subquery SelectQueryBuilder) error
ValidateSubquery checks if a subquery is valid for use in filter expressions. Returns an error if subquery is nil or produces invalid SQL.
Returns:
- ErrNilSubquery if subquery is nil
- ErrInvalidSubquery if subquery validation fails
- ErrEmptySubquerySQL if subquery produces empty SQL
This function is called internally by filter implementations to ensure subqueries are constructed correctly before query execution.
Types ¶
type Columns ¶ added in v0.15.0
type Columns interface {
// Col retrieves the vendor-quoted column name for the given struct field name.
// If aliased via As(), returns qualified column (e.g., "u.id").
// Panics if the field name is not found (fail-fast for development-time typos).
//
// Example (unaliased):
// cols.Col("ID") // Returns: "id" (PostgreSQL) or "id" (Oracle, not a reserved word)
// cols.Col("Level") // Returns: "level" (PostgreSQL) or "\"level\"" (Oracle, reserved word — double-quoted lowercase)
//
// Example (aliased):
// u := cols.As("u")
// u.Col("ID") // Returns: "u.id" (PostgreSQL) or "u.id" (Oracle, not a reserved word)
Col(fieldName string) string
// As returns a new Columns instance bound to the specified table alias.
// The returned instance shares the underlying column metadata (zero-copy).
// This method is immutable - the original Columns instance remains unchanged.
//
// Example:
// cols := qb.Columns(&User{})
// u := cols.As("u")
// p := cols.As("p")
// u.Col("ID") // "u.id"
// p.Col("ID") // "p.id"
// cols.Col("ID") // "id" (original unaffected)
//
// The alias becomes SQL syntax, so it is validated against the same
// bare-identifier grammar the table argument applies to the alias half of
// "users u" (ADR-082). Anything else, an empty alias included, panics with a
// *InvalidAliasError: As has no error channel, and an alias is a developer
// constant, so a violation is a programming error surfaced at construction
// rather than a deferred ToSQL() error.
As(alias string) Columns
// Alias returns the current table alias, or empty string if unaliased.
//
// Example:
// cols.Alias() // ""
// cols.As("u").Alias() // "u"
Alias() string
// Cols retrieves vendor-quoted column names for multiple struct field names.
// If aliased, returns qualified columns.
// Panics if any field name is not found.
//
// Example:
// cols.Cols("ID", "Name", "Email") // ["id", "name", "email"]
// cols.As("u").Cols("ID", "Name") // ["u.id", "u.name"]
Cols(fieldNames ...string) []string
// All returns vendor-quoted column names for all columns in the struct,
// in the order they were declared in the struct definition.
//
// Example:
// cols.All() // ["id", "name", "email"] (unaliased)
// cols.As("u").All() // ["u.id", "u.name", "u.email"] (aliased)
All() []string
// FieldMap extracts field values from a struct instance into a map.
// The map keys are vendor-quoted column names (respecting alias if set).
// Only fields with `db` tags are included. Zero values are included.
//
// Map iteration order is UNSPECIFIED, so building DML straight from this map
// emits a different column order per process. Sort the keys, or use AllFields,
// which returns columns and values in declaration order (#1157).
//
// Example:
// user := User{ID: 123, Name: "Alice", Email: "alice@example.com"}
// cols.FieldMap(&user)
// // Returns: {"id": 123, "name": "Alice", "email": "alice@example.com"}
//
// cols.As("u").FieldMap(&user)
// // Returns: {"u.id": 123, "u.name": "Alice", "u.email": "alice@example.com"}
//
// Panics if instance is not a struct or pointer to struct.
FieldMap(instance any) map[string]any
// AllFields extracts all field values from a struct instance as separate slices.
// Returns (columns, values) suitable for bulk INSERT/UPDATE operations.
// Only fields with `db` tags are included. Zero values are included.
//
// Example:
// user := User{ID: 123, Name: "Alice", Email: "alice@example.com"}
// cols, vals := cols.AllFields(&user)
// // cols: ["id", "name", "email"]
// // vals: [123, "Alice", "alice@example.com"]
//
// Panics if instance is not a struct or pointer to struct.
AllFields(instance any) ([]string, []any)
}
Columns represents cached metadata for a struct type with `db:"column_name"` tags. It provides methods to retrieve vendor-quoted column names with optional table aliasing.
This interface is implemented by the internal columns package and returned by QueryBuilder.Columns(). The metadata is lazily parsed on first use and cached forever.
v2.4 Breaking Changes:
- Renamed Get() → Col() for consistency with database terminology
- Renamed Fields() → Cols() with []string return type (was []any)
- All() now returns []string instead of []any
- Added As() for immutable aliasing: cols.As("u").Col("ID") → "u.id"
- Added FieldMap() and AllFields() for struct-to-query conversion
type DeleteQueryBuilder ¶ added in v0.13.0
type DeleteQueryBuilder interface {
// Filtering
Where(filter Filter) DeleteQueryBuilder
// Batch operations
Limit(limit uint64) DeleteQueryBuilder
OrderBy(orderBys ...string) DeleteQueryBuilder
// SQL generation
ToSQL() (sql string, args []any, err error)
}
DeleteQueryBuilder defines the interface for DELETE query building with type-safe filtering. This interface wraps squirrel.DeleteBuilder with Filter API support. SECURITY: The string-identifier arguments of OrderBy must be developer-controlled, not user input. They are validated against a safe identifier grammar on ALL vendors BEFORE interpolation; values outside that grammar surface as a ToSQL() error. Route dynamic/computed orderings through qb.Expr()/Raw() (which require an explicit security annotation) and pass user values through the Filter API. See ADR-031.
type Filter ¶ added in v0.13.0
type Filter interface {
squirrel.Sqlizer
// ToSQL is a convenience method with idiomatic Go naming.
// It should delegate to ToSql() in implementations.
ToSQL() (sql string, args []any, err error)
}
Filter represents a composable WHERE clause filter that can be combined with AND/OR/NOT operators. Filters are created through FilterFactory methods obtained from QueryBuilder.Filter().
Filter embeds squirrel.Sqlizer for compatibility with Squirrel's query builder, and adds ToSQL() as a convenience method with idiomatic Go naming (uppercase SQL).
This is an interface to support mocking and testing. The concrete implementation is in database/internal/builder package.
type FilterFactory ¶ added in v0.13.0
type FilterFactory interface {
// Comparison operators
Eq(column string, value any) Filter
NotEq(column string, value any) Filter
Lt(column string, value any) Filter
Lte(column string, value any) Filter
Gt(column string, value any) Filter
Gte(column string, value any) Filter
In(column string, values any) Filter
NotIn(column string, values any) Filter
Like(column, pattern string) Filter
Null(column string) Filter
NotNull(column string) Filter
Between(column string, lowerBound, upperBound any) Filter
// Regex matching (vendor-specific):
// PostgreSQL: ~ (CS), ~* (CI), !~ (NOT CS), !~* (NOT CI)
// Oracle: REGEXP_LIKE(col, pat[, 'i']), optionally negated with NOT
Regex(column, pattern string) Filter
RegexI(column, pattern string) Filter
NotRegex(column, pattern string) Filter
NotRegexI(column, pattern string) Filter
// JSONContains tests JSON containment (PostgreSQL @>). Currently
// PostgreSQL-only; calling on Oracle yields an error filter.
JSONContains(column string, value any) Filter
// Logical operators
And(filters ...Filter) Filter
Or(filters ...Filter) Filter
Not(filter Filter) Filter
// Raw escape hatch
Raw(condition string, args ...any) Filter
// Subquery support (v2.1+)
Exists(subquery SelectQueryBuilder) Filter
NotExists(subquery SelectQueryBuilder) Filter
InSubquery(column string, subquery SelectQueryBuilder) Filter
}
FilterFactory provides methods for creating composable, type-safe query filters. Filters maintain vendor-specific quoting rules and can be combined with AND/OR/NOT logic.
type InsertQueryBuilder ¶ added in v0.30.0
type InsertQueryBuilder interface {
// Columns passes names through unchanged. For Oracle reserved-word quoting,
// use qb.InsertWithColumns or pre-quote via a Columns helper.
Columns(columns ...string) InsertQueryBuilder
// Values and SetMap parameterize every value except a RawExpression, which
// is validated and spliced inline; an alias on it is a ToSQL() error naming
// the column or one-based position (#1318). See RawExpression.
Values(values ...any) InsertQueryBuilder
SetMap(clauses map[string]any) InsertQueryBuilder
Options(options ...string) InsertQueryBuilder
Prefix(sql string, args ...any) InsertQueryBuilder
Suffix(sql string, args ...any) InsertQueryBuilder
// Select returns an error (deferred to ToSQL()) if sb is not the concrete *SelectQueryBuilder from this package.
Select(sb SelectQueryBuilder) InsertQueryBuilder
ToSQL() (sql string, args []any, err error)
}
InsertQueryBuilder defines the interface for INSERT query building. Wraps squirrel.InsertBuilder so the public API exposes ToSQL() (S8179) consistently with SelectQueryBuilder/UpdateQueryBuilder/DeleteQueryBuilder. See ADR-017 for the rationale and migration path.
type Interface ¶
type Interface interface {
// Querier provides core query execution operations
Querier
// Transactor provides transaction management operations
Transactor
// Prepared statements
Prepare(ctx context.Context, query string) (Statement, error)
// Health and diagnostics
Health(ctx context.Context) error
Stats() (map[string]any, error)
// Session acquires a handle pinned to a single physical connection, for
// session-scoped state a pooled statement can silently lose. See Session
// for the error, concurrency and lifetime contract.
Session(ctx context.Context) (Session, error)
// Connection management
Close() error
// Migration support
MigrationTable() string
CreateMigrationTable(ctx context.Context) error
}
Interface defines the common database operations supported by the framework. This is the main interface that applications and modules should depend on for database operations, allowing for easy mocking and testing.
Interface is composed of focused sub-interfaces following the Single Responsibility Principle:
- Querier: Core query execution (Query, QueryRow, Exec, DatabaseType)
- Transactor: Transaction management (Begin, BeginTx)
For easier testing, consider using the smaller interfaces (Querier or Transactor) when your code doesn't need all Interface capabilities. The database/testing package provides utilities for mocking these interfaces with minimal boilerplate.
Backward compatibility: All existing code using Interface continues to work unchanged.
type InvalidAliasError ¶ added in v0.60.0
type InvalidAliasError struct {
// Alias is the refused alias, exactly as it was passed.
Alias string
}
InvalidAliasError reports an alias argument that the identifier grammar refuses. Columns.As panics with this value rather than returning an error: its signature has no error channel, and an alias is a developer constant, so a violation is a programming error surfaced at construction rather than a deferred query error.
It is a distinct type so a recovery site can report the panic by TYPE without rendering the refused alias (ADR-081), and so a caller can match it:
var invalid *dbtypes.InvalidAliasError
if errors.As(recovered.(error), &invalid) { ... }
func (*InvalidAliasError) Error ¶ added in v0.60.0
func (e *InvalidAliasError) Error() string
type JoinFilter ¶ added in v0.13.0
type JoinFilter interface {
squirrel.Sqlizer
// ToSQL is a convenience method with idiomatic Go naming.
// It should delegate to ToSql() in implementations.
ToSQL() (sql string, args []any, err error)
}
JoinFilter represents a filter specifically for JOIN ON conditions where columns are compared to other columns (not to values). This follows Single Responsibility Principle by separating column-to-column comparisons (JOIN) from column-to-value comparisons (WHERE).
JoinFilters are created through JoinFilterFactory methods obtained from QueryBuilder.JoinFilter().
Example:
jf := qb.JoinFilter()
query := qb.Select("*").From("users").JoinOn("profiles", jf.EqColumn("users.id", "profiles.user_id"))
This is an interface to support mocking and testing. The concrete implementation is in database/internal/builder package.
type JoinFilterFactory ¶ added in v0.13.0
type JoinFilterFactory interface {
// Column-to-column comparison operators
EqColumn(leftColumn, rightColumn string) JoinFilter
NotEqColumn(leftColumn, rightColumn string) JoinFilter
LtColumn(leftColumn, rightColumn string) JoinFilter
LteColumn(leftColumn, rightColumn string) JoinFilter
GtColumn(leftColumn, rightColumn string) JoinFilter
GteColumn(leftColumn, rightColumn string) JoinFilter
// Column-to-value comparison operators (v2.2+)
// These methods accept any value type, including RawExpression for complex SQL.
// Regular values generate placeholders; RawExpression values are inserted verbatim.
Eq(column string, value any) JoinFilter
NotEq(column string, value any) JoinFilter
Lt(column string, value any) JoinFilter
Lte(column string, value any) JoinFilter
Gt(column string, value any) JoinFilter
Gte(column string, value any) JoinFilter
In(column string, values any) JoinFilter
NotIn(column string, values any) JoinFilter
Like(column, pattern string) JoinFilter
Null(column string) JoinFilter
NotNull(column string) JoinFilter
Between(column string, lowerBound, upperBound any) JoinFilter
// Logical operators for complex JOIN conditions
And(filters ...JoinFilter) JoinFilter
Or(filters ...JoinFilter) JoinFilter
// Raw escape hatch for complex JOIN conditions
Raw(condition string, args ...any) JoinFilter
}
JoinFilterFactory provides methods for creating type-safe JOIN ON filters. JoinFilters support both column-to-column comparisons and column-to-value comparisons, enabling mixed JOIN conditions without requiring Raw() for common cases.
Column-to-value methods accept RawExpression for complex SQL expressions:
jf.Eq("amount", qb.MustExpr("TO_NUMBER('100')")) // Expression support (no placeholders inside expressions)
jf.Eq("status", "active") // Simple value with placeholder
type Querier ¶ added in v0.16.0
type Querier interface {
// Query executes a SQL query that returns rows, typically a SELECT statement.
// The caller is responsible for closing the returned rows.
//
// The query should use vendor-specific placeholders:
// - PostgreSQL: $1, $2, $3
// - Oracle: :1, :2, :3
//
// For vendor-agnostic query construction, use the QueryBuilder.
Query(ctx context.Context, query string, args ...any) (*sql.Rows, error)
// QueryRow executes a SQL query that is expected to return at most one row.
// QueryRow always returns a non-nil value. Errors are deferred until Row's Scan method is called.
//
// For vendor-agnostic query construction, use the QueryBuilder.
QueryRow(ctx context.Context, query string, args ...any) Row
// Exec executes a SQL statement that doesn't return rows, typically INSERT, UPDATE, or DELETE.
// The returned sql.Result provides RowsAffected and LastInsertId (if supported by the vendor).
//
// For vendor-agnostic query construction, use the QueryBuilder.
Exec(ctx context.Context, query string, args ...any) (sql.Result, error)
// DatabaseType returns the vendor identifier for this database connection.
// Valid values are defined as constants: PostgreSQL, Oracle.
//
// This is included in Querier (despite being metadata) because the query builder
// requires vendor information for placeholder generation and identifier quoting.
// Including it here prevents forcing all test mocks to implement additional interfaces.
DatabaseType() string
}
Querier defines the core query execution operations that represent 80% of typical database usage. This interface follows the Single Responsibility Principle by focusing solely on query execution, separate from transaction management, health checks, and migration support.
Querier is designed for easy mocking in unit tests, requiring only 4 methods instead of the full 12 methods in the Interface type. Most business logic only needs query execution capabilities.
The database.Interface type embeds Querier, so all existing code continues to work unchanged.
Usage in tests:
// Simple mock implementation
type mockQuerier struct {
queryFunc func(ctx context.Context, query string, args ...any) (*sql.Rows, error)
queryRowFunc func(ctx context.Context, query string, args ...any) Row
execFunc func(ctx context.Context, query string, args ...any) (sql.Result, error)
}
// Inject via ModuleDeps.DB
deps := &app.ModuleDeps{
DB: func(ctx context.Context) (database.Interface, error) {
return mockQuerier, nil
},
}
For comprehensive testing utilities, see the database/testing package which provides TestDB with fluent API for setting up query expectations and assertions.
type QueryBuilderInterface ¶ added in v0.8.1
type QueryBuilderInterface interface {
// Vendor information
Vendor() string
// Filter factories
Filter() FilterFactory
JoinFilter() JoinFilterFactory
// Expression builder (v2.1+)
// Returns error if SQL is empty, too many aliases provided, or alias contains dangerous characters.
// Call sites carry the SECURITY annotation; see RawExpression.
Expr(sql string, alias ...string) (RawExpression, error)
// MustExpr is like Expr but panics on error.
// Use this only in static initialization or tests where errors indicate programming bugs.
// Call sites carry the SECURITY annotation; see RawExpression.
MustExpr(sql string, alias ...string) RawExpression
// Column metadata extraction (v2.4+)
// Extracts column metadata from structs with `db:"column_name"` tags.
// Lazily parses struct on first use, caches forever.
// Returns vendor-specific quoted column names (e.g., Oracle reserved words).
//
// Example:
// type User struct {
// ID int64 `db:"id"`
// Level string `db:"level"` // Oracle reserved word
// }
// cols := qb.Columns(&User{})
// qb.Select(cols.Col("ID"), cols.Col("Level")).From("users")
//
// // With aliasing:
// u := cols.As("u")
// qb.Select(u.Col("ID"), u.Col("Level")).From(Table("users").As("u"))
//
// Panics if structPtr is not a pointer to a struct with db tags.
Columns(structPtr any) Columns
// Query builders
Select(columns ...any) SelectQueryBuilder
Insert(table string) InsertQueryBuilder
InsertWithColumns(table string, columns ...string) InsertQueryBuilder
// Struct-based INSERT (v2.4+)
// InsertStruct extracts all fields from a struct instance and creates an INSERT query.
// Zero-value ID fields (int64/string) are automatically excluded to support auto-increment.
InsertStruct(table string, instance any) InsertQueryBuilder
// InsertFields extracts only specified fields from a struct instance for INSERT.
// Useful for partial inserts or when you need explicit control over included fields.
InsertFields(table string, instance any, fields ...string) InsertQueryBuilder
Update(table string) UpdateQueryBuilder
Delete(table string) DeleteQueryBuilder
// Vendor-specific helpers
BuildCaseInsensitiveLike(column, value string) squirrel.Sqlizer
// BuildUpsert requires a non-empty conflictColumns that names no column twice
// and whose every entry also names a column of insertColumns, on every vendor;
// a call violating any of those returns an error before any SQL is produced.
// Both questions — whether two entries are the same column, and whether a
// conflict column is among the inserted ones — are decided by the column each
// key NAMES, not by how it is spelled. On Oracle that is the name the database
// stores: a key the builder emits unquoted folds to upper case, while one it
// emits quoted (a caller-quoted key, or a reserved word) is taken verbatim. So
// id, ID and "ID" are one column there — a case variant is a duplicate and also
// satisfies membership, and so does a quoted spelling of the same name — while
// "id" is a second column and level and LEVEL stay two, both being quoted.
// PostgreSQL quotes every identifier, so a case variant is never a duplicate
// there: it is a second column, a legitimate composite conflict target, and not
// a match for an insert key spelled differently.
//
// insertColumns and updateColumns must each name every column at most once,
// judged by that same vendor identity rule. A map cannot hold an exact repeat,
// but two keys can still name one column: on Oracle {"id": 1, "ID": 2} is one
// column written twice and is rejected, while on PostgreSQL it is two columns
// and builds. Every key is TRIMMED before it is judged, and the trimmed
// spelling is what the statement renders, so {"id": 1, " id ": 2} names one
// column on both vendors and is rejected on both, naming both spellings.
//
// ONE acceptance rule decides what a key may be, on every vendor: a single
// column name — no qualifier, no function call, no empty name — carrying no
// quote of its own beyond a plain wrapping pair. COUNT(*), t.name, a""b and
// "a""b" are therefore refused on PostgreSQL as well as on Oracle. An
// identifier argument carries no escaping beyond the accepted wrapping quote
// pair; the door quotes. A column whose name
// genuinely holds a quote has no key form here at all — conflictColumns is
// []string and both maps are keyed by string, so there is no expression key to
// pass; such a schema needs a hand-written statement. A column literally named
// count(*) is unaffected: it stays reachable as the quoted key "count(*)" — PostgreSQL used to refuse every quoted key and now takes them
// like Oracle, bounded by that same no-interior-quote rule. The rule is Oracle's grammar
// in origin — its MERGE names conflict and insert keys as column aliases in
// the USING clause and in the INSERT list, neither of which admits anything
// else — and PostgreSQL applies it so that one spelling of a column works
// everywhere the call names it. Oracle update keys become UPDATE SET targets,
// where Oracle itself would also accept an alias-qualified one; refusing those
// is this API's restriction, for the same reason.
//
// BuildUpsert rejects a column present in both conflictColumns and
// updateColumns on every vendor, because Oracle's MERGE cannot update a
// column referenced in its ON clause (ORA-38104). Identity is the same rule the
// checks above use — the column each key names, so on Oracle conflict "ID" and
// update id collide, while PostgreSQL keeps every spelling distinct. When its update value equals
// its insert value, drop it from updateColumns and no column value changes:
// the conflict match pins it on a matched row and the INSERT supplies it on an
// unmatched one. Dropping the only update column empties the set, which builds
// DO NOTHING (and omits Oracle's WHEN MATCHED arm), so a matched row is no
// longer updated at all: its UPDATE triggers stop firing and RETURNING yields
// no row. Where that matters keep a genuine non-conflict column, or issue the
// UPDATE explicitly under the same transaction and locking rule as below.
// When the two values differ, the call was rewriting the
// conflict column itself, which no vendor-portable upsert can express — issue
// a separate UPDATE instead: in the same transaction as the insert, keyed on
// the conflict columns, and holding the row lock the single statement took for
// you (SELECT ... FOR UPDATE or equivalent). Splitting one atomic upsert into
// two statements lets a concurrent writer interleave, and under READ COMMITTED
// a shared transaction alone does not stop it.
//
// Precondition failures match the ErrUpsert* sentinels in this package via errors.Is.
BuildUpsert(table string, conflictColumns []string, insertColumns, updateColumns map[string]any) (query string, args []any, err error)
// Database function builders
BuildCurrentTimestamp() string
BuildUUIDGeneration() string
BuildBooleanValue(value bool) any
// Identifier escaping
EscapeIdentifier(identifier string) string
}
QueryBuilderInterface defines the interface for vendor-specific SQL query building. This interface allows for dependency injection and mocking of query builders, enabling unit testing of business logic that constructs queries without actually generating SQL strings.
type RawExpression ¶ added in v0.14.2
type RawExpression struct {
SQL string // The raw SQL expression
Alias string // Optional alias (AS clause)
}
RawExpression represents a raw SQL expression that can be used in SELECT, GROUP BY, and ORDER BY clauses. It allows using SQL functions, aggregations, calculations, and other expressions that go beyond simple column names.
SECURITY WARNING: Raw SQL expressions are NOT escaped or sanitized by the framework. Never interpolate user input directly into expressions - this creates SQL injection vulnerabilities. Only use static SQL or carefully validated values in expressions.
Every call site that constructs one with a SQL body — through Expr, MustExpr or a struct literal — carries the inline `// SECURITY: Manual SQL review completed - <what was verified>` annotation, the same rule as f.Raw/jf.Raw/database.Raw, at Having and every other door that consumes one. Validate never inspects the body.
Safe usage:
// SECURITY: Manual SQL review completed - constant aggregate, no caller input
expr, err := qb.Expr("COUNT(*)", "total") // Aggregation with alias
if err != nil { return err }
qb.Select(expr)
expr, err = qb.Expr("UPPER(name)") // Function without alias
if err != nil { return err }
qb.Select(expr)
expr, err = qb.Expr("price * quantity", "total") // Calculation with alias
if err != nil { return err }
qb.Select(expr)
Unsafe usage (NEVER do this):
userInput := req.Query("column")
expr, err := qb.Expr(fmt.Sprintf("UPPER(%s)", userInput)) // SQL INJECTION RISK!
if err != nil { return err }
qb.Select(expr)
func Expr ¶ added in v0.14.2
func Expr(sql string, alias ...string) (RawExpression, error)
Expr creates a raw SQL expression with optional alias for use in SELECT, GROUP BY, and ORDER BY clauses.
Parameters:
- sql: The raw SQL expression (e.g., "COUNT(*)", "UPPER(name)", "price * quantity")
- alias: Optional alias for the expression (e.g., "total", "upper_name"). Max 1 alias allowed.
Returns:
- RawExpression: The constructed expression
- error: ErrEmptyExpressionSQL, ErrTooManyAliases, or ErrInvalidAlias on validation failure
Examples:
// Aggregation with alias
expr, err := qb.Expr("COUNT(*)", "total")
if err != nil { return err }
// Function without alias
expr, err := qb.Expr("UPPER(name)")
// Calculation with alias
expr, err := qb.Expr("price * quantity", "line_total")
SECURITY WARNING: Never interpolate user input directly into the sql parameter. This function does NOT sanitize SQL - you are responsible for ensuring safety. Call sites carry the SECURITY annotation; see RawExpression.
func MustExpr ¶ added in v0.19.0
func MustExpr(sql string, alias ...string) RawExpression
MustExpr is like Expr but panics on error. Use this only in static initialization or tests where errors indicate programming bugs. Call sites carry the SECURITY annotation; see RawExpression.
func (RawExpression) Validate ¶ added in v0.60.0
func (e RawExpression) Validate() error
Validate reports why this expression may not be interpolated, or nil.
RawExpression is a plain struct, so a caller can build one directly and never reach Expr(). This is the single funnel both paths share: Expr() calls it at construction, and every builder door that interpolates an expression calls it again at consumption, where a struct literal is indistinguishable from a constructed one (#1153, ADR-082).
The SQL itself is NOT validated — it is the sanctioned raw-SQL escape hatch, and the caller owns its safety. Only its emptiness and the alias are checked. A non-empty alias must be an unquoted identifier under the shared grammar (ADR-031/ADR-082): it is interpolated verbatim after AS, and the substring denylist this replaces accepted everything it did not enumerate — a space, a parenthesis, a newline (#1164).
type Row ¶ added in v0.9.0
Row represents a single result set row with basic scanning behavior.
func NewRowFromSQL ¶ added in v0.9.0
NewRowFromSQL wraps the provided *sql.Row in a Row. If row is nil, NewRowFromSQL returns nil.
type SelectQueryBuilder ¶ added in v0.11.0
type SelectQueryBuilder interface {
// Core SELECT builder methods
// From accepts either string table names or *TableRef instances with optional aliases.
// String table identifiers are validated before interpolation (see ADR-031).
From(from ...any) SelectQueryBuilder
// Type-safe JOIN methods with JoinFilter (v2.0+)
// Each accepts either string table name or *TableRef instance with optional alias
JoinOn(table any, filter JoinFilter) SelectQueryBuilder
LeftJoinOn(table any, filter JoinFilter) SelectQueryBuilder
RightJoinOn(table any, filter JoinFilter) SelectQueryBuilder
InnerJoinOn(table any, filter JoinFilter) SelectQueryBuilder
CrossJoinOn(table any) SelectQueryBuilder
GroupBy(groupBys ...any) SelectQueryBuilder
// Having adds a HAVING predicate. Prefer a RawExpression from qb.Expr() —
// Having(qb.MustExpr("SUM(amount) > ?"), 100), or qb.Expr when you handle its
// (RawExpression, error) return — which is the expression form for
// the aggregate comparisons HAVING exists for; an alias on that expression is
// an error (ErrAliasInHaving), since a predicate projects nothing. Both forms are
// raw SQL needing the SECURITY annotation at every call site; see dbtypes.RawExpression.
Having(pred any, rest ...any) SelectQueryBuilder
OrderBy(orderBys ...any) SelectQueryBuilder
Limit(limit uint64) SelectQueryBuilder
Offset(offset uint64) SelectQueryBuilder
Paginate(limit, offset uint64) SelectQueryBuilder
// ForUpdate appends `FOR UPDATE`, rendered after pagination; ForUpdateNoWait
// appends `FOR UPDATE NOWAIT` instead; the last call wins. On Oracle a lock
// combined with Limit/Offset fails ToSQL with ErrRowLockWithPagination
// (row_limiting_clause restriction). When to use which, the transaction
// requirement and the error to match are in wiki/database.md ("Row locks").
ForUpdate() SelectQueryBuilder
ForUpdateNoWait() SelectQueryBuilder
// SubqueryColumn appends `(sub) AS alias` to the projection — one scalar
// subquery per result column, the shape a stats snapshot takes in one round
// trip. sub is validated like an EXISTS subquery (ValidateSubquery) and its
// placeholders are renumbered with the outer statement's; alias must be an
// unquoted identifier (ErrInvalidAlias). A sub carrying a row lock is
// refused. On Oracle a SELECT with no From renders `FROM dual`.
SubqueryColumn(sub SelectQueryBuilder, alias string) SelectQueryBuilder
// Composable WHERE clause
Where(filter Filter) SelectQueryBuilder
// SQL generation
ToSQL() (sql string, args []any, err error)
}
SelectQueryBuilder defines the interface for enhanced SELECT query building with type safety. This interface extends basic squirrel.SelectBuilder functionality with additional methods for composable filters, JOIN operations, and vendor-specific query features. SECURITY: Every identifier argument on this builder — From and the JOIN table methods (JoinOn/LeftJoinOn/RightJoinOn/InnerJoinOn/CrossJoinOn), OrderBy, GroupBy, the Select column list, and every Filter/JoinFilter column — must be developer-controlled, not user input. All are validated against a safe identifier grammar on ALL vendors BEFORE interpolation; a value outside that grammar surfaces as a ToSQL() error. Route dynamic or computed expressions through qb.Expr()/MustExpr(). Every raw-SQL body the builder never inspects — f.Raw, jf.Raw, database.Raw, a STRING predicate passed to Having, and every RawExpression SQL body (see RawExpression) — requires an inline "// SECURITY: Manual SQL review completed - <rationale>" comment at every call site.
Having takes a predicate rather than an identifier, so no identifier grammar can judge it: a string argument is interpolated as written, and Having(qb.MustExpr(...)) is the preferred form, annotated the same way. Prefix, Suffix and Options on the INSERT builder are the same shape, minus the qb.Expr() alternative.
The Filter API parameterizes its VALUES; that is a separate property from validating its COLUMNS, and reading the first as the second is what left the column doors open between ADR-031 and ADR-082. See ADR-031 and ADR-082.
type Session ¶ added in v0.65.0
type Session interface {
Querier
Transactor
// Close releases the pinned physical connection back to the pool.
Close() error
}
Session represents a handle pinned to a single physical database connection, for session-scoped state — PostgreSQL advisory locks, SET, temp tables — that a shared pool connection can silently lose when the pool hands the next statement to a different physical backend. Session composes Querier and Transactor with Close; unlike Interface it has no Prepare, Health, Stats, or migration-table methods, none of which are meaningful on a single pinned connection.
Interface declares Session(ctx context.Context) (Session, error), so any database.Interface handle reaches the door directly — the vendor connections (postgresql.Connection, oracle.Connection) and the tracking wrapper the framework hands back from database.NewConnection / deps.DB(ctx) alike. Always Close it to return the physical connection to the pool. An open Session holds one of that pool's connections (25 by default) for its whole lifetime, so it is a scarce resource: acquire it late, release it early, and never hold one across a wait that is not itself session-scoped.
A Session holds no tenant lease of its own, so it must not outlive the request or job scope in which it was acquired — the tenant's underlying pool may be closed out from under it once that lease is released (ADR-032).
Error semantics once the physical connection is gone: the call that OBSERVES the death may return the driver's own error rather than a translated one — a PostgreSQL backend killed after the statement went out reports a raw FATAL error (SQLSTATE 57P01) that database/sql does not classify as a dead connection. Every SUBSEQUENT call returns an error satisfying errors.Is(err, sql.ErrConnDone). After Close, every call does so immediately. One exception: a failure that only surfaces while iterating the *sql.Rows returned by Query — including Rows opened through a transaction begun on the Session — reaches the caller RAW through rows.Next and rows.Err, and is never translated. The calls after it still return sql.ErrConnDone.
A Session must not be used concurrently: database/sql does not serialize statements on a single pinned connection, so concurrent calls on one Session race with each other.
Any *sql.Rows obtained from a Session that is still open makes Session.Close block until that Rows is closed, since database/sql holds the pinned connection's closing mutex for as long as the Rows stays open.
Close is not idempotent: a second Close returns sql.ErrConnDone.
type Statement ¶
type Statement interface {
// Query execution
Query(ctx context.Context, args ...any) (*sql.Rows, error)
QueryRow(ctx context.Context, args ...any) Row
Exec(ctx context.Context, args ...any) (sql.Result, error)
// Statement management
Close() error
}
Statement defines the interface for prepared statements
type TableRef ¶ added in v0.14.2
type TableRef struct {
// contains filtered or unexported fields
}
TableRef represents a table reference with optional alias for SQL queries. Use the Table() function to create instances.
Example:
table, _ := Table("customers")
aliased, _ := table.As("c")
func MustTable ¶ added in v0.19.0
MustTable is like Table but panics on error. Use this only in static initialization or tests where errors indicate programming bugs.
func Table ¶ added in v0.14.2
Table creates a new table reference. Returns ErrEmptyTableName if name is empty. Call As() to add an alias, or use directly for tables without aliases.
Example:
table, err := Table("users")
if err != nil { return err }
aliased, err := table.As("u")
func (*TableRef) Alias ¶ added in v0.14.2
Alias returns the table alias, or empty string if no alias.
func (*TableRef) As ¶ added in v0.14.2
As creates a new TableRef with the given alias. Returns ErrEmptyTableAlias if alias is empty. Returns a new TableRef instance (immutable pattern).
Example:
table, _ := Table("customers")
aliased, err := table.As("c")
type Transactor ¶ added in v0.16.0
type Transactor interface {
// Begin starts a new transaction with default isolation level.
// The returned Tx must be committed or rolled back to release resources.
//
// Common usage pattern:
// tx, err := db.Begin(ctx)
// if err != nil { return err }
// defer tx.Rollback(ctx) // No-op if already committed
// // ... execute operations on tx ...
// return tx.Commit(ctx)
Begin(ctx context.Context) (Tx, error)
// BeginTx starts a new transaction with explicit isolation level and read-only settings.
// Use this when you need precise control over transaction behavior.
//
// Common isolation levels (from database/sql):
// - sql.LevelDefault: Use database's default isolation
// - sql.LevelReadUncommitted: Lowest isolation, allows dirty reads
// - sql.LevelReadCommitted: Prevents dirty reads (PostgreSQL default)
// - sql.LevelRepeatableRead: Prevents dirty and non-repeatable reads
// - sql.LevelSerializable: Highest isolation, full transaction isolation
//
// Example (read-only transaction for complex reporting):
// tx, err := db.BeginTx(ctx, &sql.TxOptions{
// Isolation: sql.LevelRepeatableRead,
// ReadOnly: true,
// })
//
// Note: Not all databases support all isolation levels. Consult vendor documentation.
BeginTx(ctx context.Context, opts *sql.TxOptions) (Tx, error)
}
Transactor defines transaction management operations. This interface follows the Single Responsibility Principle by focusing solely on transaction lifecycle, separate from query execution, health checks, and migration support.
Transactor is typically used by business logic that needs to execute multiple operations atomically (all succeed or all fail). For query-only operations, use the Querier interface.
The database.Interface type embeds Transactor, so all existing code continues to work unchanged.
Usage in services:
func (s *OrderService) CreateWithPayment(ctx context.Context, order Order, payment Payment) error {
db, err := s.deps.DB(ctx)
if err != nil {
return err
}
tx, err := db.Begin(ctx)
if err != nil {
return err
}
defer tx.Rollback(ctx) // No-op if already committed
if err := s.insertOrder(ctx, tx, order); err != nil {
return err
}
if err := s.insertPayment(ctx, tx, payment); err != nil {
return err
}
return tx.Commit(ctx)
}
For testing transaction logic, see the database/testing package which provides TestTx for tracking commit/rollback behavior and query execution within transactions.
type Tx ¶
type Tx interface {
// Query execution within transaction
Query(ctx context.Context, query string, args ...any) (*sql.Rows, error)
QueryRow(ctx context.Context, query string, args ...any) Row
Exec(ctx context.Context, query string, args ...any) (sql.Result, error)
// Prepared statements within transaction
Prepare(ctx context.Context, query string) (Statement, error)
// Transaction control
// Note: Context is passed to support proper cancellation and tracing.
// The context should be the same one used to begin the transaction.
Commit(ctx context.Context) error
Rollback(ctx context.Context) error
}
Tx defines the interface for database transactions
type UpdateQueryBuilder ¶ added in v0.13.0
type UpdateQueryBuilder interface {
// Set and SetMap parameterize every value except a RawExpression, which is
// validated and spliced inline; an alias on it is a ToSQL() error naming the
// column (#1318). See RawExpression.
Set(column string, value any) UpdateQueryBuilder
SetMap(clauses map[string]any) UpdateQueryBuilder
// SetExpr assigns a RawExpression that carries bound arguments —
// `SetExpr("lease_until", qb.MustExpr("NOW() + (? * INTERVAL '1 second')"), secs)` —
// which Set cannot: Set splices a RawExpression without arguments. The
// expression's `?` placeholders are renumbered with the statement's. This is
// a raw-SQL door on par with f.Raw: the SQL body is never validated, so every
// call site carries the inline `// SECURITY: Manual SQL review completed -
// <what was verified>` annotation. An alias on expr is a ToSQL() error.
SetExpr(column string, expr RawExpression, args ...any) UpdateQueryBuilder
// Struct-based UPDATE (v2.4+)
// SetStruct extracts field values from a struct instance for UPDATE.
// If fields are specified, only those fields are updated.
// If no fields specified, all struct fields are updated.
SetStruct(instance any, fields ...string) UpdateQueryBuilder
// Filtering
Where(filter Filter) UpdateQueryBuilder
// SQL generation
ToSQL() (sql string, args []any, err error)
}
UpdateQueryBuilder defines the interface for UPDATE query building with type-safe filtering. This interface wraps squirrel.UpdateBuilder with Filter API support and vendor-specific quoting. SECURITY: The string-identifier arguments of Set and SetMap (the SET targets) must be developer-controlled, not user input. They are validated against a safe identifier grammar on ALL vendors BEFORE interpolation; values outside that grammar surface as a ToSQL() error. Route dynamic/computed expressions through qb.Expr()/Raw() (which require an explicit security annotation) and pass user values through the value side of Set/SetMap. See ADR-031.