Documentation
¶
Overview ¶
Package stmt assembles SQL statements: SELECT, INSERT, UPDATE and DELETE, the clause positions they can carry, and the Query a build produces.
It knows the grammar and nothing about meaning. A Predicate is something that can render itself, and this package writes it after WHERE without knowing what it compares; a Clause is something that names a Slot, and this package writes it at that position without knowing what it says. That is what lets an engine package add a construct, and an outside package add a whole statement kind, with no change here.
This is also the highest layer that is still purely about text. A Query is a string and a slice of values; how those values come back into Go is the concern of the layers above.
Index ¶
- Constants
- Variables
- func DialectName(d Dialect) string
- func EngineGuard(a *Args, engine, construct string) bool
- func EngineList(a *Args, items []SQLer) string
- func QuoteList(d Dialect, names []string) string
- func Targets(bs []Binding) []any
- type AnyClause
- type AnySelect
- type Args
- type Assignment
- type Binding
- type CTE
- type Clause
- type Delete
- func (x *Delete[D]) All() *FilteredDelete[D]
- func (x *Delete[D]) Clause(cs ...Clause[D, DeleteKind]) *Delete[D]
- func (x *Delete[D]) Clone() *Delete[D]
- func (x *Delete[D]) Dialect() Dialect
- func (x *Delete[D]) From(t Table) *Delete[D]
- func (x *Delete[D]) Where(ps ...Predicate) *FilteredDelete[D]
- func (x *Delete[D]) With(name string, body Statement) *Delete[D]
- type DeleteKind
- type Dialect
- type Expr
- func EngineBinOp[R any](engine, construct, op string, l, r SQLer) Expr[R]
- func EngineExpr[T any](engine, construct string, render func(a *Args) string) Expr[T]
- func EngineFn[R any](engine, construct, name string, args ...SQLer) Expr[R]
- func EngineLit[T any](engine, construct, sql string) Expr[T]
- func New[T any](f func(a *Args) string) Expr[T]
- func Param[T any](v T) Expr[T]
- func Subquery[T any, D render.Dialect](s *Select[D]) Expr[*T]
- type FilteredDelete
- func (x *FilteredDelete[D]) Build() (Query, error)
- func (x *FilteredDelete[D]) Clause(cs ...Clause[D, DeleteKind]) *FilteredDelete[D]
- func (x *FilteredDelete[D]) Clone() *FilteredDelete[D]
- func (x *FilteredDelete[D]) Dialect() Dialect
- func (x *FilteredDelete[D]) Where(ps ...Predicate) *FilteredDelete[D]
- func (x *FilteredDelete[D]) With(name string, body Statement) *FilteredDelete[D]
- func (x *FilteredDelete[D]) WriteSQL(b *strings.Builder, args *Args)
- type FilteredUpdate
- func (u *FilteredUpdate[D]) Build() (Query, error)
- func (u *FilteredUpdate[D]) Clause(cs ...Clause[D, UpdateKind]) *FilteredUpdate[D]
- func (u *FilteredUpdate[D]) Clone() *FilteredUpdate[D]
- func (u *FilteredUpdate[D]) Dialect() Dialect
- func (u *FilteredUpdate[D]) Set(as ...Assignment) *FilteredUpdate[D]
- func (u *FilteredUpdate[D]) Where(ps ...Predicate) *FilteredUpdate[D]
- func (u *FilteredUpdate[D]) With(name string, body Statement) *FilteredUpdate[D]
- func (u *FilteredUpdate[D]) WriteSQL(b *strings.Builder, args *Args)
- type Guard
- type Insert
- func (i *Insert[D]) Body(c Clause[D, InsertKind]) *InsertReady[D]
- func (i *Insert[D]) Clause(cs ...Clause[D, InsertKind]) *Insert[D]
- func (i *Insert[D]) Clone() *Insert[D]
- func (i *Insert[D]) Columns(cols ...SQLer) *Insert[D]
- func (i *Insert[D]) Dialect() Dialect
- func (i *Insert[D]) Into(t Table) *Insert[D]
- func (i *Insert[D]) Row(as ...Assignment) *InsertReady[D]
- func (i *Insert[D]) Select(s *Select[D]) *InsertReady[D]
- func (i *Insert[D]) With(name string, body Statement) *Insert[D]
- type InsertAliasing
- type InsertKind
- type InsertReady
- func (i *InsertReady[D]) Build() (Query, error)
- func (i *InsertReady[D]) Clause(cs ...Clause[D, InsertKind]) *InsertReady[D]
- func (i *InsertReady[D]) Clone() *InsertReady[D]
- func (i *InsertReady[D]) Dialect() Dialect
- func (i *InsertReady[D]) Row(as ...Assignment) *InsertReady[D]
- func (i *InsertReady[D]) With(name string, body Statement) *InsertReady[D]
- func (i *InsertReady[D]) WriteSQL(b *strings.Builder, args *Args)
- type Join
- type JoinType
- type Named
- type Option
- type OrderTerm
- type Predicate
- func And(ps ...Predicate) Predicate
- func EngineBinPred(engine, construct, op string, l, r SQLer) Predicate
- func EnginePred(engine, construct string, render func(a *Args) string) Predicate
- func Exists[D render.Dialect](s *Select[D]) Predicate
- func InSubquery[T any, D render.Dialect](e Expr[T], s *Select[D]) Predicate
- func NotExists[D render.Dialect](s *Select[D]) Predicate
- func NotInSubquery[T any, D render.Dialect](e Expr[T], s *Select[D]) Predicate
- func PredicateOf(f func(a *Args) string) Predicate
- func SubqueryCompare[D render.Dialect](e SQLer, op, quantifier string, s *Select[D]) Predicate
- type Query
- type Returning
- type ReturningDialect
- type SQLer
- type Select
- func (s *Select[D]) Apply(opts ...Option) *Select[D]
- func (s *Select[D]) Build() (Query, error)
- func (s *Select[D]) Clause(cs ...Clause[D, SelectKind]) *Select[D]
- func (s *Select[D]) Clone() *Select[D]
- func (s *Select[D]) Columns(bs ...Binding) *Select[D]
- func (s *Select[D]) Dialect() Dialect
- func (s *Select[D]) Distinct() *Select[D]
- func (s *Select[D]) Except(o *Select[D]) *Select[D]
- func (s *Select[D]) Exprs(es ...SQLer) *Select[D]
- func (s *Select[D]) From(src Source) *Select[D]
- func (s *Select[D]) GroupBy(es ...SQLer) *Select[D]
- func (s *Select[D]) Having(ps ...Predicate) *Select[D]
- func (s *Select[D]) Intersect(o *Select[D]) *Select[D]
- func (s *Select[D]) Join(js ...Join) *Select[D]
- func (s *Select[D]) Limit(n int) *Select[D]
- func (s *Select[D]) Offset(n int) *Select[D]
- func (s *Select[D]) Order(ts ...OrderTerm) *Select[D]
- func (s *Select[D]) SetOp(keyword string, o *Select[D]) *Select[D]
- func (s *Select[D]) Union(o *Select[D]) *Select[D]
- func (s *Select[D]) UnionAll(o *Select[D]) *Select[D]
- func (s *Select[D]) Where(ps ...Predicate) *Select[D]
- func (s *Select[D]) Window(name string, w Window) *Select[D]
- func (s *Select[D]) With(name string, body Statement) *Select[D]
- func (s *Select[D]) WithCTE(cte CTE) *Select[D]
- func (s *Select[D]) WithRecursive(name string, seed, step *Select[D]) *Select[D]
- func (s *Select[D]) WithRecursiveCTE(cte CTE) *Select[D]
- func (s *Select[D]) WriteSQL(b *strings.Builder, args *Args)
- type SelectKind
- type Slot
- type SortDirection
- type Source
- type Statement
- type Table
- type Update
- func (u *Update[D]) All() *FilteredUpdate[D]
- func (u *Update[D]) Clause(cs ...Clause[D, UpdateKind]) *Update[D]
- func (u *Update[D]) Clone() *Update[D]
- func (u *Update[D]) Dialect() Dialect
- func (u *Update[D]) Set(as ...Assignment) *Update[D]
- func (u *Update[D]) Table(t Table) *Update[D]
- func (u *Update[D]) Where(ps ...Predicate) *FilteredUpdate[D]
- func (u *Update[D]) With(name string, body Statement) *Update[D]
- type UpdateKind
- type Window
Constants ¶
const ( OpEq = expr.OpEq OpIn = expr.OpIn OpNotIn = expr.OpNotIn LogicAnd = expr.LogicAnd SortAsc = expr.SortAsc SortDesc = expr.SortDesc )
The operator and sort vocabulary this layer emits. Constants cannot be aliased, so each is a new constant with the same value.
Variables ¶
var ( // ErrNoColumns is returned by a SELECT with no bindings and no // expressions. There is nothing to select. ErrNoColumns = errors.New("sqlb: select has no columns") // ErrJoinWithoutFrom is returned when a statement has joins but no // driving source to join them to. ErrJoinWithoutFrom = errors.New("sqlb: select has joins but no FROM") // ErrDuplicateAlias is returned when two sources in one statement share // an alias. Rendering it would produce a statement whose column // references are ambiguous, which the database reports far less clearly. ErrDuplicateAlias = errors.New("sqlb: duplicate table alias in one statement") // ErrNoTargets is returned when One or Each is given a query whose select // list has items with no scan target. Such a query was built with Exprs // and is meant for use as a subquery, not for execution. ErrNoTargets = errors.New("sqlb: query has select items without scan targets") )
var ( // ErrNotAColumn is returned when an assignment target or an INSERT column // list holds something other than a bare column made by Col. An // expression has no name to assign to, and rendering it as an empty // identifier would produce SQL the database rejects much later. ErrNotAColumn = errors.New("sqlb: assignment target is not a column") // ErrNoAssignments is returned by an UPDATE with nothing to SET, or an // INSERT with neither rows nor a source query. There is nothing to write. ErrNoAssignments = errors.New("sqlb: statement has no assignments") // ErrNoWhere is returned by an UPDATE or DELETE with no WHERE and no // All(). The whole table is a real target, but it must be said out loud, // because the cost of the accidental case is the entire table. ErrNoWhere = errors.New("sqlb: update or delete has no WHERE; call All() to affect every row") // ErrRowWidth is returned when an INSERT row has a different number of // values from the column list. The builder cannot know which value was // meant for which column, so it refuses rather than aligning by position. ErrRowWidth = errors.New("sqlb: insert row width differs from column list") )
var ErrNoReturning = errors.New("sqlb: dialect has no RETURNING; use the two-statement fallback")
ErrNoReturning is returned when RETURNING was requested on a dialect that does not have it. The message names the fallback: insert, then select.
var ErrQueryInUse = errors.New("sqlb: query is already executing; build it where its scan targets live rather than sharing one")
ErrQueryInUse is returned by One and Each when a statement is executed while the same statement is already executing.
It means a DATA RACE was about to happen: a Query is not safe for concurrent use, and executing one from two goroutines is a race condition on its scan targets. Go's race detector reports it as a data race on the bound variables, and `go test -race` is how you would otherwise find this.
Why this is an error and not a lock: a Query looks like inert data, a string and some values, and that reading is what makes the mistake easy. It is not inert. Targets holds pointers into the caller's variables, so two goroutines running one Query write to the same memory, and the result is interleaved rows rather than a crash. Serialising them with a lock would hide that: the second caller would get the first caller's rows in its own variables and never know. Refusing says what actually happened.
The fix at the call site is to build the query where its destination lives, which is what the cookbook recipes do, rather than hoisting one query or one builder to package scope and sharing it.
var ErrSetOpArity = errors.New("sqlb: set operation branches select different numbers of columns")
ErrSetOpArity reports that the branches of a set operation select different numbers of columns, which every engine refuses.
Build fails rather than the server, because the message a caller gets back otherwise describes SQL they did not write: scan.All and exec's binding append the reader's columns to the statement they are given, which is the left branch, so a left branch that also declares its own select list ends up one column wider than the right. The engine then reports a mismatch against a query the caller never typed.
var ErrUnknownSlot = errors.New("sqlb: clause asks for a slot this statement does not render")
ErrUnknownSlot is returned when a statement carries a clause for a slot that statement does not render.
Why it is an error and not a shrug: a clause in an unrendered slot used to vanish, producing a statement that built cleanly and silently omitted what the caller asked for. That is the one failure shape this package exists to prevent, and it is reachable from outside the module, where a third-party dialect cannot add a slot of its own. Failing closed turns a missing clause into a message naming the slot.
var ErrUnsupportedDialect = render.ErrUnsupportedDialect
ErrUnsupportedDialect is returned by Build when a construct was rendered through a dialect that does not support it.
JoinTypeValues is the canonical iteration order over join types.
var SlotValues = []Slot{ SlotAfterSelect, SlotAfterColumns, SlotAfterFrom, SlotBeforeWhere, SlotAfterWhere, SlotAfterGroupBy, SlotAfterHaving, SlotBeforeOrder, SlotAfterOrder, SlotAfterOffset, SlotTail, SlotAfterInsert, SlotAfterVerb, SlotAfterTarget, SlotBeforeValues, SlotInsertBody, SlotAfterSet, }
SlotValues lists every slot.
Functions ¶
func DialectName ¶
DialectName reports the engine name of d, or DialectUnknown.
func EngineGuard ¶
EngineGuard reports whether a is rendering for engine, and records the construct as unsupported when it is not.
It is the one place the guard rule lives. A constructor calls it first and returns an empty fragment on false; Build then fails with ErrUnsupportedDialect naming the construct and the dialect it met.
func EngineList ¶
EngineList renders SQLers comma-separated, for a constructor assembling its own argument list.
Types ¶
type AnyClause ¶
AnyClause is a statement part that is neither an expression nor a predicate, with no dialect or kind in its type: what a Join carries for a per-table hint, and what every typed Clause also satisfies. It is an open interface, like Predicate, so an engine package can add one without a method per clause on Select. A clause must call Args.Unsupported when rendered through a dialect it does not belong to.
type AnySelect ¶
AnySelect is a Select whose engine is only known at runtime. It accepts every universal construct and no engine clause, which is exactly what a portable library should be building.
type Assignment ¶
type Assignment struct {
// contains filtered or unexported fields
}
Assignment is one column = value pair for INSERT and UPDATE. Built with Set, SetExpr or SetDefault, so the value type matches the column type.
func AssignTarget ¶
func AssignTarget(col string, target func(a *Args) string, value SQLer) Assignment
AssignTarget makes an assignment whose target is an expression over col, for engine packages: doc['customer']['tier'] = value. col is the base column, kept for the INSERT column list and for error messages.
func Set ¶
func Set[T any](col Expr[T], v T) Assignment
Set assigns a bound value to a column of the same Go type.
func SetExpr ¶
func SetExpr[T any](col Expr[T], e Expr[T]) Assignment
SetExpr assigns an expression of the column's type: SET n = n + 1.
func (Assignment) Column ¶
func (as Assignment) Column() string
Column returns the target column name.
This accessor and the two below exist for the engine packages: ON CONFLICT and MERGE render assignments outside the core's UPDATE and need the parts separately, which is the only reason an Assignment is not opaque.
func (Assignment) SQL ¶
func (as Assignment) SQL(a *Args) string
SQL renders the whole assignment, "column = value".
func (Assignment) ValueSQL ¶
func (as Assignment) ValueSQL(a *Args) string
ValueSQL renders the assigned value alone, without the column or the equals.
type Binding ¶
type Binding interface {
// SQL renders the item.
SQL(a *Args) string
// Target is the scan destination, or nil when the item has none.
Target() any
// ColumnName is the bare column name when the item came from a column, and
// "" otherwise. RETURNING on an unaliased INSERT renders it.
ColumnName() string
}
Binding is a select or RETURNING item that carries a scan destination.
It is an interface, and that is the whole point of this file. The statement layer has to put something in Query.Targets, but it must not know that the something is a pointer into a caller's variable: the moment it does, the hazard belongs to this layer, and a second result strategy has to fight a statement that already took an opinion.
So the statement layer knows only that an item renders, may carry a target, and may know its own bare column name. Whether the target is a pointer, a channel, a slot in an arena, or nil is decided above, by whichever package constructs the item. github.com/ubgo/sqlb/bind is the one that makes it a pointer, and it is where the concurrency warning lives because it is the layer that captured the memory.
Being an interface rather than a struct is also what kept the split from breaking callers. Every existing use was a slice of Binding values produced by Bind, never a struct literal, so the same code compiles against the interface unchanged.
type CTE ¶
type CTE struct {
Name string
Columns []string
Body Statement
Prefix SQLer
// Suffix renders after the body's closing parenthesis, for the SEARCH
// and CYCLE clauses of a recursive CTE.
Suffix SQLer
// contains filtered or unexported fields
}
CTE is one common table expression. Columns renders the optional column list. Prefix renders between AS and the body, for engine words such as MATERIALIZED; the core renders it and an engine package decides it.
type Clause ¶
Clause is a clause typed to the dialect D it belongs to and the statement kind K it may appear in.
Both parameters are phantom, and the marker method is what makes them bite: an interface whose type parameters appeared nowhere in its method set would be satisfied by every clause for every instantiation, and the check would be decoration. Because ClauseOf names D and K, a value built for Postgres and SELECT satisfies exactly Clause[postgres.Dialect, SelectKind], so a Select for SQLite refuses it at compile time and an Update for Postgres refuses it too. The method does nothing; its signature is the whole point.
EngineClause is how one is made. A package outside this module builds clauses for its own dialect through the same factory, and a statement of its own declares its own kind beside it: the kind set is open like the dialect set, and nothing engine-specific enters this package's list.
func EngineClause ¶
func EngineClause[D render.Dialect, K any](construct string, slot Slot, render func(a *Args) string) Clause[D, K]
EngineClause makes a guarded clause for a statement slot.
A statement that does not render the given slot fails at Build with ErrUnknownSlot, so a clause aimed at a position a statement does not have is reported rather than dropped.
D is the dialect the clause belongs to and K the statement kind it may appear in; both are type arguments written once, in the engine package, and never in user code. The engine name the runtime guard compares against is derived from D, so there is no string to misspell and no way to register a clause under one engine and use it from another. The guard stays as belt-and-braces: a statement typed to D can only hold a Clause[D, K], so it never fires for a clause that reached its statement through the types.
D must be usable as a zero value — a plain struct, as every dialect here is. A dialect that is an interface type yields no name and the guard refuses at Build, which is right: an engine clause on a statement whose engine is not statically known has already been refused by the compiler.
type Delete ¶
Delete is a DELETE whose target rows are not yet stated. It has no Build method. Where and All each return a FilteredDelete, which does.
func (*Delete[D]) All ¶
func (x *Delete[D]) All() *FilteredDelete[D]
All says the whole table is the target and completes the statement, which is what makes a DELETE with no WHERE deliberate rather than an omission.
func (*Delete[D]) Clause ¶
func (x *Delete[D]) Clause(cs ...Clause[D, DeleteKind]) *Delete[D]
Clause adds an engine clause, such as pg.DeleteUsing in SlotAfterTarget.
func (*Delete[D]) Dialect ¶
Dialect reports the dialect this statement renders for, so an Option or a helper can branch on the engine without the caller threading it through.
func (*Delete[D]) Where ¶
func (x *Delete[D]) Where(ps ...Predicate) *FilteredDelete[D]
Where states the target rows and completes the statement.
type Expr ¶
Expr is a SQL expression evaluating to the Go type T.
func EngineBinOp ¶
EngineBinOp renders "(left op right)" as a guarded expression typed R.
The parentheses matter. Without them an engine operator nested inside a core one takes its meaning from the engine's precedence table rather than from the author's nesting: pg.BitAnd inside expr.Mul rendered "tag & $1 * $2", which Postgres reads as tag & (16 * 2) because * binds tighter than &. That is valid SQL returning the wrong rows, which is the worst shape a bug can take. Pinned by TestEngineBinOp_NestsCorrectly. op is checked for the shape of an operator rather than against a list, for the reason expr.Binary's is: the operators an engine has come from its extensions, and a list here would refuse a valid one. An extension author writing a helper is still the one choosing the operator; what the shape stops is a payload that closes the expression and appends a statement.
func EngineExpr ¶
EngineExpr makes a guarded expression typed T from a render function. It is the general case; the constructors below are the shapes that recur.
func EngineLit ¶
EngineLit makes a guarded expression from a constant fragment.
Use it rather than Literal for anything only one engine accepts. Literal has no dialect to consult, so a constant such as '{}'::jsonb built on it renders under every dialect and is rejected by the database instead of by Build.
type FilteredDelete ¶
FilteredDelete is a DELETE whose target rows are stated — by a WHERE or by All — and can therefore be built. Build lives here and not on Delete, for the reason given on FilteredUpdate.
func DeleteReturning ¶
func DeleteReturning[D ReturningDialect](s *FilteredDelete[D], bs ...Binding) *FilteredDelete[D]
DeleteReturning adds RETURNING with scan targets to a DELETE, so the deleted rows can be read back in the same statement.
func (*FilteredDelete[D]) Build ¶
func (x *FilteredDelete[D]) Build() (Query, error)
Build renders the statement, or returns the first error it found. The scan targets come from the RETURNING bindings, so One and Each work on the result without a second list.
func (*FilteredDelete[D]) Clause ¶
func (x *FilteredDelete[D]) Clause(cs ...Clause[D, DeleteKind]) *FilteredDelete[D]
Clause adds an engine clause.
func (*FilteredDelete[D]) Clone ¶
func (x *FilteredDelete[D]) Clone() *FilteredDelete[D]
Clone returns an independent copy of the DELETE with its own guard.
func (*FilteredDelete[D]) Dialect ¶
func (x *FilteredDelete[D]) Dialect() Dialect
Dialect reports the dialect this statement renders for.
func (*FilteredDelete[D]) Where ¶
func (x *FilteredDelete[D]) Where(ps ...Predicate) *FilteredDelete[D]
Where adds predicates, joined with AND.
func (*FilteredDelete[D]) With ¶
func (x *FilteredDelete[D]) With(name string, body Statement) *FilteredDelete[D]
With attaches a CTE.
type FilteredUpdate ¶
FilteredUpdate is an UPDATE whose target rows are stated — by a WHERE or by All — and can therefore be built.
Build lives here and not on Update, so an UPDATE with no WHERE does not compile into existence. That sentence was the task list's day-one promise; it shipped as ErrNoWhere at Build, and this is what makes it true. A value made as a struct literal has no predicate, no table and no dialect and is rejected at Build, which is the documented literal hole.
func UpdateReturning ¶
func UpdateReturning[D ReturningDialect](s *FilteredUpdate[D], bs ...Binding) *FilteredUpdate[D]
UpdateReturning adds RETURNING with scan targets to an UPDATE.
func (*FilteredUpdate[D]) Build ¶
func (u *FilteredUpdate[D]) Build() (Query, error)
Build renders the statement, or returns the first error it found. The scan targets come from the RETURNING bindings, so One and Each work on the result without a second list.
func (*FilteredUpdate[D]) Clause ¶
func (u *FilteredUpdate[D]) Clause(cs ...Clause[D, UpdateKind]) *FilteredUpdate[D]
Clause adds an engine clause.
func (*FilteredUpdate[D]) Clone ¶
func (u *FilteredUpdate[D]) Clone() *FilteredUpdate[D]
Clone returns an independent copy of the UPDATE with its own guard.
func (*FilteredUpdate[D]) Dialect ¶
func (u *FilteredUpdate[D]) Dialect() Dialect
Dialect reports the dialect this statement renders for.
func (*FilteredUpdate[D]) Set ¶
func (u *FilteredUpdate[D]) Set(as ...Assignment) *FilteredUpdate[D]
Set adds assignments.
func (*FilteredUpdate[D]) Where ¶
func (u *FilteredUpdate[D]) Where(ps ...Predicate) *FilteredUpdate[D]
Where adds predicates, joined with AND.
func (*FilteredUpdate[D]) With ¶
func (u *FilteredUpdate[D]) With(name string, body Statement) *FilteredUpdate[D]
With attaches a CTE.
type Guard ¶
type Guard struct {
// contains filtered or unexported fields
}
Guard is the execution guard an engine package's statement builder owns.
A builder whose statements carry scan targets must hand every Query it builds one shared guard, and so must every statement derived from it, because derived statements share the caller's target pointers: two Queries from one builder running at once write into the same variables. The core's builders do this internally; Guard is how a statement kind written outside the core, pg.Merge for one, does the same. Create it once, in the constructor, copy it into every derived statement, and build through NewGuardedQuery.
The zero Guard detects nothing, like a Query struct literal. That is the documented hole in the detection, not a way to opt out: a builder that captures targets has no reason to leave it zero.
type Insert ¶
Insert is an INSERT that has no body yet. It has no Build method.
Row, Select and Body each return an InsertReady, which does. That is the whole of the typestate: the only way to obtain a value with Build is to have given the statement something to insert, so the empty INSERT that used to be ErrNoAssignments at Build is now a type error at the call site.
func (*Insert[D]) Body ¶
func (i *Insert[D]) Body(c Clause[D, InsertKind]) *InsertReady[D]
Body completes the statement with an engine clause that supplies the whole body, such as pg.DefaultValues. It is the third door to InsertReady, and it exists because DEFAULT VALUES is a legitimate INSERT with no rows and no query, which the typestate would otherwise make unwritable.
func (*Insert[D]) Clause ¶
func (i *Insert[D]) Clause(cs ...Clause[D, InsertKind]) *Insert[D]
Clause adds an engine clause in a slot other than the body: pg.OnConflict at the tail, pg.OverridingSystemValue after the target. A clause that supplies the whole body, pg.DefaultValues, goes through Body instead, because it is what makes the statement complete.
func (*Insert[D]) Dialect ¶
Dialect reports the dialect this statement renders for, so an Option or a helper can branch on the engine without the caller threading it through.
func (*Insert[D]) Into ¶
Into names the table. The alias renders only when the dialect satisfies InsertAliasing; otherwise the bare table renders and RETURNING uses bare column names for bindings made from Col.
func (*Insert[D]) Row ¶
func (i *Insert[D]) Row(as ...Assignment) *InsertReady[D]
Row adds one row of assignments and completes the statement. The first row fixes the column list; every later row must assign the same columns in the same order.
func (*Insert[D]) Select ¶
func (i *Insert[D]) Select(s *Select[D]) *InsertReady[D]
Select makes the rows come from a query, INSERT INTO t (cols) SELECT ..., and completes the statement.
type InsertAliasing ¶
type InsertAliasing = render.InsertAliasing
InsertAliasing is satisfied by dialects accepting INSERT INTO t AS alias.
type InsertReady ¶
InsertReady is an INSERT that has a body — rows, a query, or an engine clause supplying one — and can therefore be built.
It is the second of two types because an INSERT with nothing to insert is a mistake the compiler can catch: Build lives here and not on Insert, so NewInsert(d).Into(t).Build() does not compile. Reaching this type is what Row, Select and Body do. A value made as a struct literal has no body and no dialect and is rejected at Build, which is the documented literal hole.
func InsertReturning ¶
func InsertReturning[D ReturningDialect](s *InsertReady[D], bs ...Binding) *InsertReady[D]
InsertReturning adds RETURNING with scan targets to an INSERT, so One and Each work on the result. D must have RETURNING, or this does not compile.
func (*InsertReady[D]) Build ¶
func (i *InsertReady[D]) Build() (Query, error)
Build renders the statement, or returns the first error it found. The scan targets come from the RETURNING bindings, so One and Each work on the result without a second list.
func (*InsertReady[D]) Clause ¶
func (i *InsertReady[D]) Clause(cs ...Clause[D, InsertKind]) *InsertReady[D]
Clause adds an engine clause: pg.OnConflict at the tail, pg.OverridingSystemValue after the target.
func (*InsertReady[D]) Clone ¶
func (i *InsertReady[D]) Clone() *InsertReady[D]
Clone returns an independent copy of the INSERT with its own guard.
func (*InsertReady[D]) Dialect ¶
func (i *InsertReady[D]) Dialect() Dialect
Dialect reports the dialect this statement renders for.
func (*InsertReady[D]) Row ¶
func (i *InsertReady[D]) Row(as ...Assignment) *InsertReady[D]
Row adds another row. The column list was fixed by the first.
func (*InsertReady[D]) With ¶
func (i *InsertReady[D]) With(name string, body Statement) *InsertReady[D]
With adds a CTE.
type Join ¶
type Join struct {
Type JoinType
Table Table
Source Source
On []Predicate
// Using renders JOIN ... USING (cols) instead of ON. Named columns only,
// never NATURAL, because a natural join changes meaning when a column is
// added.
Using []string
// Clauses render immediately after this join's table reference and before
// its ON or USING, which is where an engine puts a per-table hint:
// SQLite's INDEXED BY, MySQL's USE INDEX, T-SQL's WITH (NOLOCK).
//
// This is the one position a Slot cannot name, because there is one per
// join rather than one per statement. A clause here is still a Clause, so
// it still carries its dialect guard; its Slot is not consulted, because
// the join itself is the position.
Clauses []AnyClause
}
Join brings a source into a statement. Table is the common case; Source covers a derived table, VALUES, a function or a LATERAL body, and wins when both are set. On is ANDed; an empty On renders no ON clause, which CROSS JOIN and LATERAL ... ON TRUE need, so give a CROSS or LATERAL join sqlb.True() when the dialect requires an ON.
type JoinType ¶
type JoinType string
JoinType is the SQL join keyword. It is a distinct string type rather than a bare string so a caller cannot pass an arbitrary fragment into the FROM clause, which would be an injection point in an otherwise parameterised builder.
const ( JoinInner JoinType = "INNER JOIN" JoinLeft JoinType = "LEFT JOIN" JoinRight JoinType = "RIGHT JOIN" JoinCross JoinType = "CROSS JOIN" )
The join types the core names.
These four are what Postgres, MySQL 8, SQLite and ClickHouse all accept with the same meaning, which is the same rule every other construct here follows.
FULL OUTER JOIN is absent for that reason rather than for want of a use case: MySQL has no FULL OUTER JOIN and emulates it with a UNION of a LEFT and a RIGHT join, so naming it here would mean shipping a constant that works on three engines and silently fails on the fourth.
It is still reachable, because JoinType is an ordinary string type and Join takes whatever it is given:
Join(Join{Type: JoinType("FULL OUTER JOIN"), ...})
That is the documented escape hatch and the SET stays open: no list here decides which joins exist, so an engine's own keyword is reachable without this module naming it. An engine package wanting a version checked against what it supports would wrap it and record Args.Unsupported, the way every other engine-specific construct does.
The TEXT is checked, which is a different thing and was added on 2026-09-24. The keyword is written into the FROM clause verbatim and cannot be quoted — a join keyword in quotes is an identifier — so a JoinType holding "INNER JOIN t ON 1=1; DROP TABLE users; --" was a statement, not a join. It now has to have the shape of a keyword: letters and single spaces, in either case. That enforces the word "keyword" this decision already rested on and refuses nothing a caller could legitimately write; TestJoinAndSetOp_TheEscapeHatchStillWorks builds FULL OUTER JOIN to prove the hatch is intact.
NATURAL joins are absent and will stay absent. A natural join changes which columns it matches on when somebody adds a column, so a query that was correct yesterday returns different rows today with nothing in the diff to show why.
type Option ¶
type Option func(*selectCore)
Option extends a statement that a library built, from code that the library does not know. It carries an extra join, predicate, binding or ordering term. The library applies options before Build and keeps ownership of Build, so its mandatory predicates cannot be removed by an option: options only add.
An Option edits the universal core of a statement and nothing else, which is why it has no dialect type parameter. Go infers a type parameter from a function's arguments, never from where its result is used, so an Option[D] returned by WithWhere(p) could not have inferred D and every caller would have written it out — on the one seam that exists for callers. Editing the core sidesteps that: a clause carries a dialect and an option does not, so clauses are added with Clause on the statement, where the compiler checks them, and there is no WithClause.
func WithColumns ¶
WithColumns adds bindings after the library's own, so the library's scan targets keep their positions.
type Predicate ¶
Predicate is a boolean condition for WHERE or ON.
func EngineBinPred ¶
EngineBinPred renders "(left op right)" as a guarded predicate.
Parenthesised for the same reason as EngineBinOp: a caller nests these inside And, Or and Not, and the author's nesting must decide the meaning rather than the engine's precedence table. The parentheses are never wrong where the bare form is valid, so this is the safe default rather than a judgement about which operators need them.
func EnginePred ¶
EnginePred makes a guarded predicate from a render function.
func Exists ¶
Exists and NotExists. The subquery may be correlated: it references the outer sources by their aliases, which the outer statement's alias check does not see, so a collision between an inner and an outer alias is the author's responsibility.
func InSubquery ¶
InSubquery renders e IN (SELECT ...). The subquery must select one item whose type is T; the compiler cannot check that across the Select, so it is stated in the signature by convention and checked by the database.
func NotExists ¶
NotExists renders NOT EXISTS (SELECT ...), the anti-join. It is usually faster than NOT IN and, unlike NOT IN, it is not defeated by a NULL in the subquery result.
func NotInSubquery ¶
NotInSubquery renders e NOT IN (SELECT ...). Beware the NULL rule: if the subquery returns a single NULL the whole predicate is NULL and matches nothing. NotExists does not have that failure.
func PredicateOf ¶
PredicateOf makes a Predicate from a render function.
func SubqueryCompare ¶
SubqueryCompare renders "e op [quantifier] (SELECT ...)". It is the mechanism behind InSubquery and behind the ANY/ALL forms in engine packages.
Both op and quantifier reach the statement text and neither can be quoted, so both are checked for their shape: op for an operator's and quantifier for a keyword's, which is what ANY, ALL and SOME are. An empty quantifier is legitimate and means the form without one, which is what IN and NOT IN use.
type Query ¶
Query is a rendered statement: SQL text, the bound values in placeholder order, and the scan targets in select-list order. Targets is nil when any select item has no target.
A Query is NOT SAFE FOR CONCURRENT USE. Executing one from two goroutines is a data race: it reads like inert data and is not, because Targets holds pointers into the caller's variables, so both write to the same memory and interleave each other's rows. Go's race detector reports it as a data race on those variables. Build the query where its destination lives, per call, which is what the cookbook recipes do. Reusing one sequentially is fine.
One and Each refuse an overlapping execution with ErrQueryInUse rather than corrupting the result, for a Query made by a builder or by NewQuery. A Query assembled as a struct literal carries no guard and gets no check.
func NewGuardedQuery ¶
NewGuardedQuery is NewQuery for a builder that owns a Guard: every Query it returns for the same guard is detected as sharing targets with the others, so ErrQueryInUse fires when two of them overlap.
func NewQuery ¶
NewQuery assembles a rendered statement with its bound values and scan targets.
It is for an engine package rendering a statement kind the core does not have, such as pg's MERGE. Building the Query through here rather than with a struct literal is what gives it the concurrent-use check; a literal is still valid and simply has none.
func (Query) Acquire ¶
Acquire reports whether execution of q may start, and claims it if so.
It is exported because executing a statement is a layer above building one, and the two live in different packages. The guard has to be reachable from there without exposing how it works: a caller learns only yes or no, and must call Release when the execution finishes.
A Query built as a struct literal has no guard and is always allowed to proceed, which is the documented hole in the detection rather than a bug. The guard detects the overlap it can see; it does not prevent the hazard.
type ReturningDialect ¶
ReturningDialect is a dialect that has RETURNING: render.Dialect plus the capability method. It is a constraint rather than an interface value on purpose. RETURNING used to be a method on the complete write types, checked at Build by a type assertion, which meant an INSERT ... RETURNING on ClickHouse compiled and failed later. A method cannot be conditional on a statement's dialect, but a function's type parameter can be, so the three functions below take the statement, infer D from it, and refuse a D without the method at compile time. A dialect declines the capability by not implementing it, never by returning false, and the runtime check in writeReturning is what happens when one breaks that rule: an outside dialect that implements SupportsReturning and answers false gets ErrNoReturning at Build rather than SQL its engine rejects. It does not guard a statement built as a literal, which this comment claimed until 2026-09-24: a literal still carries a D, and every D satisfying this constraint answers true, so a literal never reaches it.
type SQLer ¶
SQLer is anything that renders itself with the statement's Args.
func EngineQuoteLit ¶
EngineQuoteLit renders s as a string literal, written by the dialect's own quoter.
For author-chosen constants only, such as a config name or a jsonpath the grammar will not take as a parameter. Request data goes through Param; this puts its argument straight into the statement text.
It asks the dialect rather than doubling quotes itself, because that was not enough: on Postgres with standard_conforming_strings off, a backslash in a plain literal is an escape, and a jsonpath's like_regex "^\\d$" matched with the setting on and silently did not with it off. A dialect that does not implement LiteralQuoting cannot be given a literal, and Build fails with ErrNoLiteralQuoting naming it rather than writing one by a guessed rule.
type Select ¶
Select is a SELECT for the dialect D.
D is inferred from NewSelect's argument and never written: a concrete dialect gives a statement that accepts that engine's clauses, and a render.Dialect interface value — the engine chosen at runtime — gives Select[render.Dialect], which accepts only universal constructs, because no engine clause is a Clause[render.Dialect, SelectKind]. That is the portability rule as a type rather than a convention. AnySelect names it.
The universal parts live in the embedded selectCore, which is what an Option edits; the dialect-typed parts — the clauses and the dialect itself — live here.
func NewSelect ¶
NewSelect starts a statement for d.
The execution guard is created here rather than at Build, and that is load-bearing now that every method returns a derived statement. Two statements derived from one base still share their bindings, which are pointers into the caller's variables, so they must share a guard: creating it lazily would have given each derivation its own and quietly allowed the overlapping execution the guard exists to refuse. A Select made as a struct literal still has no guard and still gets no check, which TestQuery_LiteralHasNoGuard pins.
func (*Select[D]) Apply ¶
Apply runs each option against a derived copy of the statement, in order, and returns it. The receiver is not modified.
func (*Select[D]) Build ¶
Build renders the statement. Every check fails closed; the builder spec lists the order.
func (*Select[D]) Clause ¶
func (s *Select[D]) Clause(cs ...Clause[D, SelectKind]) *Select[D]
Clause adds engine clauses. Each is written in its slot, in insertion order within the slot.
func (*Select[D]) Clone ¶
Clone returns a copy that shares nothing with the original, including its execution guard.
Deriving a query no longer needs it: every builder method copies, so base.Where(x) leaves base alone. What Clone means now is narrower and still necessary — a derived statement inherits the guard, because it holds the same bindings, which are pointers into the caller's variables, so two queries derived from one base must not execute at once. A clone is a separate statement and is not refused because the one it came from is running.
Why this is worth its own file rather than a one-line struct copy: a shallow copy shares the backing arrays, so appending to the copy can write into the original's spare capacity and change it. Every slice is therefore copied, and the clause map is rebuilt.
What is deliberately not deep: the expressions, predicates and clauses themselves. Those are values that render and do not mutate, so sharing them is safe and copying them would be pointless work.
func (*Select[D]) Exprs ¶
Exprs adds select items without scan targets. A query with such items cannot be run through One or Each (ErrNoTargets); it is for a subquery, a CTE body, an INSERT ... SELECT, or a caller that scans on its own.
func (*Select[D]) GroupBy ¶
GroupBy adds grouping expressions. Standard SQL, so it is core. Mixed Go types are fine, which is why it takes SQLer. GroupingSets, Cube and Rollup are SQLers too.
func (*Select[D]) Join ¶
Join adds joins, in order. Accumulates across calls, so a library and its caller can each contribute one.
func (*Select[D]) Limit ¶
Limit sets the row limit, which binds as a parameter rather than being rendered into the statement.
func (*Select[D]) Offset ¶
Offset sets the row offset, also bound. Paging far into a large result with OFFSET is slow on every engine; a keyset predicate in Where is the alternative.
func (*Select[D]) SetOp ¶
SetOp adds a set operation with the given keyword. Engine packages use it for INTERSECT ALL and EXCEPT ALL, which SQLite lacks.
The keyword is a string rather than a closed type for the reason JoinType is open: INTERSECT ALL is absent from this module's constants because SQLite has no such thing, and naming it here would ship a constant that works on three engines and fails on the fourth. So the set is the caller's.
The text is not. It is written between the two branches verbatim and cannot be quoted, so it is checked for the shape of a keyword — letters and single spaces. Until 2026-09-24 it was unchecked, and this doc comment said only who used it, which is the failure that hid it: an escape hatch nobody has written down as one reads like an ordinary argument.
func (*Select[D]) Union ¶
Union renders UNION, which removes duplicate rows.
Each branch of a set operation renders parenthesised, so a branch may carry its own ORDER BY and LIMIT. The receiver's ORDER BY, LIMIT and OFFSET apply to the whole compound, as in SQL, and an ORDER BY over a compound must name an output column: use OutputCol, not a table column.
func (*Select[D]) Where ¶
Where adds predicates, joined with AND. Accumulates across calls, which is what lets a caller narrow a query a library built.
func (*Select[D]) With ¶
With adds a common table expression. A later CTE may reference an earlier one. Reference the CTE as a source with Table{Name: name, Alias: alias}.
func (*Select[D]) WithCTE ¶
WithCTE adds a fully specified CTE, for a column list or an engine prefix.
func (*Select[D]) WithRecursive ¶
WithRecursive adds WITH RECURSIVE name AS (seed UNION ALL step). The step references name as a source; the seed and the step must select the same number of items, which the database checks.
func (*Select[D]) WithRecursiveCTE ¶
WithRecursiveCTE adds a fully specified recursive CTE: use it for a UNION (not UNION ALL) step, a column list, or a SEARCH or CYCLE suffix.
type Slot ¶
type Slot int
Slot is a fixed position in a statement where an engine-specific clause may be written. The core defines the slots; engine packages define the clauses.
The set is complete rather than minimal: there is a slot at every boundary the renderer emits, so every position in a statement has a name. That is a deliberate change from "no slot ships ahead of a clause". A slot is the one extension point a package outside this module cannot add for itself, since a position only means something if the renderer writes it, and completeness is what keeps an outside engine package first class. Several slots therefore have no clause in this module; the doc comment on each says which engine's clause it is for, or says plainly that none is known.
A clause aimed at a slot a statement does not render fails at Build with ErrUnknownSlot rather than being dropped.
const ( // SlotAfterSelect sits between SELECT and the column list: DISTINCT ON, TOP. SlotAfterSelect Slot = iota // SlotAfterColumns sits after the select list and before FROM. T-SQL puts // INTO there. No engine in this module uses it; it exists so the set has // no hole between the column list and FROM. SlotAfterColumns // SlotAfterFrom sits after the driving table and before joins: FINAL, // SAMPLE, TABLESAMPLE, ARRAY JOIN. SlotAfterFrom // SlotBeforeWhere sits after joins and before WHERE: PREWHERE. SlotBeforeWhere // SlotAfterWhere sits after WHERE and before GROUP BY, in a SELECT, an // UPDATE or a DELETE. No engine in this module uses it; it exists so the // set has no hole on either side of WHERE. SlotAfterWhere // SlotAfterGroupBy sits after the GROUP BY list: MySQL's WITH ROLLUP and // WITH CUBE, which are a suffix there rather than a wrapper as they are // in Postgres. SlotAfterGroupBy // SlotAfterHaving sits after HAVING and before WINDOW. No engine in this // module uses it; it exists so the set has no hole between the two. SlotAfterHaving // SlotBeforeOrder sits after HAVING and WINDOW and before ORDER BY: // QUALIFY, which DuckDB, Snowflake and BigQuery use to filter on a window // result without a subquery. SlotBeforeOrder // SlotAfterOrder sits after ORDER BY and before LIMIT: LIMIT n BY. SlotAfterOrder // SlotAfterOffset sits after OFFSET and before the tail: FETCH FIRST. SlotAfterOffset // SlotTail sits last: FOR UPDATE, SETTINGS, ON CONFLICT. SlotTail // SlotAfterInsert sits between the INSERT keyword and INTO: SQLite's // OR REPLACE and its siblings, which no other engine spells there. SlotAfterInsert // SlotAfterVerb sits between the UPDATE or DELETE keyword and its target, // where MySQL puts LOW_PRIORITY, QUICK and IGNORE. SlotAfterInsert is the // same position in an INSERT, and is separate because SQLite's OR REPLACE // family renders there and reads as part of the keyword. SlotAfterVerb // SlotBeforeValues is an INSERT slot between the column list and VALUES // or SELECT: OVERRIDING SYSTEM VALUE. SlotBeforeValues // SlotInsertBody replaces the VALUES part of an INSERT: DEFAULT VALUES. SlotInsertBody // SlotAfterSet is an UPDATE slot after the SET list: FROM sources. SlotAfterSet // SlotAfterTarget sits after the target table of an INSERT, UPDATE or // DELETE and before whatever follows it: DELETE's USING sources, and the // place T-SQL puts a table hint such as WITH (TABLOCK). SlotAfterTarget )
type SortDirection ¶
type SortDirection = expr.SortDirection
SortDirection is the ORDER BY direction.
type Source ¶
Source is anything that can appear in FROM or JOIN.
func EngineSource ¶
EngineSource makes a guarded FROM source, for a set-returning function or any other table expression only one engine has.
type Update ¶
Update is an UPDATE whose target rows are not yet stated. It has no Build method. Where and All each return a FilteredUpdate, which does.
func (*Update[D]) All ¶
func (u *Update[D]) All() *FilteredUpdate[D]
All states that every row is the target and completes the statement. It is what makes an UPDATE with no WHERE deliberate rather than an omission.
func (*Update[D]) Clause ¶
func (u *Update[D]) Clause(cs ...Clause[D, UpdateKind]) *Update[D]
Clause adds an engine clause, such as pg.UpdateFrom in SlotAfterSet.
func (*Update[D]) Dialect ¶
Dialect reports the dialect this statement renders for, so an Option or a helper can branch on the engine without the caller threading it through.
func (*Update[D]) Set ¶
func (u *Update[D]) Set(as ...Assignment) *Update[D]
Set adds assignments. Calling it more than once accumulates rather than replacing, so a library and a caller can each contribute.
func (*Update[D]) Where ¶
func (u *Update[D]) Where(ps ...Predicate) *FilteredUpdate[D]
Where states the target rows and completes the statement. Predicates are joined with AND, and more may be added on the result.