Documentation
¶
Overview ¶
Package qb is a standalone PostgreSQL query builder.
qb has no runtime dependency on a database connection — it only uses pgx for safe identifier quoting. Import it independently of any pool code.
Building Queries ¶
All builder methods return the same pointer for chaining. Errors accumulate and are returned on the first Build* call:
sql, args, err := qb.New("students").
Columns("id", "name").
Where(qb.Where("school_id", qb.OpEq, schoolID)).
Where(qb.WhereExists(
qb.New("enrolments").Where(qb.Where("student_id", qb.OpEq, studentID)),
)).
OrderBy("name", qb.Asc, qb.NullsLast).
Limit(20).
BuildSelect()
Supported Statements ¶
- Builder.BuildSelect: SELECT with WHERE, JOIN, GROUP BY, HAVING, ORDER BY, LIMIT, OFFSET, locking, UNION, UNION ALL, CTEs, window functions
- Builder.BuildInsert: INSERT … VALUES … ON CONFLICT … RETURNING
- Builder.BuildInsertBatch: multi-row INSERT in one statement
- Builder.BuildUpdate: UPDATE … SET … WHERE … RETURNING
- Builder.BuildDelete: DELETE … WHERE … RETURNING
Condition Constructors ¶
Use the Where* helpers instead of raw Condition literals:
qb.Where("price", qb.OpGt, 100)
qb.WhereIn("status", []string{"active", "pending"})
qb.WhereNull("deleted_at")
qb.WhereTextSearch("search_vector", "go & postgres")
qb.WhereJSONContains("metadata", map[string]any{"plan": "pro"})
qb.WhereExists(qb.New("orders").Where(qb.Where("user_id", qb.OpEq, uid)))
qb.WhereRaw("lower(email) = ?", email)
Security ¶
Column names passed to Builder.Columns and Where* helpers are quoted via QuoteIdent. Raw expressions (e.g. Builder.Having, JOIN conditions, Builder.OnConflict) are written verbatim — never interpolate user input into those fields.
File Layout ¶
types.go — enum-like constants (JoinType, SortDir, LockMode, …) builder.go — Builder struct, New, Clone, lifecycle quote.go — identifier sanitisation (security-critical) params.go — placeholder helpers ($N shifting, ? injection) select.go — Columns/Distinct/WindowCol + BuildSelect insert.go — OnConflict + BuildInsert + BuildInsertBatch update.go — BuildUpdate delete.go — BuildDelete where.go — Where, WhereGroup, condition rendering join.go — INNER/LEFT/RIGHT/FULL/LATERAL joins cte.go — With, WithRecursive union.go — Union, UnionAll clauses.go — GROUP BY, ORDER BY, LIMIT, OFFSET, locking, RETURNING setters condition.go — Operator constants + Condition constructors
Index ¶
- Constants
- Variables
- func OrGroup(conds ...Condition) condGroup
- func QuoteIdent(name string) string
- type Builder
- func (b *Builder) BuildDelete() (sql string, args []any, err error)
- func (b *Builder) BuildInsert(data map[string]any) (sql string, args []any, err error)
- func (b *Builder) BuildInsertBatch(rows []map[string]any) (sql string, args []any, err error)
- func (b *Builder) BuildSelect() (sql string, args []any, err error)
- func (b *Builder) BuildUpdate(data map[string]any) (sql string, args []any, err error)
- func (b *Builder) Clone() *Builder
- func (b *Builder) Columns(cols ...string) *Builder
- func (b *Builder) Distinct() *Builder
- func (b *Builder) ForShare(wait LockWait) *Builder
- func (b *Builder) ForUpdate(wait LockWait) *Builder
- func (b *Builder) FullJoin(table, condition string) *Builder
- func (b *Builder) GroupBy(cols ...string) *Builder
- func (b *Builder) HasReturning() bool
- func (b *Builder) Having(expr string, args ...any) *Builder
- func (b *Builder) InnerJoin(table, condition string) *Builder
- func (b *Builder) Join(kind JoinType, table, condition string) *Builder
- func (b *Builder) LateralJoin(kind JoinType, sub *Builder, alias string) *Builder
- func (b *Builder) LeftJoin(table, condition string) *Builder
- func (b *Builder) Limit(n int) *Builder
- func (b *Builder) Lock(mode LockMode, wait LockWait) *Builder
- func (b *Builder) Offset(n int) *Builder
- func (b *Builder) OnConflict(clause string) *Builder
- func (b *Builder) OrderBy(col string, dir SortDir, nulls ...NullsOrder) *Builder
- func (b *Builder) Returning(cols ...string) *Builder
- func (b *Builder) ReturningAll() *Builder
- func (b *Builder) ReturningID() *Builder
- func (b *Builder) ReturningNone() *Builder
- func (b *Builder) RightJoin(table, condition string) *Builder
- func (b *Builder) Union(other *Builder) *Builder
- func (b *Builder) UnionAll(other *Builder) *Builder
- func (b *Builder) Where(c Condition) *Builder
- func (b *Builder) WhereGroup(g condGroup) *Builder
- func (b *Builder) WindowCol(fn, partitionBy, orderBy, alias string) *Builder
- func (b *Builder) With(name, query string, args ...any) *Builder
- func (b *Builder) WithRecursive(name, query string, args ...any) *Builder
- type Condition
- func Where(col string, op Operator, val any) Condition
- func WhereAll(col string, val any) Condition
- func WhereAny(col string, val any) Condition
- func WhereArrayContains(col string, val any) Condition
- func WhereArrayOverlap(col string, val any) Condition
- func WhereBetween(col string, low, high any) Condition
- func WhereExists(sub *Builder) Condition
- func WhereIn(col string, vals any) Condition
- func WhereJSONContains(col string, val any) Condition
- func WhereJSONHasKey(col, key string) Condition
- func WhereNotBetween(col string, low, high any) Condition
- func WhereNotExists(sub *Builder) Condition
- func WhereNotIn(col string, vals any) Condition
- func WhereNotNull(col string) Condition
- func WhereNull(col string) Condition
- func WhereRaw(expr string, args ...any) Condition
- func WhereSubquery(col string, op Operator, sub *Builder) Condition
- func WhereTextSearch(col, tsquery string) Condition
- type JoinType
- type LockMode
- type LockWait
- type NullsOrder
- type Operator
- type SortDir
Examples ¶
Constants ¶
const MaxQueryParams = 65535
MaxQueryParams is the PostgreSQL limit on bound parameters per query.
Variables ¶
var ( // ErrNilBuilder is returned when a Builder method is called on a nil builder. ErrNilBuilder = fmt.Errorf("qb: builder is nil") // ErrNoTable is returned when the Builder has no target table. ErrNoTable = fmt.Errorf("qb: table name must not be empty") // ErrEmptyData is returned when INSERT or UPDATE data is empty. ErrEmptyData = fmt.Errorf("qb: data map must not be empty") // ErrEmptyRows is returned when batch rows are empty. ErrEmptyRows = fmt.Errorf("qb: batch rows must not be empty") )
Functions ¶
func OrGroup ¶
func OrGroup(conds ...Condition) condGroup
OrGroup wraps conditions joined with OR inside parentheses:
WHERE (status = $1 OR status = $2) AND total > $3
func QuoteIdent ¶
QuoteIdent safely quotes a single PostgreSQL identifier.
QuoteIdent("my table") → "my table"
Types ¶
type Builder ¶
type Builder struct {
// contains filtered or unexported fields
}
Builder constructs parameterised PostgreSQL queries. All methods return the same pointer for chaining. Errors accumulate and are returned on the first Build* call.
func New ¶
New creates a Builder targeting table.
Example ¶
package main
import (
"fmt"
"github.com/rajangupta9/pgkit/qb"
)
func main() {
sql, args, err := qb.New("users").
Columns("id", "name").
Where(qb.Where("active", qb.OpEq, true)).
OrderBy("created_at", qb.Desc).
Limit(10).
BuildSelect()
if err != nil {
return
}
fmt.Println(sql)
fmt.Println(args)
}
Output: SELECT "id", "name" FROM "users" WHERE "active" = $1 ORDER BY "created_at" DESC LIMIT 10 [true]
func (*Builder) BuildDelete ¶
BuildDelete produces:
[WITH …] DELETE FROM "table" WHERE … [RETURNING …]
func (*Builder) BuildInsert ¶
BuildInsert produces:
[WITH …] INSERT INTO "table" (cols) VALUES ($1,…) [ON CONFLICT …] RETURNING "id"
Default RETURNING is "id". Override with ReturningAll() / Returning(…) / ReturningNone().
func (*Builder) BuildInsertBatch ¶
BuildInsertBatch inserts multiple rows in one statement.
INSERT INTO "table" (col1, col2) VALUES ($1,$2), ($3,$4), … RETURNING "id"
All rows must have identical key sets (first row defines the column list).
func (*Builder) BuildSelect ¶
BuildSelect produces a full parameterised SELECT statement starting at $1.
func (*Builder) BuildUpdate ¶
BuildUpdate produces:
[WITH …] UPDATE "table" SET col=$1,… WHERE … [RETURNING …]
func (*Builder) Clone ¶
Clone returns a deep copy of the builder. Safe to modify without affecting the original.
func (*Builder) Columns ¶
Columns appends expressions to the SELECT list. Plain column names are auto-quoted; expressions like "COUNT(*)", "u.email", "SUM(total) AS total" are written verbatim.
func (*Builder) HasReturning ¶
HasReturning reports whether the builder has an explicit RETURNING clause.
func (*Builder) Having ¶
Having sets a raw HAVING expression. Use ? as placeholder. SECURITY: expr is written verbatim; never let user input flow in.
b.Having("SUM(total) > ?", 500)
func (*Builder) Join ¶
Join appends any JOIN variant. table is quoted as a table expression; condition is written verbatim. Never pass user input directly to condition. For CROSS JOIN pass "" as condition.
func (*Builder) LateralJoin ¶
LateralJoin appends a LATERAL subquery join:
LEFT JOIN LATERAL (SELECT …) AS alias ON TRUE
func (*Builder) OnConflict ¶
OnConflict sets the ON CONFLICT clause for INSERT (upsert). The clause is written verbatim — never let user input flow in.
b.OnConflict("(email) DO UPDATE SET name = EXCLUDED.name, updated_at = NOW()")
b.OnConflict("(id) DO NOTHING")
func (*Builder) OrderBy ¶
func (b *Builder) OrderBy(col string, dir SortDir, nulls ...NullsOrder) *Builder
OrderBy appends a column to ORDER BY.
b.OrderBy("created_at", qb.Desc, qb.NullsLast)
func (*Builder) ReturningAll ¶
ReturningAll appends RETURNING *.
func (*Builder) ReturningID ¶
ReturningID appends RETURNING "id". Default for BuildInsert.
func (*Builder) ReturningNone ¶
ReturningNone suppresses any RETURNING clause, overriding the INSERT default.
func (*Builder) WhereGroup ¶
WhereGroup appends a condGroup (single or OR-group) ANDed into WHERE.
b.WhereGroup(qb.OrGroup(
qb.Where("status", qb.OpEq, "active"),
qb.Where("status", qb.OpEq, "pending"),
))
→ WHERE ("status" = $1 OR "status" = $2)
func (*Builder) WindowCol ¶
WindowCol appends a window function expression to the SELECT list.
fn is the window function call (e.g. "RANK()", "SUM(total)", "LAG(price)"). partitionBy is the PARTITION BY column (empty string = no partition). orderBy is the ORDER BY expression written verbatim (e.g. "total DESC"). alias is the AS name for the column.
SECURITY: fn and orderBy are written verbatim — never pass user input.
qb.New("orders").
Columns("user_id", "country", "total").
WindowCol("RANK()", "country", "total DESC", "rank").
WindowCol("SUM(total)", "country", "created_at", "running_total")
→ SELECT "user_id", "country", "total",
RANK() OVER (PARTITION BY "country" ORDER BY total DESC) AS "rank",
SUM(total) OVER (PARTITION BY "country" ORDER BY created_at) AS "running_total"
type Condition ¶
type Condition struct {
// Column is the quoted column name or qualified reference (e.g. "price",
// "orders.total"). Ignored for OpExists / OpNotExists / OpRaw.
Column string
// Operator is the SQL operator to apply. Use the Op* constants.
Operator Operator
// Value is the right-hand side of the predicate. For OpIn / OpNotIn it
// must be a slice; for OpBetween / OpNotBetween a two-element slice; for
// OpIsNull / OpNotNull it is ignored; for OpRaw it is a rawExpr.
Value any
// Sub is set for EXISTS / NOT EXISTS / column-IN-subquery conditions.
Sub *Builder
}
Condition is a single predicate in a WHERE clause. Prefer the Where* constructor functions over building Condition literals directly.
func WhereAny ¶
WhereAny matches rows where col = ANY(array). val must be a typed slice; pgx binds it as a PostgreSQL array.
func WhereArrayContains ¶
WhereArrayContains checks array_col @> val.
func WhereArrayOverlap ¶
WhereArrayOverlap checks array_col && val (any element in common).
func WhereBetween ¶
WhereBetween constructs a BETWEEN predicate.
func WhereJSONContains ¶
WhereJSONContains checks jsonb_col @> val (JSON superset).
func WhereJSONHasKey ¶
WhereJSONHasKey checks jsonb_col ? key.
func WhereNotBetween ¶
WhereNotBetween constructs a NOT BETWEEN predicate.
func WhereNotExists ¶
WhereNotExists appends NOT EXISTS (subquery).
func WhereNotIn ¶
WhereNotIn constructs a predicate for NOT IN with a slice of values.
func WhereNotNull ¶
WhereNotNull constructs a predicate for IS NOT NULL.
func WhereRaw ¶
WhereRaw appends a raw SQL fragment. Use ? as placeholder.
WhereRaw("lower(email) = ?", email)
WhereRaw("age BETWEEN ? AND ?", 18, 65)
func WhereSubquery ¶
WhereSubquery appends col op (subquery) — e.g. id IN (SELECT id FROM ...). op should be OpIn / OpNotIn or a comparison operator.
func WhereTextSearch ¶
WhereTextSearch checks tsvector_col @@ tsquery.
type JoinType ¶
type JoinType string
JoinType is the SQL JOIN variant used by Builder.Join and its helpers.
const ( JoinInner JoinType = "INNER" // INNER JOIN — only matching rows from both sides. JoinLeft JoinType = "LEFT" // LEFT JOIN — all rows from the left, matched rows from the right. JoinRight JoinType = "RIGHT" // RIGHT JOIN — all rows from the right, matched rows from the left. JoinFull JoinType = "FULL" // FULL JOIN — all rows from both sides. JoinCross JoinType = "CROSS" // CROSS JOIN — cartesian product; no ON condition. )
type LockMode ¶
type LockMode string
LockMode controls the row-level locking clause appended to a SELECT. Pass to Builder.Lock; or use the convenience methods Builder.ForUpdate and Builder.ForShare.
type LockWait ¶
type LockWait string
LockWait controls the wait behaviour when the desired row lock cannot be acquired immediately. Passed to Builder.ForUpdate, Builder.ForShare, and Builder.Lock.
type NullsOrder ¶
type NullsOrder string
NullsOrder controls NULL placement in ORDER BY, passed as an optional third argument to Builder.OrderBy.
const ( NullsFirst NullsOrder = "NULLS FIRST" // NULLs sort before all non-null values. NullsLast NullsOrder = "NULLS LAST" // NULLs sort after all non-null values. )
type Operator ¶
type Operator string
Operator is a SQL comparison or containment operator.
const ( // Scalar comparison OpEq Operator = "=" OpNotEq Operator = "<>" OpLt Operator = "<" OpLte Operator = "<=" OpGt Operator = ">" OpGte Operator = ">=" // Pattern matching OpLike Operator = "LIKE" OpILike Operator = "ILIKE" // Membership OpIn Operator = "IN" OpNotIn Operator = "NOT IN" // Null checks (no value needed) OpIsNull Operator = "IS NULL" OpNotNull Operator = "IS NOT NULL" // Range OpBetween Operator = "BETWEEN" OpNotBetween Operator = "NOT BETWEEN" // PostgreSQL array operators — value must be a Go slice bound as a pgx array. OpAny Operator = "= ANY" // col = ANY($n) OpAll Operator = "= ALL" // col = ALL($n) // Array containment OpArrayContains Operator = "@>" // array_col @> $n OpArrayContainedBy Operator = "<@" // array_col <@ $n OpArrayOverlap Operator = "&&" // array_col && $n // JSONB operators OpJSONContains Operator = "@>" // jsonb_col @> $n OpJSONContainedBy Operator = "<@" // jsonb_col <@ $n OpJSONHasKey Operator = "?" // jsonb_col ? $n OpJSONHasAllKeys Operator = "?&" // jsonb_col ?& $n OpJSONHasAnyKey Operator = "?|" // jsonb_col ?| $n // Full-text search OpTextSearch Operator = "@@" // tsvector_col @@ tsquery // Internal — handled specially by Builder.renderCond OpExists Operator = "__EXISTS__" OpNotExists Operator = "__NOT_EXISTS__" OpSubquery Operator = "__SUBQUERY__" OpRaw Operator = "__RAW__" )