Documentation
¶
Overview ¶
Package gohan is a SQL query builder where values are always bound and identifiers are always quoted and escaped by construction, so a caller cannot reintroduce SQL injection through the normal API.
Three escape hatches exist for trusted input only, and must never be built from request data: Raw's sql text, Fn's function name, and Cast's type string.
String position rule: whether a string is treated as a column name or as a bound value depends on where it appears.
- Column position: the argument to Count, Sum, Avg, Min, Max, select lists, GROUP BY, ORDER BY, and INSERT/UPDATE column names is a column name if it is a string.
- Value position: comparison right-hand sides, Fn arguments, Raw arguments, Cast's value, Case's THEN/ELSE, and Val treat a string as a bound value. Pass Col("x") when a column is meant.
Generic assumes ANSI double-quoted identifiers and must not be registered for MySQL, where a double-quoted string is a string literal, not an identifier.
On SQLite, LIKE is ASCII case-insensitive; on PostgreSQL, ClickHouse and Generic, LIKE is case-sensitive.
Index ¶
- Constants
- func InsertColumns(rec any) ([]string, error)
- func RecordColumns(t reflect.Type) ([]string, error)
- func Register(driverName string, d Dialect)
- type CaseBuilder
- type ConflictBuilder
- type DeleteBuilder
- type Dialect
- type Error
- type Expr
- type Feature
- type InsertBuilder
- func (b *InsertBuilder) Build(d Dialect) (string, []any, error)
- func (b *InsertBuilder) Columns(cols ...string) *InsertBuilder
- func (b *InsertBuilder) FromSelect(q *SelectBuilder) *InsertBuilder
- func (b *InsertBuilder) OnConflict(cols ...string) ConflictBuilder
- func (b *InsertBuilder) Returning(cols ...any) *InsertBuilder
- func (b *InsertBuilder) Rows(records ...any) *InsertBuilder
- func (b *InsertBuilder) SetMap(m map[string]any) *InsertBuilder
- func (b *InsertBuilder) Values(vals ...any) *InsertBuilder
- type Order
- type RecordOption
- type SelectBuilder
- func (s *SelectBuilder) AddColumns(cols ...any) *SelectBuilder
- func (s *SelectBuilder) ArrayJoin(cols ...any) *SelectBuilder
- func (s *SelectBuilder) As(alias string) Subquery
- func (s *SelectBuilder) Build(d Dialect) (string, []any, error)
- func (s *SelectBuilder) Columns(cols ...any) *SelectBuilder
- func (s *SelectBuilder) CrossJoin(t any) *SelectBuilder
- func (s *SelectBuilder) Distinct() *SelectBuilder
- func (s *SelectBuilder) Final() *SelectBuilder
- func (s *SelectBuilder) From(table any) *SelectBuilder
- func (s *SelectBuilder) FullJoin(t any, on Expr) *SelectBuilder
- func (s *SelectBuilder) GroupBy(cols ...any) *SelectBuilder
- func (s *SelectBuilder) Having(conds ...Expr) *SelectBuilder
- func (s *SelectBuilder) IsCompound() bool
- func (s *SelectBuilder) Join(t any, on Expr) *SelectBuilder
- func (s *SelectBuilder) JoinUsing(t any, cols ...string) *SelectBuilder
- func (s *SelectBuilder) LeftArrayJoin(cols ...any) *SelectBuilder
- func (s *SelectBuilder) LeftJoin(t any, on Expr) *SelectBuilder
- func (s *SelectBuilder) LeftJoinUsing(t any, cols ...string) *SelectBuilder
- func (s *SelectBuilder) Limit(n uint64) *SelectBuilder
- func (s *SelectBuilder) Offset(n uint64) *SelectBuilder
- func (s *SelectBuilder) OrderBy(o ...any) *SelectBuilder
- func (s *SelectBuilder) Prewhere(conds ...Expr) *SelectBuilder
- func (s *SelectBuilder) RightJoin(t any, on Expr) *SelectBuilder
- func (s *SelectBuilder) Sample(ratio float64) *SelectBuilder
- func (s *SelectBuilder) SampleRows(n uint64) *SelectBuilder
- func (s *SelectBuilder) Settings(m map[string]any) *SelectBuilder
- func (s *SelectBuilder) Union(q *SelectBuilder) *SelectBuilder
- func (s *SelectBuilder) UnionAll(q *SelectBuilder) *SelectBuilder
- func (s *SelectBuilder) Where(conds ...Expr) *SelectBuilder
- func (s *SelectBuilder) With(name string, q *SelectBuilder) *SelectBuilder
- func (s *SelectBuilder) WithRecursive(name string, q *SelectBuilder) *SelectBuilder
- type Statement
- type Subquery
- type TableRef
- type UpdateBuilder
- func (b *UpdateBuilder) All() *UpdateBuilder
- func (b *UpdateBuilder) Build(d Dialect) (string, []any, error)
- func (b *UpdateBuilder) Returning(cols ...any) *UpdateBuilder
- func (b *UpdateBuilder) Set(col string, v any) *UpdateBuilder
- func (b *UpdateBuilder) SetMap(m map[string]any) *UpdateBuilder
- func (b *UpdateBuilder) SetRecord(rec any, opts ...RecordOption) *UpdateBuilder
- func (b *UpdateBuilder) Where(conds ...Expr) *UpdateBuilder
- type Value
- func And(exprs ...Expr) Value
- func Avg(col any) Value
- func Cast(v any, typ string) Value
- func Col(name string) Value
- func Count(col any) Value
- func CountAll() Value
- func Excluded(col string) Value
- func Exists(q *SelectBuilder) Value
- func Fn(name string, args ...any) Value
- func Int(n int64) Value
- func Match(fields map[string]any) Value
- func Max(col any) Value
- func Min(col any) Value
- func Not(e Expr) Value
- func NotExists(q *SelectBuilder) Value
- func Or(exprs ...Expr) Value
- func Raw(sql string, args ...any) Value
- func Sub(q *SelectBuilder) Value
- func Sum(col any) Value
- func Val(v any) Value
- func (v Value) As(alias string) Expr
- func (v Value) Asc() Order
- func (v Value) Between(lo, hi any) Value
- func (v Value) Contains(s string) Value
- func (v Value) Desc() Order
- func (v Value) Eq(x any) Value
- func (v Value) Gt(x any) Value
- func (v Value) Gte(x any) Value
- func (v Value) HasPrefix(s string) Value
- func (v Value) HasSuffix(s string) Value
- func (v Value) ILike(p any) Value
- func (v Value) In(values ...any) Value
- func (v Value) IsNotNull() Value
- func (v Value) IsNull() Value
- func (v Value) Like(p any) Value
- func (v Value) Lt(x any) Value
- func (v Value) Lte(x any) Value
- func (v Value) Neq(x any) Value
- func (v Value) NotBetween(lo, hi any) Value
- func (v Value) NotILike(p any) Value
- func (v Value) NotIn(values ...any) Value
- func (v Value) NotLike(p any) Value
Examples ¶
Constants ¶
const ( ErrUnknownDialect = Error("gohan: unknown or zero dialect") ErrInvalidIdentifier = Error("gohan: invalid identifier") ErrUnsupported = Error("gohan: not supported by dialect") ErrRawArgs = Error("gohan: raw placeholder count does not match arguments") ErrRawPlaceholder = Error("gohan: raw sql contains a forbidden placeholder sequence") ErrInvalidFunction = Error("gohan: invalid function name") ErrInvalidType = Error("gohan: invalid cast type") ErrEmptyMatch = Error("gohan: Match requires at least one field") ErrEmptyCase = Error("gohan: CASE requires at least one WHEN") ErrUnsafeValue = Error("gohan: value type cannot be bound safely for this dialect") ErrTooManyArgs = Error("gohan: statement exceeds the dialect's bound-argument limit") ErrNilExpr = Error("gohan: nil or zero expression") ErrNoWhere = Error("gohan: statement requires a WHERE clause; call All() to affect every row") ErrNoTable = Error("gohan: statement has no table") ErrNeedAlias = Error("gohan: subquery in FROM requires an alias") ErrInvalidColumn = Error("gohan: column must be a string or an expression") ErrInvalidLimit = Error("gohan: LIMIT/OFFSET out of range for dialect") ErrNoColumns = Error("gohan: statement has no columns to write") ErrValueCount = Error("gohan: value count does not match column count") ErrInsertMixed = Error("gohan: Rows, Values, SetMap and FromSelect cannot be combined") ErrInvalidRecord = Error("gohan: record must be a non-nil struct or pointer to struct") ErrRecordType = Error("gohan: all records must have the same type") ErrRecordShape = Error("gohan: record type has a field shape gohan cannot map safely") ErrInconsistentOmit = Error("gohan: records disagree on omitted fields") ErrUnknownField = Error("gohan: field not found in record") ErrDuplicateColumn = Error("gohan: column set more than once") ErrConflictTarget = Error("gohan: DO UPDATE requires conflict columns") ErrCompoundPart = Error("gohan: a UNION member cannot have ORDER BY, LIMIT, OFFSET, WITH, SETTINGS or its own UNION") ErrInvalidSample = Error("gohan: SAMPLE ratio must be in (0, 1]") ErrInvalidSetting = Error("gohan: invalid SETTINGS name") ErrEmptyClause = Error("gohan: clause requires at least one column") )
Variables ¶
This section is empty.
Functions ¶
func InsertColumns ¶
InsertColumns returns the columns an INSERT of rec would write: every mapped column except Auto fields, a nil pointer with OmitNil, and a zero value with OmitEmpty.
func RecordColumns ¶
RecordColumns returns every column t maps (Auto fields included, in field.GetStructMeta order), after the same shape and duplicate checks as recordValues. t may be a struct type or a pointer to one.
Types ¶
type CaseBuilder ¶
type CaseBuilder struct {
// contains filtered or unexported fields
}
CaseBuilder builds a CASE WHEN ... THEN ... [ELSE ...] END expression.
func Case ¶
func Case() CaseBuilder
Case starts a CASE expression.
Example ¶
ExampleCase shows Int for an integer constant inside a CASE expression: on PostgreSQL, a bound constant here would be typed as text and fail SUM's type check ("function sum(text) does not exist"), so integer constants use Int, not a bound value.
package main
import (
"fmt"
"github.com/oddbit-project/gohan"
)
func main() {
sql, args, err := gohan.Select(
gohan.Sum(gohan.Case().
When(gohan.Col("status").Eq("done"), gohan.Int(1)).
Else(gohan.Int(0))).As("done_count"),
).From("tasks").Build(gohan.Postgres())
fmt.Println(sql, args, err)
}
Output: SELECT SUM(CASE WHEN "status" = $1 THEN 1 ELSE 0 END) AS "done_count" FROM "tasks" [done] <nil>
func (CaseBuilder) Else ¶
func (c CaseBuilder) Else(v any) Value
Else adds an ELSE v clause and finishes the CASE expression.
func (CaseBuilder) End ¶
func (c CaseBuilder) End() Value
End finishes the CASE expression without an ELSE clause. No When calls fails with ErrEmptyCase at render.
func (CaseBuilder) When ¶
func (c CaseBuilder) When(cond Expr, then any) CaseBuilder
When adds a WHEN cond THEN then clause.
type ConflictBuilder ¶
type ConflictBuilder struct {
// contains filtered or unexported fields
}
ConflictBuilder configures the action of an ON CONFLICT clause.
func (ConflictBuilder) DoNothing ¶
func (c ConflictBuilder) DoNothing() *InsertBuilder
DoNothing renders "ON CONFLICT [(cols)] DO NOTHING".
func (ConflictBuilder) DoUpdate ¶
func (c ConflictBuilder) DoUpdate(set map[string]any) *InsertBuilder
DoUpdate renders "ON CONFLICT (cols) DO UPDATE SET ...", with set's keys sorted. A value is rendered in place if it is an Expr (e.g. Excluded or Raw), otherwise bound.
func (ConflictBuilder) DoUpdateExcluded ¶
func (c ConflictBuilder) DoUpdateExcluded(cols ...string) *InsertBuilder
DoUpdateExcluded renders "ON CONFLICT (cols) DO UPDATE SET c = excluded.c" for each c in cols. Each c must be one of the INSERT's own columns, else Build fails with ErrUnknownField.
type DeleteBuilder ¶
type DeleteBuilder struct {
// contains filtered or unexported fields
}
DeleteBuilder builds a DELETE statement. Builders are immutable: every method returns a new value and never modifies the receiver.
func Delete ¶
func Delete(table any) *DeleteBuilder
Delete starts a DELETE statement against table: a string or TableRef. A Subquery fails with ErrNoTable at Build.
Example (RequiresWhere) ¶
ExampleDelete_requiresWhere shows that a DELETE with no WHERE clause fails at Build with ErrNoWhere instead of deleting every row. Deleting every row on purpose uses All().
package main
import (
"fmt"
"github.com/oddbit-project/gohan"
)
func main() {
sql, args, err := gohan.Delete("users").Build(gohan.Postgres())
fmt.Println(sql, args, err)
}
Output: [] gohan: statement requires a WHERE clause; call All() to affect every row
func (*DeleteBuilder) All ¶
func (d *DeleteBuilder) All() *DeleteBuilder
All allows the DELETE to build with no WHERE clause, or a trivially true one, affecting every row.
func (*DeleteBuilder) Build ¶
func (d *DeleteBuilder) Build(dl Dialect) (string, []any, error)
Build renders the DELETE statement under dialect dl using a fresh writer.
func (*DeleteBuilder) Returning ¶
func (d *DeleteBuilder) Returning(cols ...any) *DeleteBuilder
Returning sets the RETURNING column list. Zero cols means no RETURNING clause.
func (*DeleteBuilder) Where ¶
func (d *DeleteBuilder) Where(conds ...Expr) *DeleteBuilder
Where appends conds, ANDed with any existing conditions.
type Dialect ¶
type Dialect struct {
// contains filtered or unexported fields
}
Dialect describes the SQL identifier quoting, placeholder style and feature set of a target database.
func ClickHouse ¶
func ClickHouse() Dialect
ClickHouse returns the ClickHouse dialect: `?` placeholders, double-quoted identifiers with backslash doubling, ILIKE/ClickHouse clause support, and no bound-argument limit.
func DialectFor ¶
DialectFor returns the Dialect registered for driverName, or ErrUnknownDialect if none is registered.
func Generic ¶
func Generic() Dialect
Generic returns the ANSI SQL dialect: `?` placeholders, ANSI double-quoted identifiers, UPDATE support, and a 999 bound-argument limit. It must not be registered for MySQL, where double-quoted text is a string literal, not an identifier.
func Postgres ¶
func Postgres() Dialect
Postgres returns the PostgreSQL dialect: numbered ($n) placeholders, double-quoted identifiers, RETURNING/upsert/ILIKE/UPDATE support, and a 65535 bound-argument limit.
func SQLite ¶
func SQLite() Dialect
SQLite returns the SQLite dialect: `?` placeholders, backtick-quoted identifiers, RETURNING/upsert/UPDATE support, and a 32766 bound-argument limit.
type Expr ¶
type Expr interface {
// contains filtered or unexported methods
}
Expr is any renderable SQL fragment. It is sealed: only this package can implement it.
type Feature ¶
type Feature uint
Feature is a bitmask of optional SQL capabilities a Dialect supports.
type InsertBuilder ¶
type InsertBuilder struct {
// contains filtered or unexported fields
}
InsertBuilder builds an INSERT statement. Builders are immutable: every method returns a new value and never modifies the receiver.
func Insert ¶
func Insert(table any) *InsertBuilder
Insert starts an INSERT statement against table: a string or a TableRef without an alias.
Example (Upsert) ¶
ExampleInsert_upsert inserts a row and, on a conflicting primary key, updates the other columns from the values that were about to be inserted (DoUpdateExcluded).
package main
import (
"fmt"
"github.com/oddbit-project/gohan"
)
func main() {
sql, args, err := gohan.Insert("users").
Columns("id", "name", "email").
Values(1, "alice", "alice@example.com").
OnConflict("id").
DoUpdateExcluded("name", "email").
Build(gohan.Postgres())
fmt.Println(sql, args, err)
}
Output: INSERT INTO "users" ("id", "name", "email") VALUES ($1, $2, $3) ON CONFLICT ("id") DO UPDATE SET "name" = excluded."name", "email" = excluded."email" [1 alice alice@example.com] <nil>
func (*InsertBuilder) Build ¶
func (b *InsertBuilder) Build(d Dialect) (string, []any, error)
Build renders the INSERT statement under dialect d using a fresh writer.
func (*InsertBuilder) Columns ¶
func (b *InsertBuilder) Columns(cols ...string) *InsertBuilder
Columns replaces the column list used with Values or FromSelect.
func (*InsertBuilder) FromSelect ¶
func (b *InsertBuilder) FromSelect(q *SelectBuilder) *InsertBuilder
FromSelect makes the INSERT an "INSERT INTO ... SELECT ...", using the column list set by Columns.
func (*InsertBuilder) OnConflict ¶
func (b *InsertBuilder) OnConflict(cols ...string) ConflictBuilder
OnConflict starts an ON CONFLICT clause targeting cols (which may be empty for DoNothing).
func (*InsertBuilder) Returning ¶
func (b *InsertBuilder) Returning(cols ...any) *InsertBuilder
Returning sets the RETURNING column list.
func (*InsertBuilder) Rows ¶
func (b *InsertBuilder) Rows(records ...any) *InsertBuilder
Rows appends one row per record (a struct or pointer to struct). Values are extracted immediately (a snapshot): mutating a record afterwards does not change the statement. The first record decides the column set; a later record whose omit decision (OmitNil/OmitEmpty) differs for any field fails at Build with ErrInconsistentOmit.
func (*InsertBuilder) SetMap ¶
func (b *InsertBuilder) SetMap(m map[string]any) *InsertBuilder
SetMap sets a single row from m, with columns in sorted key order.
func (*InsertBuilder) Values ¶
func (b *InsertBuilder) Values(vals ...any) *InsertBuilder
Values appends one row of values, in column position.
type Order ¶
type Order struct {
// contains filtered or unexported fields
}
Order is an ORDER BY item. It implements Expr.
type RecordOption ¶
type RecordOption func(*recordOptions)
RecordOption configures UpdateBuilder.SetRecord.
func ExcludeFields ¶
func ExcludeFields(names ...string) RecordOption
ExcludeFields removes the named fields (struct field name or db name) from SetRecord.
func IncludeFields ¶
func IncludeFields(names ...string) RecordOption
IncludeFields restricts SetRecord to only the named fields (struct field name or db name). When set, ExcludeFields is ignored.
func SkipZeroValues ¶
func SkipZeroValues() RecordOption
SkipZeroValues skips fields holding their type's zero value.
func WithAutoFields ¶
func WithAutoFields() RecordOption
WithAutoFields includes fields marked Auto, which are otherwise always skipped by SetRecord.
type SelectBuilder ¶
type SelectBuilder struct {
// contains filtered or unexported fields
}
SelectBuilder builds a SELECT statement. Builders are immutable: every method returns a new value and never modifies the receiver.
func Select ¶
func Select(cols ...any) *SelectBuilder
Select starts a SELECT statement with the given select-list columns. A string is a column name; anything else that is not an Expr fails with ErrInvalidColumn at Build.
Example ¶
ExampleSelect builds a SELECT with a WHERE, ORDER BY and LIMIT clause against the PostgreSQL dialect.
package main
import (
"fmt"
"github.com/oddbit-project/gohan"
)
func main() {
sql, args, err := gohan.Select("id", "name").
From("users").
Where(gohan.Col("active").Eq(true)).
OrderBy(gohan.Col("name").Asc()).
Limit(10).
Build(gohan.Postgres())
fmt.Println(sql, args, err)
}
Output: SELECT "id", "name" FROM "users" WHERE "active" = $1 ORDER BY "name" ASC LIMIT 10 [true] <nil>
func (*SelectBuilder) AddColumns ¶
func (s *SelectBuilder) AddColumns(cols ...any) *SelectBuilder
AddColumns appends to the select list.
func (*SelectBuilder) ArrayJoin ¶
func (s *SelectBuilder) ArrayJoin(cols ...any) *SelectBuilder
ArrayJoin adds an ARRAY JOIN clause over cols (by position; Col("a"). As("x") allowed). Zero columns fails with ErrEmptyClause; calling both ArrayJoin and LeftArrayJoin on the same builder fails with ErrUnsupported. ErrUnsupported unless the dialect has FeatureClickHouse.
func (*SelectBuilder) As ¶
func (s *SelectBuilder) As(alias string) Subquery
As turns the SELECT into a Subquery usable as a FROM source, aliased by alias (a single-part identifier).
func (*SelectBuilder) Build ¶
func (s *SelectBuilder) Build(d Dialect) (string, []any, error)
Build renders the SELECT statement under dialect d using a fresh writer.
func (*SelectBuilder) Columns ¶
func (s *SelectBuilder) Columns(cols ...any) *SelectBuilder
Columns replaces the select list.
func (*SelectBuilder) CrossJoin ¶
func (s *SelectBuilder) CrossJoin(t any) *SelectBuilder
CrossJoin adds a CROSS JOIN against t.
func (*SelectBuilder) Distinct ¶
func (s *SelectBuilder) Distinct() *SelectBuilder
Distinct adds DISTINCT to the select list.
func (*SelectBuilder) Final ¶
func (s *SelectBuilder) Final() *SelectBuilder
Final adds FINAL to the FROM clause. Fails with ErrUnsupported at Build on a dialect without FeatureClickHouse, and also when the FROM source is a Subquery (ClickHouse or not).
func (*SelectBuilder) From ¶
func (s *SelectBuilder) From(table any) *SelectBuilder
From sets the FROM source: a string ("schema.table" allowed), a TableRef, or a Subquery.
func (*SelectBuilder) FullJoin ¶
func (s *SelectBuilder) FullJoin(t any, on Expr) *SelectBuilder
FullJoin adds a FULL JOIN against t, matched by on.
func (*SelectBuilder) GroupBy ¶
func (s *SelectBuilder) GroupBy(cols ...any) *SelectBuilder
GroupBy appends to the GROUP BY list.
func (*SelectBuilder) Having ¶
func (s *SelectBuilder) Having(conds ...Expr) *SelectBuilder
Having appends conds, ANDed with any existing HAVING conditions.
func (*SelectBuilder) IsCompound ¶
func (s *SelectBuilder) IsCompound() bool
IsCompound reports whether s has any UNION members. Where/Having/ Prewhere called on a compound builder apply to the first member's core only, while ORDER BY/LIMIT/OFFSET apply to the whole compound; callers that need filters applied to the whole result set (e.g. a repository layer) should reject a compound base query.
func (*SelectBuilder) Join ¶
func (s *SelectBuilder) Join(t any, on Expr) *SelectBuilder
Join adds an INNER JOIN against t, matched by on.
func (*SelectBuilder) JoinUsing ¶
func (s *SelectBuilder) JoinUsing(t any, cols ...string) *SelectBuilder
JoinUsing adds an INNER JOIN against t, matched by the named columns. Zero columns fails with ErrEmptyClause at Build.
func (*SelectBuilder) LeftArrayJoin ¶
func (s *SelectBuilder) LeftArrayJoin(cols ...any) *SelectBuilder
LeftArrayJoin adds a LEFT ARRAY JOIN clause over cols. Zero columns fails with ErrEmptyClause; calling both ArrayJoin and LeftArrayJoin on the same builder fails with ErrUnsupported. ErrUnsupported unless the dialect has FeatureClickHouse.
func (*SelectBuilder) LeftJoin ¶
func (s *SelectBuilder) LeftJoin(t any, on Expr) *SelectBuilder
LeftJoin adds a LEFT JOIN against t, matched by on.
func (*SelectBuilder) LeftJoinUsing ¶
func (s *SelectBuilder) LeftJoinUsing(t any, cols ...string) *SelectBuilder
LeftJoinUsing adds a LEFT JOIN against t, matched by the named columns. Zero columns fails with ErrEmptyClause at Build.
func (*SelectBuilder) Limit ¶
func (s *SelectBuilder) Limit(n uint64) *SelectBuilder
Limit sets the LIMIT clause. Limit(0) renders LIMIT 0.
func (*SelectBuilder) Offset ¶
func (s *SelectBuilder) Offset(n uint64) *SelectBuilder
Offset sets the OFFSET clause.
func (*SelectBuilder) OrderBy ¶
func (s *SelectBuilder) OrderBy(o ...any) *SelectBuilder
OrderBy appends to the ORDER BY list. An Order is used as-is; a string or Value is rendered ascending.
func (*SelectBuilder) Prewhere ¶
func (s *SelectBuilder) Prewhere(conds ...Expr) *SelectBuilder
Prewhere appends conds, ANDed with any existing PREWHERE conditions. ErrUnsupported unless the dialect has FeatureClickHouse.
func (*SelectBuilder) RightJoin ¶
func (s *SelectBuilder) RightJoin(t any, on Expr) *SelectBuilder
RightJoin adds a RIGHT JOIN against t, matched by on.
func (*SelectBuilder) Sample ¶
func (s *SelectBuilder) Sample(ratio float64) *SelectBuilder
Sample adds SAMPLE ratio to the FROM clause. ratio must be in (0, 1] and finite, else ErrInvalidSample at Build. Fails with ErrUnsupported at Build on a dialect without FeatureClickHouse, and also when the FROM source is a Subquery (ClickHouse or not).
func (*SelectBuilder) SampleRows ¶
func (s *SelectBuilder) SampleRows(n uint64) *SelectBuilder
SampleRows adds SAMPLE n (an absolute row count) to the FROM clause. n must be >= 1, else ErrInvalidSample at Build. Fails with ErrUnsupported at Build on a dialect without FeatureClickHouse, and also when the FROM source is a Subquery (ClickHouse or not).
func (*SelectBuilder) Settings ¶
func (s *SelectBuilder) Settings(m map[string]any) *SelectBuilder
Settings merges m into the SETTINGS clause: existing keys are overwritten, new keys are added. m is copied into a sorted []kv; mutating m after the call has no effect. Keys must match ^[A-Za-z_][A-Za-z0-9_]*$, else ErrInvalidSetting at Build. ErrUnsupported unless the dialect has FeatureClickHouse.
func (*SelectBuilder) Union ¶
func (s *SelectBuilder) Union(q *SelectBuilder) *SelectBuilder
Union adds a UNION member: UNION DISTINCT on ClickHouse, UNION elsewhere. q may not have its own ORDER BY, LIMIT, OFFSET, WITH, SETTINGS or UNION — such a member fails with ErrCompoundPart at Build.
Example ¶
ExampleSelectBuilder_Union shows the UNION keyword difference between PostgreSQL (UNION) and ClickHouse (UNION DISTINCT).
package main
import (
"fmt"
"github.com/oddbit-project/gohan"
)
func main() {
q := gohan.Select("id").From("t1").
Union(gohan.Select("id").From("t2"))
sqlPg, _, errPg := q.Build(gohan.Postgres())
fmt.Println(sqlPg, errPg)
sqlCh, _, errCh := q.Build(gohan.ClickHouse())
fmt.Println(sqlCh, errCh)
}
Output: SELECT "id" FROM "t1" UNION SELECT "id" FROM "t2" <nil> SELECT "id" FROM "t1" UNION DISTINCT SELECT "id" FROM "t2" <nil>
func (*SelectBuilder) UnionAll ¶
func (s *SelectBuilder) UnionAll(q *SelectBuilder) *SelectBuilder
UnionAll adds a UNION ALL member. q may not have its own ORDER BY, LIMIT, OFFSET, WITH, SETTINGS or UNION — such a member fails with ErrCompoundPart at Build.
func (*SelectBuilder) Where ¶
func (s *SelectBuilder) Where(conds ...Expr) *SelectBuilder
Where appends conds, ANDed with any existing conditions.
func (*SelectBuilder) With ¶
func (s *SelectBuilder) With(name string, q *SelectBuilder) *SelectBuilder
With adds a WITH entry: "name AS (q)". name must be a single-part identifier.
func (*SelectBuilder) WithRecursive ¶
func (s *SelectBuilder) WithRecursive(name string, q *SelectBuilder) *SelectBuilder
WithRecursive adds a WITH RECURSIVE entry: "name AS (q)". name must be a single-part identifier. If any CTE on the builder was added with WithRecursive, the whole WITH clause renders RECURSIVE.
type Statement ¶
Statement is anything that renders to a complete SQL string and its bound arguments.
type Subquery ¶
type Subquery struct {
// contains filtered or unexported fields
}
Subquery is a SELECT usable as a FROM/JOIN source. It is not an Expr and cannot appear where an expression is expected.
type TableRef ¶
type TableRef struct {
// contains filtered or unexported fields
}
TableRef names a table, with an optional alias.
type UpdateBuilder ¶
type UpdateBuilder struct {
// contains filtered or unexported fields
}
UpdateBuilder builds an UPDATE statement. Builders are immutable: every method returns a new value and never modifies the receiver.
func Update ¶
func Update(table any) *UpdateBuilder
Update starts an UPDATE statement against table: a string or a TableRef without an alias.
Example ¶
ExampleUpdate builds an UPDATE. A nil or trivially-true WHERE is rejected; see ExampleDelete_requiresWhere.
package main
import (
"fmt"
"github.com/oddbit-project/gohan"
)
func main() {
sql, args, err := gohan.Update("users").
Set("name", "bob").
Where(gohan.Col("id").Eq(1)).
Build(gohan.Postgres())
fmt.Println(sql, args, err)
}
Output: UPDATE "users" SET "name" = $1 WHERE "id" = $2 [bob 1] <nil>
func (*UpdateBuilder) All ¶
func (b *UpdateBuilder) All() *UpdateBuilder
All allows the UPDATE to build with no WHERE clause, or a trivially true one, affecting every row.
func (*UpdateBuilder) Build ¶
func (b *UpdateBuilder) Build(d Dialect) (string, []any, error)
Build renders the UPDATE statement under dialect d using a fresh writer.
func (*UpdateBuilder) Returning ¶
func (b *UpdateBuilder) Returning(cols ...any) *UpdateBuilder
Returning sets the RETURNING column list.
func (*UpdateBuilder) Set ¶
func (b *UpdateBuilder) Set(col string, v any) *UpdateBuilder
Set appends "col = v" to the SET list.
func (*UpdateBuilder) SetMap ¶
func (b *UpdateBuilder) SetMap(m map[string]any) *UpdateBuilder
SetMap appends one "col = v" per entry of m, in sorted key order.
func (*UpdateBuilder) SetRecord ¶
func (b *UpdateBuilder) SetRecord(rec any, opts ...RecordOption) *UpdateBuilder
SetRecord appends one "col = v" per updatable field of rec (a struct or pointer to struct), per opts. Values are extracted immediately (a snapshot): mutating rec afterwards does not change the statement. A shape or option error is deferred and reported at Build.
func (*UpdateBuilder) Where ¶
func (b *UpdateBuilder) Where(conds ...Expr) *UpdateBuilder
Where appends conds, ANDed with any existing conditions.
type Value ¶
type Value struct {
// contains filtered or unexported fields
}
Value is any scalar or boolean expression. All comparison methods live on Value. The zero Value is invalid and fails to render with ErrNilExpr.
func And ¶
And renders exprs joined by AND, in parentheses (unless there is exactly one). And() renders (1=1).
func Excluded ¶
Excluded renders "excluded."col"": the proposed-for-insertion value of col, for use inside DoUpdate. ErrUnsupported on dialects without FeatureUpsert.
func Exists ¶
func Exists(q *SelectBuilder) Value
Exists renders "EXISTS (<select>)". If q's combined WHERE is syntactically never (an empty result set) and q has no UNION members, the returned Value is flagged never for the trivially-true/false guards in delete.go/update.go: this over-approximates (e.g. an aggregate subquery over an empty set still returns one row), which only makes the guard refuse more often, never less.
func Fn ¶
Fn renders name(args...). name is trusted input — never build it from request data — and must match ^[A-Za-z_][A-Za-z0-9_]*$.
func Match ¶
Match renders an AND of equality comparisons, one per field, with keys sorted for deterministic output. Match(nil) or an empty map fails with ErrEmptyMatch.
func Not ¶
Not renders NOT (e). If e is a Value, Not swaps its trivial/never flags: the negation of an always-true condition is always-false and vice versa.
func NotExists ¶
func NotExists(q *SelectBuilder) Value
NotExists renders "NOT EXISTS (<select>)". See Exists for the trivial flag.
func Or ¶
Or renders exprs joined by OR, in parentheses (unless there is exactly one). Or() renders (1=0).
func Raw ¶
Raw renders sql text verbatim, substituting each `?` marker with the corresponding argument (bound, or rendered inline if it is an Expr). `??` writes a literal `?`, except on ClickHouse where it is rejected. sql is trusted input — never build it from request data.
Example ¶
ExampleRaw shows Raw's two placeholder forms with a real PostgreSQL operator that itself uses `?`: jsonb's key-exists operator. `??` writes a literal `?` (the operator) without consuming an argument; `?` binds the argument in position.
package main
import (
"fmt"
"github.com/oddbit-project/gohan"
)
func main() {
sql, args, err := gohan.Select().
From("docs").
Where(gohan.Raw("data ?? ?", "owner")).
Build(gohan.Postgres())
fmt.Println(sql, args, err)
}
Output: SELECT * FROM "docs" WHERE (data ? $1) [owner] <nil>
func Sub ¶
func Sub(q *SelectBuilder) Value
Sub renders a scalar subquery: "(<select>)". It shares the outer statement's writer so placeholders keep counting across nesting.
func Val ¶
Val binds v as an argument. Val(nil) renders the keyword NULL. Val(expr) renders expr in place.
func (Value) In ¶
In renders "v IN (values...)", expanding a single slice argument. With no values (after expansion) it renders 1=0. A single *SelectBuilder argument renders "v IN (<select>)".
func (Value) NotBetween ¶
NotBetween renders "v NOT BETWEEN lo AND hi".