Help us shape the future of Go! Take the 2026 Go Developer Survey

query

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Nov 1, 2026 License: MIT Imports: 12 Imported by: 0

README

Query

Go Reference

When filters and query variants grow, keep SQL conditions and their bound values together instead of assembling strings. query publishes immutable, parameterized SQL definitions from mutable builder callbacks. It does not execute queries or scan results. Keep the normal package name and use statement for the mutable draft, so package types stay available:

import "arkoren.dev/foundation/query"

Table(name, configure...) and FromRaw(source, configure...) return Query. Their callbacks use BuilderFunc = func(*Builder); predicate groups use PredicateBuilderFunc = func(*PredicateBuilder). These aliases also work in callback slices and return types without conversion. Callbacks run directly once, synchronously in order on one *Builder; each setting returns that draft. Supplied nil callbacks panic, while expanded nil or empty slices omit edits. Use base.Derive(configure...) for variants; no callbacks preserves the base and its first error. Publication detaches owned containers and retained root, predicate, and conflict drafts, not arbitrary SQL argument objects. Only published queries and raw statements compile or execute.

statement, err := query.Table("users", func(statement *query.Builder) {
	statement.Select("id", "name")
	statement.Where("active", true)
}).Build()

statement.SQL() returns ANSI SQL with ? placeholders, and statement.Args() returns the separately bound values. This query selects the active users' IDs and names. Identifiers use ANSI double-quote syntax, and paging uses OFFSET n ROWS and FETCH FIRST n ROWS ONLY. Use BuildFor with a concrete dialect for LIMIT/OFFSET databases.

Dialects

BuildFor compiles for a specific database:

users := query.Table("users", func(statement *query.Builder) {
	statement.Where("id", 7)
})

statement, err := users.BuildFor(query.Postgres) // SELECT * FROM "users" WHERE "id" = $1
statement, err = users.BuildFor(query.MySQL)     // SELECT * FROM `users` WHERE `id` = ?
statement, err = users.Build()                 // ANSI, same as BuildFor(query.ANSI)

The package provides ANSI, SQLite, Postgres, and MySQL. Dialects are opaque, comparable values, and the zero Dialect is ANSI. New dialects are added to this package, so the compiler can grow dialect-specific syntax without breaking callers.

Dialect Placeholders Identifiers Paging
ANSI ? "x" OFFSET n ROWS, FETCH FIRST n ROWS ONLY
SQLite ? "x" LIMIT n OFFSET n; offset alone adds LIMIT -1
Postgres $n "x" LIMIT n OFFSET n; offset alone is OFFSET n
MySQL ? `x` LIMIT n OFFSET n; offset alone adds LIMIT 18446744073709551615

ILIKE is valid on Postgres only; other dialects return ErrUnsupported. IS DISTINCT FROM and IS NOT DISTINCT FROM compile on ANSI, Postgres, and SQLite. MySQL has neither operator, so compilation returns ErrUnsupported instead of emitting invalid SQL. The same checks apply to column comparisons and join conditions; raw SQL remains your responsibility.

Raw expressions

Raw expressions that accept arguments, such as HavingRaw, use ? placeholders. BuildFor rewrites each ? outside quoted text ('...', "...", `...`) and outside -- and /* */ comments to the dialect placeholder. Block comments nest on ANSI and Postgres; MySQL and SQLite end them at the first */. The Postgres scanner ends line comments at CR or LF; other dialects use LF. The number of ? placeholders must equal the number of arguments, or the build fails with ErrInvalidQuery. Write ?? for a literal ?, such as the Postgres jsonb operator. An empty expression is an error. On MySQL, -- begins a comment only if whitespace or a control character follows; otherwise placeholders after the two minus signs still bind. Raw text is never escaped or validated: do not build it from user input. Backslash escapes in MySQL strings and Postgres dollar quoting are not understood.

SelectRaw takes no arguments and copies its selection expression verbatim, without scanning placeholders. Write a single ? for a Postgres jsonb operator there, not ??. Do not put bind parameters in a SelectRaw expression.

WhereRaw and OrWhereRaw use the argument-binding scanner described above. query.Raw(sql, args...) uses that scanner too and wraps a whole statement for Exec, Select, and Find calls the builder cannot express; it implements Statementer with Build/BuildFor but is opaque.

Subquery source

To filter the result of SQL the builder cannot express, wrap it with FromRaw:

query.FromRaw(query.Raw("SELECT id, n FROM events WHERE kind = ?", "click"), func(statement *query.Builder) {
	statement.As("e")
	statement.Where("n", ">", 3)
})
// SELECT * FROM (
// SELECT id, n FROM events WHERE kind = ?
// ) AS "e" WHERE "n" > ?

The alias defaults to t. Subquery arguments bind before the outer ones. INSERT, UPDATE, and DELETE reject a raw source.

Mutations

UPDATE and DELETE without a WHERE fail unless Unfiltered() is called, which allows them to affect every row. INSERT, UPDATE, and DELETE also fail when the builder carries joins, grouping, having, ordering, limit, offset, or DISTINCT (and INSERT rejects WHERE).

SQLite also rejects qualified column names in INSERT maps, UPDATE assignments, and conflict targets or assignments with ErrUnsupported. Use name, not users.name, for those written columns. Qualified table names and read-side column references remain available.

Empty WhereIn lists compile as false; empty WhereNotIn lists compile as true, with no arguments in either case. An empty negative membership does not limit which rows a mutation affects, even though it supplies a WHERE clause.

Limit and Offset reject negative inputs. Omitting a limit is unbounded; Limit(0) explicitly requests no rows. Offset(0) explicitly skips no rows and remains present in Inspect, unlike an omitted offset.

Scopes

Tenant or access filters must apply even when a caller adds an OR condition. Use a scope to keep that mandatory filter outside the caller's disjunction.

Scope(fn...) groups the entire nonempty user filter chain, then ANDs each scope as its own group when the statement compiles. This keeps both structured ORs and disjunctions in WhereRaw inside every scope, including predicates added after Scope. Raw fragments still require trusted, constant SQL text and separately bound values; grouping does not validate arbitrary SQL. Without scopes, raw fragments retain their verbatim operator precedence. They apply to SELECT, UPDATE, DELETE, Count(), and Seek, and count as a filter for the Unfiltered() guard. An empty scope is a no-op; a supplied nil callback panics. Each scope has its own *PredicateBuilder, which exposes only predicates and nested groups; INSERT rejects scopes. Ordinary groups accept variadic callbacks on one draft and validate their combined result: an empty ordinary group is invalid.

active := func(predicate *query.PredicateBuilder) {
	predicate.Where("active", true)
}
query.Table("users", func(statement *query.Builder) {
	statement.Where("role", "admin")
	statement.OrWhere("role", "owner")
	statement.Scope(active)
})
// WHERE ("role" = ? OR "role" = ?) AND ("active" = ?)
Exists

WhereExists, WhereNotExists, OrWhereExists, and OrWhereNotExists take a published SELECT query, correlated with WhereColumn. Placeholders continue across the subquery.

members := query.Table("members", func(statement *query.Builder) {
	statement.WhereColumn("members.group_id", "posts.group_id")
	statement.Where("members.user_id", 7)
})
query.Table("posts", func(statement *query.Builder) {
	statement.WhereExists(members)
})

MySQL fails with error 1093 when an UPDATE or DELETE subquery reads the target table.

Returning, upserts, computed updates

Given your row column map, request the inserted ID on Postgres or SQLite:

query.Table("users", func(statement *query.Builder) {
	statement.Insert(row)
	statement.Returning("id")
})

On Postgres or SQLite, update the name when a unique email constraint conflicts:

query.Table("users", func(statement *query.Builder) {
	statement.Insert(row)
	statement.OnConflict("email").DoUpdate("name")
})

To leave an existing row unchanged instead, choose DoNothing:

query.Table("users", func(statement *query.Builder) {
	statement.Insert(row)
	statement.OnConflict("email").DoNothing()
})

For a view counter, compute the new count and timestamp in the database:

query.Table("posts", func(statement *query.Builder) {
	statement.Where("id", 7)
	statement.Increment("views", 1)
	statement.SetRaw("seen_at", "CURRENT_TIMESTAMP")
})
Feature Postgres, SQLite MySQL ANSI
Returning RETURNING cols ErrUnsupported ErrUnsupported
DoUpdate ON CONFLICT (cols) DO UPDATE SET c = EXCLUDED.c ON DUPLICATE KEY UPDATE c = VALUES(c) ErrUnsupported
DoNothing ON CONFLICT (cols) DO NOTHING INSERT IGNORE ErrUnsupported

Design: OnConflict(cols...) returns a parent-attached *Conflict draft so the target and the action read in order. MySQL has no conflict target and ignores the columns, including an omitted target; DoUpdate needs them on Postgres and SQLite. Its update list must be nonempty and contain no duplicate assignments. INSERT IGNORE also downgrades unrelated errors to warnings, and VALUES() is deprecated since MySQL 8.0.20 but works everywhere. Increment, Decrement, and SetRaw turn the builder into an UPDATE and compose with Update(map); map columns render first, sorted, then expressions in call order. Assigning a column twice fails.

Row locking

ForUpdate(), ForShare(), SkipLocked(), and NoWait() append FOR UPDATE|SHARE [SKIP LOCKED|NOWAIT] after paging on Postgres and MySQL (FOR SHARE needs MySQL 8). SQLite has no row locks and ANSI has no portable form, so both return ErrUnsupported. SkipLocked and NoWait need a lock and exclude each other.

Postgres also returns ErrUnsupported when a locking query declares DISTINCT, GROUP BY, or HAVING. Raw selection expressions remain database-validated; the compiler does not inspect them for aggregates or window functions.

Inspect, Count, and Seek

Inspect() returns a Spec copy (Kind such as KindSelect, table, []Order with Column and Desc, limit, offset, distinct, grouped, having, raw source, returning). Count() returns the matching immutable COUNT(*) query without orders, limit, offset, or locks; grouped, distinct, having, and raw-sourced queries are wrapped as SELECT COUNT(*) FROM (...) AS "count". Seek(values, backward) adds a keyset filter from the published query's orders, leaving the base unchanged:

query.Table("posts", func(statement *query.Builder) {
	statement.OrderByDesc("created_at")
	statement.OrderBy("id")
}).Seek([]any{ts, 42}, false)
// WHERE ("created_at" < ? OR ("created_at" = ? AND "id" > ?))
// ORDER BY "created_at" DESC, "id" ASC

backward reverses every comparison and direction. Values cannot be nil, and the orders must end in a unique non-null column. database.CursorPaginate and database.Each use Seek for you.

A raw source makes Count and deep offset pages wrap the whole subquery, so GROUP BY, DISTINCT, or LIMIT inside FromRaw SQL act as optimizer barriers and slow deep pages.

Structs and named parameters

Values turns a struct into the column map that Insert, InsertRows, and Update take, using db tags; Only and Except pick columns. Targets returns the scan destinations of a result row for a struct with the same rules. RawNamed is Raw with :name placeholders, bound from a map such as the one Values returns:

query.Table("posts", func(statement *query.Builder) {
	statement.Insert(query.Values(post, query.Except("id")))
	statement.Returning("id")
})
query.RawNamed("UPDATE posts SET title = :title WHERE id = :id", query.Values(post))

Only and Except copy their column-name slices when you create the options, so later changes to an expanded input slice do not affect a saved option. The copy covers the column names, not the struct's field values; Values returns those values as is.

Several Only options combine their names, as do several Except options; exclusions apply after selection. Only() adds no restriction: by itself it keeps every column, and it does not clear an earlier selection. Except() drops nothing. Expanded nil and empty column slices have the same behavior as supplying no columns.

Values panics on anything but a struct or a non-nil pointer to one. It returns field values as declared. If only *T implements driver.Valuer, an untagged embedded value T follows ordinary flattened field mapping; embed *T to retain and bind that Valuer pointer as one column. Types recognized through sql.Scanner and time.Time remain scalar rather than flattened.

Schema

DDL (CREATE TABLE, ALTER TABLE, indexes, foreign keys) lives in the arkoren.dev/foundation/query/schema package of this module. It uses a callback API and compiles to argument-free statements for every dialect:

operation := schema.Create("posts", func(table *schema.Table) {
	table.ID()
	table.ForeignID("user_id", "users").OnDelete(schema.Cascade)
	table.Text("body")
	table.Timestamps()
	table.Index("user_id")
})
err := operation.Err()
for _, statement := range operation.Statements() {
	compiled, err := statement.BuildFor(query.Postgres)
}

Create, CreateIfNotExists, and Alter accept zero or more declaration callbacks. After validating the table name, they invoke each supplied function directly, once, synchronously in argument order on the same table. An invalid name skips the callbacks. A supplied nil function or application panic propagates and stops the sequence; an expanded nil or empty callback slice supplies none.

The combined declaration is validated after all callbacks. Omitting callbacks or supplying an empty declaration does not infer a schema: Create still needs columns and Alter needs actions, reported through Operation.Err(). Published operations retain independent table, action, and child-declaration snapshots; later changes to retained builders cannot change their SQL or statement count. Builders are not safe for concurrent mutation. Arbitrary values passed to Default are not recursively copied.

Dialect.TransactionalDDL reports whether schema changes roll back: true for Postgres and SQLite, false for MySQL (which commits implicitly) and for ANSI, because the standard does not require transactional DDL and the compiler promises nothing for the generic dialect. Dialect.Quote quotes one identifier part. ErrUnsupported also covers dialect limits in DDL.

On SQLite, adding a NOT NULL column requires a non-NULL literal default; omitting the default or specifying Default(nil) returns ErrUnsupported. Use Nullable() when the added column must allow NULL.

SQLite foreign-key targets must be unqualified and resolve in the referencing table's schema. A qualified References target returns ErrUnsupported rather than silently discarding its schema.

Several column Primary() modifiers form one composite key in Create. Alter rejects multiple added primary-key columns because it emits a separate statement per added column; use raw DDL for an existing table's composite key.

Installation

go get arkoren.dev/foundation/query

Requirements

Go 1.27 or later. Start with the query guide or use the API reference for exact declarations.

Development

go test -short -race ./...
golangci-lint run

License

MIT. See LICENSE.

Documentation

Overview

Package query builds parameterized SQL statements from structured, composable query state.

Query.Build emits ANSI SQL: "?" placeholders, double-quoted identifiers, and OFFSET n ROWS / FETCH FIRST n ROWS ONLY for paging. Query.BuildFor compiles for a specific Dialect: Postgres numbers placeholders, MySQL quotes identifiers with backticks, and MySQL, SQLite, and Postgres page with LIMIT and OFFSET. An OFFSET without a LIMIT renders as LIMIT 18446744073709551615 OFFSET n on MySQL, LIMIT -1 OFFSET n on SQLite, and a bare OFFSET n on Postgres. SQLite otherwise shares ANSI placeholders and identifier quoting. Use SelectRaw and HavingRaw for aggregates and other expressions the structured API cannot express.

Table and FromRaw publish immutable Query values from ordered callbacks on one mutable Builder. Settings return the same draft. Query.Derive starts an independent draft from a base; no callbacks preserves its sticky errors. Supplied nil callbacks panic, while expanded nil or empty slices omit edits. Publication detaches owned containers and retained root and predicate drafts, not arbitrary SQL argument objects. Only published values compile or execute. Values become positional bindings; identifiers are validated and quoted.

Example:

import "arkoren.dev/foundation/query"
users := query.Table("users", func(statement *query.Builder) {
	statement.Where("active", true)
})
statement, err := users.BuildFor(query.Postgres)
if err != nil {
	return err
}
_ = statement.SQL()

Raw sources, statements, and conditions

FromRaw starts a SELECT from a subquery, rendered as FROM (sql) AS alias with the alias set by Builder.As ("t" by default). Its arguments bind before the outer query's. WhereRaw and OrWhereRaw add verbatim conditions like HavingRaw. Raw wraps a whole statement for Exec, Select, and Find calls the builder cannot express; it satisfies Statementer but is opaque.

Mutation features

Builder.Returning appends RETURNING on Postgres and SQLite and fails with ErrUnsupported elsewhere. Builder.OnConflict followed by DoUpdate or DoNothing builds an upsert: ON CONFLICT on Postgres and SQLite, ON DUPLICATE KEY UPDATE with VALUES() or INSERT IGNORE on MySQL, and ErrUnsupported on ANSI. Builder.Increment, Builder.Decrement, and Builder.SetRaw add computed assignments that compose with Builder.Update. Builder.ForUpdate, Builder.ForShare, Builder.SkipLocked, and Builder.NoWait lock selected rows on Postgres and MySQL 8; SQLite and ANSI fail with ErrUnsupported. PostgreSQL row locks also reject DISTINCT, GROUP BY, and HAVING. SQLite rejects qualified written column names in INSERT, UPDATE, and conflict clauses with ErrUnsupported; qualified tables and read references remain available. Duplicate DoUpdate assignments are invalid on every dialect.

Introspection and pagination

Query.Inspect returns a read-only Spec copy of the query's table, orders, paging, and shape: its Kind (SELECT, INSERT, UPDATE, or DELETE) and its Order list. Query.Count derives the matching COUNT(*) query, wrapping grouped, DISTINCT, HAVING, and raw-sourced queries in a subquery. Query.Seek adds keyset (cursor) pagination filters from the query's orders. Both transformations leave the base unchanged. Limit and Offset reject negative inputs. Limit(0) requests no rows rather than an unbounded result; Offset(0) remains an explicit zero in Inspect.

Scopes and exists

Builder.Scope groups the entire nonempty user filter chain, then ANDs each scope as its own group. Neither a later OrWhere nor a disjunction in Builder.WhereRaw can bypass a scope. Scopes also apply to UPDATE, DELETE, and Query.Count, and satisfy the Builder.Unfiltered guard. Each scope receives its own PredicateBuilder; an empty scope is a no-op, nil panics. Ordinary predicate groups share one restricted draft across their callbacks; validate the combined result, which must not be empty. Raw text must remain trusted, constant SQL with bound values: grouping does not validate arbitrary SQL. Without scopes, raw precedence is unchanged. Builder.WhereExists and its NOT and OR variants take a published SELECT query, correlated with Builder.WhereColumn. MySQL fails with error 1093 when an UPDATE or DELETE subquery reads the target table.

UPDATE and DELETE statements without a WHERE clause are rejected unless Builder.Unfiltered is called, which explicitly allows them to affect every row. INSERT, UPDATE, and DELETE builders that carry clauses they cannot render, such as joins, grouping, ordering, limits, or DISTINCT, fail at Build instead of silently dropping them. An empty WhereIn list is false; an empty WhereNotIn list is true. Neither binds arguments. A negative membership with no values therefore does not restrict the affected rows, even though it satisfies the WHERE guard.

Raw expressions and literal safety

SelectRaw and HavingRaw render their expression verbatim and cannot be validated: never build one from user input; pass values as arguments. A raw expression that accepts arguments, such as HavingRaw, binds them through "?" placeholders, which BuildFor rewrites to the dialect placeholder. SelectRaw takes no arguments and copies its expression without scanning: write a single "?" for a PostgreSQL JSON operator there, not "??". Placeholders are counted outside '...', "...", and `...` quotes, -- line comments, and /* */ block comments. The number of placeholders must equal the number of arguments, and "??" writes a literal "?" (for example the PostgreSQL jsonb operator). An empty expression is an error. Block comments nest on ANSI and PostgreSQL; MySQL and SQLite end a block comment at the first */. PostgreSQL line comments end at CR or LF; other dialects use LF. Backslash escapes inside MySQL strings and PostgreSQL dollar quoting are not understood, so keep such text out of raw expressions or pass it as an argument. MySQL recognizes -- as a comment only before whitespace or a control character; otherwise the scanner retains the two subtraction/minus tokens. Structured IS DISTINCT FROM and IS NOT DISTINCT FROM comparisons fail with ErrUnsupported on MySQL; ILIKE and NOT ILIKE require PostgreSQL. These dialect checks also apply to column comparisons and join conditions.

DDL lives in the subpackage arkoren.dev/foundation/query/schema, which compiles to argument-free statements through Raw and reports dialect limits with ErrUnsupported. Dialect.TransactionalDDL tells whether DDL rolls back, and Dialect.Quote quotes one identifier part for a dialect.

Structs and named parameters

Values turns a struct into a column-to-value map for Insert and Update, and Targets returns the scan destinations of a result row for a struct, with one set of field rules (see Values). RawNamed writes caller SQL with :name placeholders instead of ?, so a map from Values can bind it. Only and Except retain copied column names. Only() adds no restriction; Except() drops nothing. Values retains field objects rather than converting them: a nonpointer embedded T with only a pointer Valuer maps its fields, whereas an embedded *T implementing Valuer stays one bound pointer value.

Package query only builds statements. Database execution, result scanning, and transactions belong to the database contract and its drivers.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ANSI uses ? placeholders and double-quoted identifiers. [Query.Build]
	// compiles for ANSI.
	//
	// Example:
	//
	//	statement, err := query.Table("users").BuildFor(query.ANSI)
	//	if err != nil {
	//		return err
	//	}
	//	_ = statement.SQL()
	ANSI = Dialect{
			// contains filtered or unexported fields
	}

	// SQLite uses ? placeholders and double-quoted identifiers.
	//
	// Example:
	//
	//	statement, err := query.Table("users").BuildFor(query.SQLite)
	//	if err != nil {
	//		return err
	//	}
	//	_ = statement.SQL()
	SQLite = Dialect{
			// contains filtered or unexported fields
	}

	// Postgres uses numbered $n placeholders and double-quoted identifiers.
	//
	// Example:
	//
	//	statement, err := query.Table("users", func(statement *query.Builder) {
	//		statement.Where("id", 7)
	//	}).BuildFor(query.Postgres)
	//	if err != nil {
	//		return err
	//	}
	//	_ = statement.Args()
	Postgres = Dialect{
				// contains filtered or unexported fields
	}

	// MySQL uses ? placeholders and backtick-quoted identifiers, so it works
	// without the ANSI_QUOTES SQL mode.
	//
	// Example:
	//
	//	statement, err := query.Table("users").BuildFor(query.MySQL)
	//	if err != nil {
	//		return err
	//	}
	//	_ = statement.SQL()
	MySQL = Dialect{
			// contains filtered or unexported fields
	}
)
View Source
var (
	// ErrUnknownColumn reports that [Targets] found a result column with no
	// field in the destination struct.
	//
	// Example:
	//
	//	var user struct{ ID int }
	//	_, err := query.Targets(&user, []string{"missing"})
	//	_ = errors.Is(err, query.ErrUnknownColumn)
	ErrUnknownColumn = errors.New("column has no field in destination")

	// ErrInvalidTarget reports that [Targets] cannot scan into its
	// destination: it is not a non-nil pointer, or it is a single value and
	// the result does not have exactly one column.
	//
	// Example:
	//
	//	_, err := query.Targets(7, []string{"id"})
	//	_ = errors.Is(err, query.ErrInvalidTarget)
	ErrInvalidTarget = errors.New("invalid scan destination")
)
View Source
var ErrInvalidQuery = errors.New("invalid query")

ErrInvalidQuery identifies validation or compilation failures from Query and RawStatement. Build errors wrap the underlying cause as well as this sentinel, so callers can classify them without depending on diagnostic text.

Example:

_, err := query.Table("invalid table").Build()
_ = errors.Is(err, query.ErrInvalidQuery)
View Source
var ErrUnsupported = errors.New("unsupported by dialect")

ErrUnsupported indicates that a dialect cannot express a statement or clause. Build errors that carry it also match ErrInvalidQuery.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.ForUpdate()
})
_, err := users.BuildFor(query.SQLite)
_ = errors.Is(err, query.ErrUnsupported)

Functions

func Targets

func Targets(dest any, columns []string) ([]any, error)

Targets returns the scan destinations for one result row: one pointer per entry of columns, in order, for use with database/sql.Rows.Scan.

dest is a non-nil pointer to a struct, whose fields follow the rules of Values, or to a single value (a number, a string, time.Time, a database/sql.Scanner, or a struct without mapped fields), which needs exactly one column. A column without a field fails with ErrUnknownColumn; fields without a column are left untouched. Nil embedded struct pointers on the way to a field are allocated. Any other destination fails with ErrInvalidTarget.

Example:

var user struct {
	ID   int
	Name string
}
targets, err := query.Targets(&user, []string{"id", "name"})
if err != nil {
	return err
}
if err := rows.Scan(targets...); err != nil {
	return err
}
_ = user.Name
Example

ExampleTargets handles column-mapping failure before consuming destinations. Writing through those destinations models the values supplied by Rows.Scan.

package main

import (
	"fmt"

	"arkoren.dev/foundation/query"
)

func main() {
	var user struct {
		// ID receives the result's id column.
		ID int

		// Name receives the result's name column.
		Name string
	}

	targets, err := query.Targets(&user, []string{"id", "name"})

	if err != nil {
		fmt.Println(err)

		return
	}

	*targets[0].(*int) = 7
	*targets[1].(*string) = "Ada"

	fmt.Println(user.ID, user.Name)
}
Output:
7 Ada

func Values

func Values(v any, opts ...ValuesOption) map[string]any

Values returns the column values of a struct as a map from column name to value, ready for Builder.Insert, Builder.Update, or RawNamed. v is a struct or a non-nil pointer to one; Values panics on anything else, which is a programming error.

The columns follow one set of rules, shared with Targets and with cursor pagination:

  • The column is the db tag name, the text before the first comma. An empty name uses the lowercased field name, so an untagged exported field "CreatedAt" is the column "createdat". db:"-" skips the field.
  • Unexported fields are skipped.
  • An untagged embedded struct is flattened into the columns of its parent. A field of the outer struct wins over one with the same column in an embedded struct; the first discovered field wins at equal depth.
  • A field whose type implements database/sql/driver.Valuer or, through a pointer, database/sql.Scanner, and time.Time, is one column and is never flattened. The value is returned as is, so the database driver converts it. Valuer detection uses the declared field type: if only *T implements Valuer, embed T to map its fields or use *T to bind the pointer itself. Scanner and time.Time detection still take precedence over flattening.

A nil embedded struct pointer yields a typed nil pointer for each of its columns, which a database driver writes as NULL. Values are not converted: a pointer field is returned as the pointer.

Example:

user := struct {
	ID   int
	Name string
}{ID: 7, Name: "Ada"}
values := query.Values(user, query.Except("id"))
statement, err := query.Table("users", func(statement *query.Builder) {
	statement.Where("id", user.ID)
	statement.Update(values)
}).Build()
if err != nil {
	return err
}
_ = statement.SQL()

Types

type Builder

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

Builder declares mutable SQL settings during construction. Settings return this same draft. Drafts cannot compile or execute, and are not concurrency-safe. Publication detaches their owned state.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.Where("active", true)
})
_ = users

func (*Builder) AddSelect

func (statement *Builder) AddSelect(columns ...string) *Builder

AddSelect appends selected columns. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.AddSelect("email")
})
_ = users

func (*Builder) As

func (statement *Builder) As(alias string) *Builder

As names the subquery source of a FromRaw query. It is an error on builders without a raw source: plain tables cannot be aliased, and a table name with an alias, such as "posts p", is an invalid identifier. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.FromRaw(query.Raw("SELECT id FROM users"), func(statement *query.Builder) {
	statement.As("members")
})
_ = users

func (*Builder) CrossJoin

func (statement *Builder) CrossJoin(table string) *Builder

CrossJoin appends a cross join. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.CrossJoin("roles")
})
_ = users

func (*Builder) Decrement

func (statement *Builder) Decrement(column string, by any) *Builder

Decrement changes the query into an update statement that subtracts by from column, rendered as column = column - ?. See Builder.Increment. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.Where("id", 7)
	statement.Decrement("credits", 1)
})
_ = users

func (*Builder) Delete

func (statement *Builder) Delete() *Builder

Delete changes the query into a delete statement. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.Where("id", 7)
	statement.Delete()
})
_ = users

func (*Builder) Distinct

func (statement *Builder) Distinct() *Builder

Distinct makes the select statement return distinct rows. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.Distinct()
})
_ = users

func (*Builder) ForShare

func (statement *Builder) ForShare() *Builder

ForShare locks the selected rows against concurrent updates while allowing other readers to lock them, rendered as FOR SHARE. MySQL needs 8.0 or later. See Builder.ForUpdate for dialect support. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.ForShare()
})
_ = users

func (*Builder) ForUpdate

func (statement *Builder) ForUpdate() *Builder

ForUpdate locks the selected rows against concurrent writes until the transaction ends, rendered as FOR UPDATE. Locking is supported on Postgres and MySQL; SQLite has no row locks and ANSI has no portable form, so those dialects fail with ErrUnsupported. Only SELECT accepts it. PostgreSQL also rejects DISTINCT, GROUP BY, and HAVING with a row lock, returning ErrUnsupported. Raw expressions remain database-validated. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.ForUpdate()
})
_ = users

func (*Builder) From

func (statement *Builder) From(name string) *Builder

From sets the query's source table. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.From("accounts")
})
_ = users

func (*Builder) GroupBy

func (statement *Builder) GroupBy(columns ...string) *Builder

GroupBy appends grouping columns. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.Select("role")
	statement.GroupBy("role")
})
_ = users

func (*Builder) Having

func (statement *Builder) Having(column string, args ...any) *Builder

Having appends an AND aggregate filter. Use a compatible grouped or aggregate selection; the database validates grouping semantics. Comparison arguments follow Builder.Where. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.Select("role")
	statement.GroupBy("role")
	statement.Having("role", "admin")
})
_ = users

func (*Builder) HavingRaw

func (statement *Builder) HavingRaw(expression string, args ...any) *Builder

HavingRaw appends an AND aggregate filter rendered verbatim, such as "COUNT(*) > ?", with args bound to its placeholders. An empty expression is an error, and the number of ? placeholders must equal len(args); write ?? for a literal question mark. Never pass user input as expression: it is not escaped. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.SelectRaw("COUNT(*) AS total")
	statement.HavingRaw("COUNT(*) > ?", 5)
})
_ = users
Example

ExampleBuilder_HavingRaw selects an aggregate before filtering its single group, rather than retaining the incompatible default wildcard selection.

package main

import (
	"fmt"

	"arkoren.dev/foundation/query"
)

func main() {
	statement, err := query.Table("users", func(statement *query.Builder) {
		statement.SelectRaw("COUNT(*) AS total")
		statement.HavingRaw("COUNT(*) > ?", 5)
	}).BuildFor(query.Postgres)

	if err != nil {
		fmt.Println(err)

		return
	}

	fmt.Println(statement.SQL(), statement.Args())
}
Output:
SELECT COUNT(*) AS total FROM "users" HAVING COUNT(*) > $1 [5]

func (*Builder) Increment

func (statement *Builder) Increment(column string, by any) *Builder

Increment changes the query into an update statement that adds by to column, rendered as column = column + ?. It composes with Builder.Update and other expressions; assigning one column twice fails at Build. A nil amount is an error. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.Where("id", 7)
	statement.Increment("visits", 1)
})
_ = users

func (*Builder) Insert

func (statement *Builder) Insert[M ~map[string]V, V any](values M) *Builder

Insert changes the query into a single-row insert statement. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.Insert(map[string]any{"name": "Ada"})
})
_ = users

func (*Builder) InsertRows

func (statement *Builder) InsertRows[M ~map[string]V, V any](values ...M) *Builder

InsertRows changes the query into a multi-row insert statement. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.InsertRows(map[string]any{"name": "Ada"}, map[string]any{"name": "Grace"})
})
_ = users

func (*Builder) Join

func (statement *Builder) Join(table, first, operator, second string) *Builder

Join appends an inner join with a column comparison. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.Join("accounts", "accounts.id", "=", "users.account_id")
})
_ = users

func (*Builder) Latest

func (statement *Builder) Latest(column string) *Builder

Latest orders descending by column. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.Latest("created_at")
})
_ = users

func (*Builder) LeftJoin

func (statement *Builder) LeftJoin(table, first, operator, second string) *Builder

LeftJoin appends a left join with a column comparison. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.LeftJoin("accounts", "accounts.id", "=", "users.account_id")
})
_ = users

func (*Builder) Limit

func (statement *Builder) Limit(limit int) *Builder

Limit restricts the maximum number of returned rows. Only SELECT accepts it; INSERT, UPDATE, and DELETE reject it at Build. Omission leaves the result unbounded. Zero explicitly requests no rows; negative input records an error that later calls cannot repair. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.Limit(20)
})
_ = users

func (*Builder) NoWait

func (statement *Builder) NoWait() *Builder

NoWait makes a locking SELECT fail at once when a row is locked by another transaction. It requires Builder.ForUpdate or Builder.ForShare and cannot combine with Builder.SkipLocked. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.ForUpdate()
	statement.NoWait()
})
_ = users

func (*Builder) Offset

func (statement *Builder) Offset(offset int) *Builder

Offset skips rows before returning results. It may be used without Limit; each dialect then emits its own unbounded-limit form. Only SELECT accepts it. Omission adds no offset clause. Zero explicitly skips no rows and remains visible through Inspect; negative input records a sticky validation error. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.Offset(40)
})
_ = users

func (*Builder) Oldest

func (statement *Builder) Oldest(column string) *Builder

Oldest orders ascending by column. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.Oldest("created_at")
})
_ = users

func (*Builder) OnConflict

func (statement *Builder) OnConflict(columns ...string) *Conflict

OnConflict begins upsert handling for an INSERT, naming the columns of the unique constraint that may conflict. Postgres and SQLite use them as the ON CONFLICT target and require a nonempty target for Conflict.DoUpdate. MySQL reacts to any unique key and ignores the target, including an empty one. Conflict.DoNothing permits omission on all supported dialects; upserts are unsupported on ANSI. It returns an attached child draft. Finish with DoUpdate or DoNothing to install conflict handling; either completion returns this parent Builder.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.Insert(map[string]any{"email": "ada@example.test"})
	statement.OnConflict("email").DoNothing()
})
_ = users

func (*Builder) OrHaving

func (statement *Builder) OrHaving(column string, args ...any) *Builder

OrHaving appends an OR aggregate filter. Selection and grouping requirements are those of Builder.Having. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.Select("role")
	statement.GroupBy("role")
	statement.Having("role", "admin")
	statement.OrHaving("role", "owner")
})
_ = users
Example

ExampleBuilder_OrHaving filters a compatible grouped selection and appends the second group filter with OR while retaining binding order.

package main

import (
	"fmt"

	"arkoren.dev/foundation/query"
)

func main() {
	statement, err := query.Table("users", func(statement *query.Builder) {
		statement.Select("role")
		statement.GroupBy("role")
		statement.Having("role", "admin")
		statement.OrHaving("role", "owner")
	}).BuildFor(query.Postgres)

	if err != nil {
		fmt.Println(err)

		return
	}

	fmt.Println(statement.SQL(), statement.Args())
}
Output:
SELECT "role" FROM "users" GROUP BY "role" HAVING "role" = $1 OR "role" = $2 [admin owner]

func (*Builder) OrWhere

func (statement *Builder) OrWhere(column string, args ...any) *Builder

OrWhere appends an OR value comparison. Argument forms and nil handling follow Builder.Where. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.OrWhere("role", "admin")
})
_ = users

func (*Builder) OrWhereBetween

func (statement *Builder) OrWhereBetween(column string, minimum, maximum any) *Builder

OrWhereBetween appends an OR inclusive range comparison. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.OrWhereBetween("score", 1, 10)
})
_ = users

func (*Builder) OrWhereColumn

func (statement *Builder) OrWhereColumn(first string, args ...string) *Builder

OrWhereColumn appends an OR comparison between columns. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.OrWhereColumn("created_at", "<", "updated_at")
})
_ = users

func (*Builder) OrWhereExists

func (statement *Builder) OrWhereExists(sub Query) *Builder

OrWhereExists appends an OR EXISTS (sub) filter. See Builder.WhereExists.

Example:

owned := query.Table("owners", func(statement *query.Builder) {
	statement.WhereColumn("owners.post_id", "posts.id")
})
_ = query.Table("posts", func(statement *query.Builder) {
	statement.Where("public", true)
	statement.OrWhereExists(owned)
})

func (*Builder) OrWhereGroup

func (statement *Builder) OrWhereGroup(configure ...PredicateBuilderFunc) *Builder

OrWhereGroup appends a parenthesized OR predicate group. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled. Callbacks run directly once, synchronously in order on one restricted draft; nil entries panic. The final group must contain predicates, even when callbacks are omitted or an expanded empty slice is supplied.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.OrWhereGroup(func(predicate *query.PredicateBuilder) {
		predicate.Where("active", true)
	})
})
_ = users

func (*Builder) OrWhereIn

func (statement *Builder) OrWhereIn[T any](column string, values ...T) *Builder

OrWhereIn appends an OR membership comparison. Empty lists are always false and bind no arguments, as in Builder.WhereIn. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.OrWhereIn("role", "admin", "editor")
})
_ = users

func (*Builder) OrWhereNotBetween

func (statement *Builder) OrWhereNotBetween(column string, minimum, maximum any) *Builder

OrWhereNotBetween appends an OR negative range comparison. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.OrWhereNotBetween("score", 1, 10)
})
_ = users

func (*Builder) OrWhereNotExists

func (statement *Builder) OrWhereNotExists(sub Query) *Builder

OrWhereNotExists appends an OR NOT EXISTS (sub) filter. See Builder.WhereExists.

Example:

hidden := query.Table("hides", func(statement *query.Builder) {
	statement.WhereColumn("hides.post_id", "posts.id")
})
_ = query.Table("posts", func(statement *query.Builder) {
	statement.Where("public", true)
	statement.OrWhereNotExists(hidden)
})

func (*Builder) OrWhereNotGroup

func (statement *Builder) OrWhereNotGroup(configure ...PredicateBuilderFunc) *Builder

OrWhereNotGroup appends a negated OR predicate group. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled. Callbacks run directly once, synchronously in order on one restricted draft; nil entries panic. The final group must contain predicates, even when callbacks are omitted or an expanded empty slice is supplied.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.OrWhereNotGroup(func(predicate *query.PredicateBuilder) {
		predicate.Where("active", true)
	})
})
_ = users

func (*Builder) OrWhereNotIn

func (statement *Builder) OrWhereNotIn[T any](column string, values ...T) *Builder

OrWhereNotIn appends an OR negative membership comparison. Empty lists are always true and bind no arguments, as in Builder.WhereNotIn. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.OrWhereNotIn("role", "guest")
})
_ = users

func (*Builder) OrWhereNotNull

func (statement *Builder) OrWhereNotNull(column string) *Builder

OrWhereNotNull appends an OR not-null comparison. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.OrWhereNotNull("email")
})
_ = users

func (*Builder) OrWhereNull

func (statement *Builder) OrWhereNull(column string) *Builder

OrWhereNull appends an OR null comparison. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.OrWhereNull("deleted_at")
})
_ = users

func (*Builder) OrWhereRaw

func (statement *Builder) OrWhereRaw(expression string, args ...any) *Builder

OrWhereRaw appends an OR filter rendered verbatim. See Builder.WhereRaw. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.OrWhereRaw("lower(email) = ?", "ada@example.test")
})
_ = users

func (*Builder) OrderBy

func (statement *Builder) OrderBy(column string) *Builder

OrderBy appends ascending ordering for column. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.OrderBy("id")
})
_ = users

func (*Builder) OrderByDesc

func (statement *Builder) OrderByDesc(column string) *Builder

OrderByDesc appends descending ordering for column. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.OrderByDesc("created_at")
})
_ = users

func (*Builder) Returning

func (statement *Builder) Returning(columns ...string) *Builder

Returning asks INSERT, UPDATE, and DELETE to return the listed columns, such as "id" or "*". It is supported on Postgres and SQLite; building for MySQL or ANSI, or for a SELECT, fails (ANSI and MySQL with ErrUnsupported). Execute such statements as queries, not exec calls. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.Insert(map[string]any{"name": "Ada"})
	statement.Returning("id")
})
_ = users

func (*Builder) RightJoin

func (statement *Builder) RightJoin(table, first, operator, second string) *Builder

RightJoin appends a right join with a column comparison. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.RightJoin("accounts", "accounts.id", "=", "users.account_id")
})
_ = users

func (*Builder) Scope

func (statement *Builder) Scope(scopes ...Scope) *Builder

Scope adds each scope's predicates as a parenthesized group that is ANDed after the entire nonempty user predicate chain, itself parenthesized when the statement compiles. Thus an OR in WhereRaw or a later OrWhere cannot bypass a scope. Raw expressions must still be trusted SQL with bound values; grouping does not validate or sandbox arbitrary SQL. Scopes also apply to UPDATE, DELETE, and Query.Count, and satisfy the guard that Builder.Unfiltered lifts. Each callback receives an independent restricted draft, once in order. No callbacks or an empty scope adds nothing; supplied nil callbacks panic. The restricted surface permits only predicates, and INSERT rejects scopes. Publication detaches retained predicate drafts.

Example:

active := func(predicate *query.PredicateBuilder) {
	predicate.Where("active", true)
}
statement, err := query.Table("users", func(statement *query.Builder) {
	statement.Where("role", "admin")
	statement.Scope(active)
}).Build()
if err != nil {
	return err
}
_ = statement.SQL()

func (*Builder) Select

func (statement *Builder) Select(columns ...string) *Builder

Select replaces the selected columns. An empty selection compiles as *. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.Select("id", "name")
})
_ = users

func (*Builder) SelectRaw

func (statement *Builder) SelectRaw(expression string) *Builder

SelectRaw appends expression, such as "COUNT(*) AS total", to the selected columns verbatim. An empty expression is an error. Never pass user input: it is not escaped. Unlike Builder.HavingRaw, SelectRaw takes no arguments and does not scan placeholders: a ? is copied as written, so use the single ? for PostgreSQL's JSON operator, not the ?? binding escape. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.SelectRaw("COUNT(*) AS total")
})
_ = users

func (*Builder) SetRaw

func (statement *Builder) SetRaw(column, expression string, args ...any) *Builder

SetRaw changes the query into an update statement that assigns the SQL expression to column, such as SetRaw("seen_at", "COALESCE(seen_at, ?)", now). Placeholder rules follow Builder.HavingRaw. Never pass user input as expression. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.Where("id", 7)
	statement.SetRaw("visits", "visits + ?", 1)
})
_ = users

func (*Builder) SkipLocked

func (statement *Builder) SkipLocked() *Builder

SkipLocked makes a locking SELECT skip rows another transaction has locked. It requires Builder.ForUpdate or Builder.ForShare and cannot combine with Builder.NoWait. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.ForUpdate()
	statement.SkipLocked()
})
_ = users

func (*Builder) Unfiltered

func (statement *Builder) Unfiltered() *Builder

Unfiltered allows an UPDATE or DELETE without a WHERE clause, affecting every row. Without it, Build rejects such statements. It has no effect on SELECT or INSERT. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.Delete()
	statement.Unfiltered()
})
_ = users

func (*Builder) Update

func (statement *Builder) Update[M ~map[string]V, V any](values M) *Builder

Update changes the query into an update statement. It replaces earlier Update values but keeps expressions added by Builder.Increment, Builder.Decrement, and Builder.SetRaw. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.Where("id", 7)
	statement.Update(map[string]any{"name": "Ada"})
})
_ = users

func (*Builder) Where

func (statement *Builder) Where(column string, args ...any) *Builder

Where appends an AND value comparison. Pass (column, value) for equality or (column, operator, value) for an explicit supported operator. Equality/inequality with typed or untyped nil becomes IS NULL/IS NOT NULL. Invalid arguments record a sticky error. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.Where("active", true)
})
_ = users

func (*Builder) WhereBetween

func (statement *Builder) WhereBetween(column string, minimum, maximum any) *Builder

WhereBetween appends an AND inclusive range comparison. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereBetween("score", 1, 10)
})
_ = users

func (*Builder) WhereColumn

func (statement *Builder) WhereColumn(first string, args ...string) *Builder

WhereColumn appends an AND comparison between columns. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereColumn("created_at", "<", "updated_at")
})
_ = users

func (*Builder) WhereExists

func (statement *Builder) WhereExists(sub Query) *Builder

WhereExists appends an AND EXISTS (sub) filter. The subquery must be a SELECT; correlate it with Builder.WhereColumn. MySQL rejects an UPDATE or DELETE whose subquery reads the target table (error 1093).

Example:

members := query.Table("members", func(statement *query.Builder) {
	statement.WhereColumn("members.group_id", "posts.group_id")
	statement.Where("members.user_id", 7)
})
_ = query.Table("posts", func(statement *query.Builder) {
	statement.WhereExists(members)
})

func (*Builder) WhereGroup

func (statement *Builder) WhereGroup(configure ...PredicateBuilderFunc) *Builder

WhereGroup appends a parenthesized AND predicate group. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled. Callbacks run directly once, synchronously in order on one restricted draft; nil entries panic. The final group must contain predicates, even when callbacks are omitted or an expanded empty slice is supplied.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereGroup(func(predicate *query.PredicateBuilder) {
		predicate.Where("active", true)
	})
})
_ = users

func (*Builder) WhereIn

func (statement *Builder) WhereIn[T any](column string, values ...T) *Builder

WhereIn appends an AND membership comparison. An empty value list, including an expanded nil slice, is always false and binds no arguments. Use an explicit type argument when no values infer T. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereIn("role", "admin", "editor")
})
_ = users

func (*Builder) WhereNotBetween

func (statement *Builder) WhereNotBetween(column string, minimum, maximum any) *Builder

WhereNotBetween appends an AND negative range comparison. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereNotBetween("score", 1, 10)
})
_ = users

func (*Builder) WhereNotExists

func (statement *Builder) WhereNotExists(sub Query) *Builder

WhereNotExists appends an AND NOT EXISTS (sub) filter. See Builder.WhereExists.

Example:

banned := query.Table("bans", func(statement *query.Builder) {
	statement.WhereColumn("bans.user_id", "users.id")
})
_ = query.Table("users", func(statement *query.Builder) {
	statement.WhereNotExists(banned)
})

func (*Builder) WhereNotGroup

func (statement *Builder) WhereNotGroup(configure ...PredicateBuilderFunc) *Builder

WhereNotGroup appends a negated AND predicate group. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled. Callbacks run directly once, synchronously in order on one restricted draft; nil entries panic. The final group must contain predicates, even when callbacks are omitted or an expanded empty slice is supplied.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereNotGroup(func(predicate *query.PredicateBuilder) {
		predicate.Where("active", true)
	})
})
_ = users

func (*Builder) WhereNotIn

func (statement *Builder) WhereNotIn[T any](column string, values ...T) *Builder

WhereNotIn appends an AND negative membership comparison. An empty value list, including an expanded nil slice, is always true and binds no arguments; it does not restrict an UPDATE or DELETE to fewer rows. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereNotIn("role", "guest")
})
_ = users

func (*Builder) WhereNotNull

func (statement *Builder) WhereNotNull(column string) *Builder

WhereNotNull appends an AND not-null comparison. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereNotNull("email")
})
_ = users

func (*Builder) WhereNull

func (statement *Builder) WhereNull(column string) *Builder

WhereNull appends an AND null comparison. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereNull("deleted_at")
})
_ = users

func (*Builder) WhereRaw

func (statement *Builder) WhereRaw(expression string, args ...any) *Builder

WhereRaw appends an AND filter rendered verbatim, such as "lower(email) = ?", with args bound to its placeholders. The placeholder rules of Builder.HavingRaw apply. Never pass user input as expression. It mutates this draft and returns the same receiver. Invalid settings remain recorded until the published query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereRaw("lower(email) = ?", "ada@example.test")
})
_ = users

type BuilderFunc

type BuilderFunc = func(*Builder)

BuilderFunc edits the shared construction draft used by Table, FromRaw, and Query.Derive. It is an alias, so ordinary callbacks and callback slices are accepted without conversion. Supplied callbacks run synchronously; publication detaches owned draft state, not arbitrary argument objects.

Example:

policies := []query.BuilderFunc{
	func(statement *query.Builder) {
		statement.Where("active", true)
	},
}
users := query.Table("users", policies...)
_ = users
Example

ExampleBuilderFunc composes a reusable construction policy through the public alias and compiles its published query, without retaining a mutable draft.

package main

import (
	"fmt"

	"arkoren.dev/foundation/query"
)

func main() {
	policies := []query.BuilderFunc{
		func(statement *query.Builder) {
			statement.Where("active", true)
		},
	}
	statement, err := query.Table("users", policies...).BuildFor(query.Postgres)

	if err != nil {
		fmt.Println(err)

		return
	}

	fmt.Println(statement.SQL())
	fmt.Println(statement.Args())
}
Output:
SELECT * FROM "users" WHERE "active" = $1
[true]

type Conflict

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

Conflict selects how an INSERT handles rows that violate a unique constraint. Create one with Builder.OnConflict and finish it with Conflict.DoUpdate or Conflict.DoNothing. Completion changes the attached parent; publication detaches conflict state. Neither the child nor its parent draft can compile or execute SQL.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.Insert(map[string]any{"email": "ada@example.test", "name": "Ada"})
	conflict := statement.OnConflict("email")
	conflict.DoUpdate("name")
})
_ = users

func (*Conflict) DoNothing

func (conflict *Conflict) DoNothing() *Builder

DoNothing skips rows that conflict. It renders ON CONFLICT DO NOTHING on Postgres and SQLite, and INSERT IGNORE on MySQL, which also downgrades other errors, such as invalid values, to warnings.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.Insert(map[string]any{"email": "ada@example.test", "name": "Ada"})
	statement.OnConflict("email").DoNothing()
})
_ = users

func (*Conflict) DoUpdate

func (conflict *Conflict) DoUpdate(columns ...string) *Builder

DoUpdate overwrites the listed columns of the existing row with the values the INSERT proposed. It renders SET "c" = EXCLUDED."c" on Postgres and SQLite, and ON DUPLICATE KEY UPDATE `c` = VALUES(`c`) on MySQL. VALUES() is deprecated since MySQL 8.0.20 but works on every MySQL and MariaDB. The column list must be nonempty and contain no duplicate assignments. Invalid input records a sticky error on the parent draft.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.Insert(map[string]any{"email": "ada@example.test", "name": "Ada"})
	statement.OnConflict("email").DoUpdate("name")
})
_ = users

type Dialect

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

Dialect selects the SQL syntax a database expects for bind parameters and quoted identifiers. Dialects are opaque, comparable values; use ANSI, SQLite, Postgres, or MySQL. The zero Dialect is ANSI.

Example:

dialect := query.Postgres
statement, err := query.Table("users").BuildFor(dialect)
if err != nil {
	return err
}
_ = statement.SQL()

func (Dialect) Quote

func (dialect Dialect) Quote(part string) string

Quote wraps one identifier part in the dialect's quote character and doubles any quote character inside it. It does not split qualified names and does not validate: quote "schema.table" part by part.

Example:

query.MySQL.Quote("users")    // `users`
query.Postgres.Quote("users") // "users"

func (Dialect) String

func (dialect Dialect) String() string

String returns the dialect name. Names are lowercase: ansi, sqlite, postgres, or mysql, including ansi for the zero Dialect. The result is diagnostic text, not a dialect constructor.

Example:

_ = query.Postgres.String() // "postgres"

func (Dialect) TransactionalDDL

func (dialect Dialect) TransactionalDDL() bool

TransactionalDDL reports whether the dialect rolls back schema changes with the surrounding transaction. PostgreSQL and SQLite do. MySQL implicitly commits around most DDL statements. It is also false for ANSI: the standard does not require transactional DDL and real databases differ, so the compiler promises nothing for the generic dialect.

Example:

transactional := query.Postgres.TransactionalDDL()
_ = transactional // true; schema execution can roll back with its transaction.

type Kind

type Kind uint8

Kind identifies the SQL statement a Query compiles to. Its zero value is KindSelect; obtain the actual kind from Query.Inspect.

Example:

kind := query.Table("users").Inspect().Kind
_ = kind.String() // "SELECT"
const (
	// KindSelect is the zero kind, so a new Builder selects.
	//
	// Example:
	//
	//	_ = query.Table("users").Inspect().Kind == query.KindSelect
	KindSelect Kind = iota

	// KindInsert compiles rows into INSERT.
	//
	// Example:
	//
	//	users := query.Table("users", func(statement *query.Builder) {
	//		statement.Insert(map[string]any{"name": "Ada"})
	//	})
	//	_ = users.Inspect().Kind == query.KindInsert
	KindInsert

	// KindUpdate compiles updates and predicates into UPDATE.
	//
	// Example:
	//
	//	users := query.Table("users", func(statement *query.Builder) {
	//		statement.Update(map[string]any{"name": "Ada"})
	//		statement.Where("id", 7)
	//	})
	//	_ = users.Inspect().Kind == query.KindUpdate
	KindUpdate

	// KindDelete compiles predicates into DELETE.
	//
	// Example:
	//
	//	users := query.Table("users", func(statement *query.Builder) {
	//		statement.Delete()
	//		statement.Where("id", 7)
	//	})
	//	_ = users.Inspect().Kind == query.KindDelete
	KindDelete
)

func (Kind) String

func (kind Kind) String() string

String returns the statement keyword: SELECT, INSERT, UPDATE, or DELETE. Values outside the declared kinds retain the SELECT fallback.

Example:

_ = query.Table("users").Inspect().Kind.String() // "SELECT"

type Order

type Order struct {
	// Column is the ordered column, qualified with dots when it was.
	//
	// Example:
	//
	//	users := query.Table("users", func(statement *query.Builder) {
	//		statement.OrderBy("users.id")
	//	})
	//	_ = users.Inspect().Orders[0].Column // "users.id"
	Column string

	// Desc reports whether the ordering is descending.
	//
	// Example:
	//
	//	users := query.Table("users", func(statement *query.Builder) {
	//		statement.OrderByDesc("id")
	//	})
	//	_ = users.Inspect().Orders[0].Desc // true
	Desc bool
}

Order is one ordering of a Spec. Inspect returns orders in declaration order, with owned slice storage. Editing an Order changes only the inspection result, not its query.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.OrderByDesc("users.id")
})
ordering := users.Inspect().Orders[0]
_ = ordering.Column // "users.id"
_ = ordering.Desc   // true

type PredicateBuilder

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

PredicateBuilder declares only filters inside scopes and predicate groups. It has no source, selection, mutation, ordering, or compilation methods. Methods return this draft; publication detaches retained nested declarations.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereGroup(func(predicate *query.PredicateBuilder) {
		predicate.Where("active", true)
		predicate.OrWhere("role", "admin")
	})
})
_ = users

func (*PredicateBuilder) OrWhere

func (predicate *PredicateBuilder) OrWhere(column string, args ...any) *PredicateBuilder

OrWhere adds the predicate described by Builder.OrWhere. It changes this restricted draft and returns the same receiver; validation errors remain sticky until the containing query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereGroup(func(predicate *query.PredicateBuilder) {
		predicate.OrWhere("role", "admin")
	})
})
_ = users

func (*PredicateBuilder) OrWhereBetween

func (predicate *PredicateBuilder) OrWhereBetween(column string, minimum, maximum any) *PredicateBuilder

OrWhereBetween adds the predicate described by Builder.OrWhereBetween. It changes this restricted draft and returns the same receiver; validation errors remain sticky until the containing query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereGroup(func(predicate *query.PredicateBuilder) {
		predicate.OrWhereBetween("score", 1, 10)
	})
})
_ = users

func (*PredicateBuilder) OrWhereColumn

func (predicate *PredicateBuilder) OrWhereColumn(first string, args ...string) *PredicateBuilder

OrWhereColumn adds the predicate described by Builder.OrWhereColumn. It changes this restricted draft and returns the same receiver; validation errors remain sticky until the containing query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereGroup(func(predicate *query.PredicateBuilder) {
		predicate.OrWhereColumn("created_at", "<", "updated_at")
	})
})
_ = users

func (*PredicateBuilder) OrWhereExists

func (predicate *PredicateBuilder) OrWhereExists(sub Query) *PredicateBuilder

OrWhereExists adds the predicate described by Builder.OrWhereExists. It changes this restricted draft and returns the same receiver; validation errors remain sticky until the containing query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereGroup(func(predicate *query.PredicateBuilder) {
		predicate.OrWhereExists(query.Table("members"))
	})
})
_ = users

func (*PredicateBuilder) OrWhereGroup

func (predicate *PredicateBuilder) OrWhereGroup(configure ...PredicateBuilderFunc) *PredicateBuilder

OrWhereGroup adds the predicate described by Builder.OrWhereGroup. It changes this restricted draft and returns the same receiver; validation errors remain sticky until the containing query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereGroup(func(predicate *query.PredicateBuilder) {
		predicate.OrWhereGroup(func(predicate *query.PredicateBuilder) {
			predicate.Where("active", true)
		})
	})
})
_ = users

func (*PredicateBuilder) OrWhereIn

func (predicate *PredicateBuilder) OrWhereIn[T any](column string, values ...T) *PredicateBuilder

OrWhereIn adds the predicate described by Builder.OrWhereIn. It changes this restricted draft and returns the same receiver; validation errors remain sticky until the containing query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereGroup(func(predicate *query.PredicateBuilder) {
		predicate.OrWhereIn("role", "admin", "editor")
	})
})
_ = users

func (*PredicateBuilder) OrWhereNotBetween

func (predicate *PredicateBuilder) OrWhereNotBetween(column string, minimum, maximum any) *PredicateBuilder

OrWhereNotBetween adds the predicate described by Builder.OrWhereNotBetween. It changes this restricted draft and returns the same receiver; validation errors remain sticky until the containing query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereGroup(func(predicate *query.PredicateBuilder) {
		predicate.OrWhereNotBetween("score", 1, 10)
	})
})
_ = users

func (*PredicateBuilder) OrWhereNotExists

func (predicate *PredicateBuilder) OrWhereNotExists(sub Query) *PredicateBuilder

OrWhereNotExists adds the predicate described by Builder.OrWhereNotExists. It changes this restricted draft and returns the same receiver; validation errors remain sticky until the containing query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereGroup(func(predicate *query.PredicateBuilder) {
		predicate.OrWhereNotExists(query.Table("bans"))
	})
})
_ = users

func (*PredicateBuilder) OrWhereNotGroup

func (predicate *PredicateBuilder) OrWhereNotGroup(configure ...PredicateBuilderFunc) *PredicateBuilder

OrWhereNotGroup adds the predicate described by Builder.OrWhereNotGroup. It changes this restricted draft and returns the same receiver; validation errors remain sticky until the containing query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereGroup(func(predicate *query.PredicateBuilder) {
		predicate.OrWhereNotGroup(func(predicate *query.PredicateBuilder) {
			predicate.Where("active", true)
		})
	})
})
_ = users

func (*PredicateBuilder) OrWhereNotIn

func (predicate *PredicateBuilder) OrWhereNotIn[T any](column string, values ...T) *PredicateBuilder

OrWhereNotIn adds the predicate described by Builder.OrWhereNotIn. It changes this restricted draft and returns the same receiver; validation errors remain sticky until the containing query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereGroup(func(predicate *query.PredicateBuilder) {
		predicate.OrWhereNotIn("role", "guest")
	})
})
_ = users

func (*PredicateBuilder) OrWhereNotNull

func (predicate *PredicateBuilder) OrWhereNotNull(column string) *PredicateBuilder

OrWhereNotNull adds the predicate described by Builder.OrWhereNotNull. It changes this restricted draft and returns the same receiver; validation errors remain sticky until the containing query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereGroup(func(predicate *query.PredicateBuilder) {
		predicate.OrWhereNotNull("email")
	})
})
_ = users

func (*PredicateBuilder) OrWhereNull

func (predicate *PredicateBuilder) OrWhereNull(column string) *PredicateBuilder

OrWhereNull adds the predicate described by Builder.OrWhereNull. It changes this restricted draft and returns the same receiver; validation errors remain sticky until the containing query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereGroup(func(predicate *query.PredicateBuilder) {
		predicate.OrWhereNull("deleted_at")
	})
})
_ = users

func (*PredicateBuilder) OrWhereRaw

func (predicate *PredicateBuilder) OrWhereRaw(expression string, args ...any) *PredicateBuilder

OrWhereRaw adds the predicate described by Builder.OrWhereRaw. It changes this restricted draft and returns the same receiver; validation errors remain sticky until the containing query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereGroup(func(predicate *query.PredicateBuilder) {
		predicate.OrWhereRaw("lower(email) = ?", "ada@example.test")
	})
})
_ = users

func (*PredicateBuilder) Where

func (predicate *PredicateBuilder) Where(column string, args ...any) *PredicateBuilder

Where adds the predicate described by Builder.Where. It changes this restricted draft and returns the same receiver; validation errors remain sticky until the containing query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereGroup(func(predicate *query.PredicateBuilder) {
		predicate.Where("active", true)
	})
})
_ = users

func (*PredicateBuilder) WhereBetween

func (predicate *PredicateBuilder) WhereBetween(column string, minimum, maximum any) *PredicateBuilder

WhereBetween adds the predicate described by Builder.WhereBetween. It changes this restricted draft and returns the same receiver; validation errors remain sticky until the containing query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereGroup(func(predicate *query.PredicateBuilder) {
		predicate.WhereBetween("score", 1, 10)
	})
})
_ = users

func (*PredicateBuilder) WhereColumn

func (predicate *PredicateBuilder) WhereColumn(first string, args ...string) *PredicateBuilder

WhereColumn adds the predicate described by Builder.WhereColumn. It changes this restricted draft and returns the same receiver; validation errors remain sticky until the containing query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereGroup(func(predicate *query.PredicateBuilder) {
		predicate.WhereColumn("created_at", "<", "updated_at")
	})
})
_ = users

func (*PredicateBuilder) WhereExists

func (predicate *PredicateBuilder) WhereExists(sub Query) *PredicateBuilder

WhereExists adds the predicate described by Builder.WhereExists. It changes this restricted draft and returns the same receiver; validation errors remain sticky until the containing query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereGroup(func(predicate *query.PredicateBuilder) {
		predicate.WhereExists(query.Table("members"))
	})
})
_ = users

func (*PredicateBuilder) WhereGroup

func (predicate *PredicateBuilder) WhereGroup(configure ...PredicateBuilderFunc) *PredicateBuilder

WhereGroup adds the predicate described by Builder.WhereGroup. It changes this restricted draft and returns the same receiver; validation errors remain sticky until the containing query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereGroup(func(predicate *query.PredicateBuilder) {
		predicate.WhereGroup(func(predicate *query.PredicateBuilder) {
			predicate.Where("active", true)
		})
	})
})
_ = users

func (*PredicateBuilder) WhereIn

func (predicate *PredicateBuilder) WhereIn[T any](column string, values ...T) *PredicateBuilder

WhereIn adds the predicate described by Builder.WhereIn. It changes this restricted draft and returns the same receiver; validation errors remain sticky until the containing query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereGroup(func(predicate *query.PredicateBuilder) {
		predicate.WhereIn("role", "admin", "editor")
	})
})
_ = users

func (*PredicateBuilder) WhereNotBetween

func (predicate *PredicateBuilder) WhereNotBetween(column string, minimum, maximum any) *PredicateBuilder

WhereNotBetween adds the predicate described by Builder.WhereNotBetween. It changes this restricted draft and returns the same receiver; validation errors remain sticky until the containing query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereGroup(func(predicate *query.PredicateBuilder) {
		predicate.WhereNotBetween("score", 1, 10)
	})
})
_ = users

func (*PredicateBuilder) WhereNotExists

func (predicate *PredicateBuilder) WhereNotExists(sub Query) *PredicateBuilder

WhereNotExists adds the predicate described by Builder.WhereNotExists. It changes this restricted draft and returns the same receiver; validation errors remain sticky until the containing query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereGroup(func(predicate *query.PredicateBuilder) {
		predicate.WhereNotExists(query.Table("bans"))
	})
})
_ = users

func (*PredicateBuilder) WhereNotGroup

func (predicate *PredicateBuilder) WhereNotGroup(configure ...PredicateBuilderFunc) *PredicateBuilder

WhereNotGroup adds the predicate described by Builder.WhereNotGroup. It changes this restricted draft and returns the same receiver; validation errors remain sticky until the containing query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereGroup(func(predicate *query.PredicateBuilder) {
		predicate.WhereNotGroup(func(predicate *query.PredicateBuilder) {
			predicate.Where("active", true)
		})
	})
})
_ = users

func (*PredicateBuilder) WhereNotIn

func (predicate *PredicateBuilder) WhereNotIn[T any](column string, values ...T) *PredicateBuilder

WhereNotIn adds the predicate described by Builder.WhereNotIn. It changes this restricted draft and returns the same receiver; validation errors remain sticky until the containing query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereGroup(func(predicate *query.PredicateBuilder) {
		predicate.WhereNotIn("role", "guest")
	})
})
_ = users

func (*PredicateBuilder) WhereNotNull

func (predicate *PredicateBuilder) WhereNotNull(column string) *PredicateBuilder

WhereNotNull adds the predicate described by Builder.WhereNotNull. It changes this restricted draft and returns the same receiver; validation errors remain sticky until the containing query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereGroup(func(predicate *query.PredicateBuilder) {
		predicate.WhereNotNull("email")
	})
})
_ = users

func (*PredicateBuilder) WhereNull

func (predicate *PredicateBuilder) WhereNull(column string) *PredicateBuilder

WhereNull adds the predicate described by Builder.WhereNull. It changes this restricted draft and returns the same receiver; validation errors remain sticky until the containing query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereGroup(func(predicate *query.PredicateBuilder) {
		predicate.WhereNull("deleted_at")
	})
})
_ = users

func (*PredicateBuilder) WhereRaw

func (predicate *PredicateBuilder) WhereRaw(expression string, args ...any) *PredicateBuilder

WhereRaw adds the predicate described by Builder.WhereRaw. It changes this restricted draft and returns the same receiver; validation errors remain sticky until the containing query is compiled.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.WhereGroup(func(predicate *query.PredicateBuilder) {
		predicate.WhereRaw("lower(email) = ?", "ada@example.test")
	})
})
_ = users

type PredicateBuilderFunc

type PredicateBuilderFunc = func(*PredicateBuilder)

PredicateBuilderFunc edits only predicates in a group or scope. Group callbacks share one restricted draft; each Scope callback has its own. The alias preserves identity with func(*PredicateBuilder), including slices.

Example:

policies := []query.PredicateBuilderFunc{
	func(predicate *query.PredicateBuilder) {
		predicate.Where("active", true)
	},
}
users := query.Table("users", func(statement *query.Builder) {
	statement.Scope(policies...)
})
_ = users
Example

ExamplePredicateBuilderFunc uses the restricted callback alias for a protected scope, leaving the whole user disjunction inside its own AND boundary.

package main

import (
	"fmt"

	"arkoren.dev/foundation/query"
)

func main() {
	policies := []query.PredicateBuilderFunc{
		func(predicate *query.PredicateBuilder) {
			predicate.Where("active", true)
		},
	}

	statement, err := query.Table("users", func(statement *query.Builder) {
		statement.Where("role", "admin")
		statement.OrWhere("role", "owner")
		statement.Scope(policies...)
	}).BuildFor(query.Postgres)

	if err != nil {
		fmt.Println(err)

		return
	}

	fmt.Println(statement.SQL())
	fmt.Println(statement.Args())
}
Output:
SELECT * FROM "users" WHERE ("role" = $1 OR "role" = $2) AND ("active" = $3)
[admin owner true]

type Query

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

Query is an immutable SQL definition, compiled lazily for a chosen dialect. Construct it with Table or FromRaw and use Derive for independent variants. Its owned containers are detached from construction drafts. Arbitrary argument objects remain caller-owned: do not mutate them while compiling or executing.

Example:

users := query.Table("users")
statement, err := users.BuildFor(query.Postgres)
if err != nil {
	return err
}
_ = statement.SQL()

func FromRaw

func FromRaw(statement RawStatement, configure ...BuilderFunc) Query

FromRaw publishes a SELECT over a raw subquery, with default alias "t". Use As to change the alias. Positional or named source arguments bind before outer predicates through the normal dialect compiler, not precompiled SQL. Empty SQL and source mutations are invalid. Callback invocation and ownership follow Table; trusted SQL is required because source text is not escaped.

Example:

users := query.FromRaw(query.Raw("SELECT id FROM users WHERE active = ?", true),
	func(statement *query.Builder) {
		statement.Where("id", ">", 10)
	})
_ = users

func Table

func Table(name string, configure ...BuilderFunc) Query

Table publishes a query targeting name after invoking configure in order. Callbacks share one draft and run synchronously once; supplied nil callbacks panic. Invalid names return an invalid query before callbacks are invoked. No callbacks, including an expanded empty slice, selects every column.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.Select("id", "name")
})
_ = users

func (Query) Build

func (query Query) Build() (Statement, error)

Build compiles the query into parameterized SQL for the ANSI dialect.

Example:

statement, err := query.Table("users").Build()
if err != nil {
	return err
}
_ = statement.SQL()

func (Query) BuildFor

func (query Query) BuildFor(dialect Dialect) (Statement, error)

BuildFor compiles the query into parameterized SQL for dialect. Raw expressions passed to *Raw methods use ? placeholders; BuildFor rewrites each ? outside quoted text and comments to the dialect placeholder, and ?? stands for a literal question mark. INSERT, UPDATE, and DELETE builders that carry clauses they cannot use, such as joins or limits, fail. SQLite rejects qualified INSERT, UPDATE, and conflict column names with ErrUnsupported; qualified table names and read-side references remain valid.

Example:

statement, err := query.Table("users").BuildFor(query.Postgres)
if err != nil {
	return err
}
_ = statement.SQL()

func (Query) Count

func (query Query) Count() Query

Count returns a SELECT COUNT(*) query for the rows the query selects, ignoring ordering, limit, offset, and row locks, so it is the companion of a paged query. A plain query keeps its table, joins, and filters and swaps the select list for COUNT(*). A grouped, DISTINCT, HAVING, or raw-sourced query is wrapped, as in SELECT COUNT(*) FROM (SELECT ...) AS "count", so the count matches the rows the original returns; its select list must then have unique column names. Only SELECT queries can be counted.

Example:

users := query.Table("users")
total := users.Count()
_ = total

func (Query) Derive

func (query Query) Derive(configure ...BuilderFunc) Query

Derive publishes an independent variant after applying configure to one draft. Callbacks run directly once, synchronously and in order; nil callbacks panic. No callbacks preserves the base, including its first error. Settings cannot repair inherited errors. Retained drafts cannot change either published value.

Example:

base := query.Table("users")
active := base.Derive(func(statement *query.Builder) {
	statement.Where("active", true)
})
_ = active

func (Query) Inspect

func (query Query) Inspect() Spec

Inspect returns a snapshot of the query's structure. Slices and pointers in the result are copies, so the query stays immutable.

Example:

users := query.Table("users")
_ = users.Inspect().Table

func (Query) IsSelect

func (query Query) IsSelect() bool

IsSelect reports whether the query compiles to a SELECT statement, as opposed to an INSERT, UPDATE, or DELETE.

Example:

users := query.Table("users")
_ = users.IsSelect()

func (Query) Seek

func (query Query) Seek(values []any, backward bool) Query

Seek adds a keyset pagination filter for the rows after (or, with backward, before) the row whose ordering values are values. It reads the query's orders, so call it after OrderBy and friends, and pass one value per ordering in the same sequence. For orders (a ASC, b DESC) it appends

WHERE (a > ? OR (a = ? AND b < ?))

to the existing filters, which are parenthesized when they contain OR. Backward reverses every ordering direction and comparison, returning rows in reverse order for paging toward the start; reverse the fetched rows to restore display order. Call Seek once per query. Like a scope, the filter is ANDed after the existing filters even when they contain OR. Values must not be nil, because NULL never compares; order by a non-null unique column last so rows have a total order. Only SELECT accepts it.

Example:

users := query.Table("users", func(statement *query.Builder) {
	statement.OrderBy("id")
})
next := users.Seek([]any{7}, false)
_ = next

type RawStatement

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

RawStatement is caller-written SQL with positional ? or named :name placeholders, constructed by Raw or RawNamed. Compilation checks nonblank text and bindings, not SQL syntax. Use it for statements the structured API cannot express. Never build SQL from user input; pass values as arguments. Owned argument containers are copied; objects in their slots remain shared.

Example:

definition := query.Raw("SELECT id FROM users WHERE active = ?", true)
statement, err := definition.BuildFor(query.Postgres)
if err != nil {
	return err
}
_ = statement.SQL()
Example

ExampleRawStatement compiles named bindings and demonstrates that Args owns its returned slice independently of both the raw definition and statement.

package main

import (
	"fmt"

	"arkoren.dev/foundation/query"
)

func main() {
	definition := query.RawNamed("SELECT :id", map[string]any{"id": 7})

	statement, err := definition.BuildFor(query.Postgres)

	if err != nil {
		fmt.Println(err)

		return
	}

	arguments := statement.Args()
	arguments[0] = 9

	fmt.Println(statement.SQL(), statement.Args())
	fmt.Println(query.Postgres.String(), query.Postgres.TransactionalDDL())
}
Output:
SELECT $1 [7]
postgres true

func Raw

func Raw(sql string, args ...any) RawStatement

Raw creates a RawStatement from sql and its arguments. Placeholders follow the raw expression rules of Builder.HavingRaw: ? outside quotes and comments becomes the dialect placeholder, and ?? writes a literal ?. The argument slice is copied; argument objects themselves remain shared.

Example:

statement, err := query.Raw("SELECT ?", 7).BuildFor(query.Postgres)
if err != nil {
	return err
}
_ = statement.SQL() // "SELECT $1"

func RawNamed

func RawNamed(sql string, args map[string]any) RawStatement

RawNamed creates a RawStatement whose placeholders are names: each :name outside quotes and comments becomes the dialect placeholder and binds args["name"]. A name is a letter or underscore followed by letters, digits, and underscores, and may repeat, binding its value again each time. "::" is kept as written, so PostgreSQL casts such as id::text work. A ? is not a placeholder here and is kept as written. Quotes and comments follow the rules of Raw. A name without an entry in args fails at Build with ErrInvalidQuery; entries that no name uses are ignored, so the map returned by Values can be passed whole. The map is copied.

Example:

statement := query.RawNamed("UPDATE users SET name = :name WHERE id = :id",
	map[string]any{"name": "Ada", "id": 7})

func (RawStatement) Build

func (statement RawStatement) Build() (Statement, error)

Build compiles the statement for the ANSI dialect. Binding failures have the same ErrInvalidQuery classification as BuildFor. It does not validate the SQL syntax or execute the statement.

Example:

statement, err := query.Raw("SELECT ?", 7).Build()
if err != nil {
	return err
}
_ = statement.Args()

func (RawStatement) BuildFor

func (statement RawStatement) BuildFor(dialect Dialect) (Statement, error)

BuildFor rewrites the placeholders for dialect. It fails with ErrInvalidQuery when the SQL is blank, the positional placeholder count differs from its argument count, or a named placeholder has no argument. Unused entries in a named argument map are ignored, as described by RawNamed. It leaves the definition unchanged and does not validate SQL syntax.

Example:

definition := query.RawNamed("SELECT :id", map[string]any{"id": 7})
statement, err := definition.BuildFor(query.Postgres)
if err != nil {
	return err
}
_ = statement.SQL() // "SELECT $1"

type Scope

type Scope = PredicateBuilderFunc

Scope is a reusable function that adds predicates to a query.

Example:

active := func(predicate *query.PredicateBuilder) {
	predicate.Where("active", true)
}
_ = query.Table("users", func(statement *query.Builder) {
	statement.Scope(active)
})

type Spec

type Spec struct {
	// Kind is the statement the query compiles to.
	//
	// Example:
	//
	//	_ = query.Table("users").Inspect().Kind == query.KindSelect
	Kind Kind

	// Table is the source table as written, such as "public.users". It is
	// empty when the source is raw SQL.
	//
	// Example:
	//
	//	_ = query.Table("public.users").Inspect().Table // "public.users"
	Table string

	// Orders lists the orderings in the order they apply; nil when none.
	//
	// Example:
	//
	//	users := query.Table("users", func(statement *query.Builder) {
	//		statement.OrderBy("id")
	//	})
	//	_ = users.Inspect().Orders[0].Column
	Orders []Order

	// Limit is a copy of the row limit; nil when unset.
	// Explicit zero requests no rows and differs from an unset limit.
	//
	// Example:
	//
	//	spec := query.Table("users", func(statement *query.Builder) {
	//		statement.Limit(20)
	//	}).Inspect()
	//	if spec.Limit != nil {
	//		_ = *spec.Limit // 20
	//	}
	Limit *int

	// Offset is a copy of the row offset; nil when unset.
	// Explicit zero remains present even though it skips no rows.
	//
	// Example:
	//
	//	spec := query.Table("users", func(statement *query.Builder) {
	//		statement.Offset(0)
	//	}).Inspect()
	//	if spec.Offset != nil {
	//		_ = *spec.Offset // 0
	//	}
	Offset *int

	// Distinct reports whether the select is DISTINCT.
	//
	// Example:
	//
	//	users := query.Table("users", func(statement *query.Builder) {
	//		statement.Distinct()
	//	})
	//	_ = users.Inspect().Distinct // true
	Distinct bool

	// Grouped reports whether the query has GROUP BY columns.
	//
	// Example:
	//
	//	users := query.Table("users", func(statement *query.Builder) {
	//		statement.Select("role")
	//		statement.GroupBy("role")
	//	})
	//	_ = users.Inspect().Grouped // true
	Grouped bool

	// Having reports whether the query has HAVING filters.
	//
	// Example:
	//
	//	users := query.Table("users", func(statement *query.Builder) {
	//		statement.SelectRaw("COUNT(*)")
	//		statement.HavingRaw("COUNT(*) > ?", 5)
	//	})
	//	_ = users.Inspect().Having // true
	Having bool

	// Raw reports whether the source is a [FromRaw] subquery.
	// Query-sourced wrappers created by Count do not set this flag.
	//
	// Example:
	//
	//	users := query.FromRaw(query.Raw("SELECT id FROM users"))
	//	_ = users.Inspect().Raw // true
	Raw bool

	// Returning reports whether an INSERT, UPDATE, or DELETE has a
	// RETURNING clause and so yields rows.
	//
	// Example:
	//
	//	users := query.Table("users", func(statement *query.Builder) {
	//		statement.Insert(map[string]any{"name": "Ada"})
	//		statement.Returning("id")
	//	})
	//	_ = users.Inspect().Returning // true; compile for PostgreSQL or SQLite.
	Returning bool
}

Spec is a read-only snapshot of a Query, for callers such as pagination helpers that need to know what a query selects without compiling it. Changing a Spec never affects the query it came from. Inspection reports declarations even if later compilation rejects them; it does not validate the query or resolve a database schema.

Example:

spec := query.Table("users", func(statement *query.Builder) {
	statement.OrderBy("id")
	statement.Limit(20)
}).Inspect()
_ = spec.Table
_ = spec.Orders
Example

ExampleSpec inspects paging and ordering without compiling SQL, then shows that changing the snapshot's pointer does not change the published query.

package main

import (
	"fmt"

	"arkoren.dev/foundation/query"
)

func main() {
	users := query.Table("users", func(statement *query.Builder) {
		statement.OrderByDesc("users.id")
		statement.Limit(20)
	})

	spec := users.Inspect()

	fmt.Println(spec.Kind.String(), spec.Table, spec.Orders[0].Column, spec.Orders[0].Desc)

	*spec.Limit = 0

	fmt.Println(*users.Inspect().Limit)
}
Output:
SELECT users users.id true
20

type Statement

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

Statement is compiled SQL and its ordered positional arguments. Obtain it through Query or RawStatement compilation. SQL returns immutable text; Args copies its outer slice, not the objects stored in argument slots. The zero Statement contains empty SQL and no arguments.

Example:

statement, err := query.Raw("SELECT ?", 7).BuildFor(query.Postgres)
if err != nil {
	return err
}
_ = statement.SQL()  // "SELECT $1"
_ = statement.Args() // []any{7}

func (Statement) Args

func (statement Statement) Args() []any

Args returns a copy of the ordered positional arguments. Editing the returned slice cannot change the statement. Argument objects themselves remain shared; no arguments returns nil.

Example:

statement, err := query.Raw("SELECT ?", 7).Build()
if err != nil {
	return err
}
arguments := statement.Args()
arguments[0] = 9 // A later statement.Args() still contains 7.

func (Statement) SQL

func (statement Statement) SQL() string

SQL returns the compiled SQL text. It contains placeholders rather than interpolated argument values; pass Args separately to the database. The zero Statement returns an empty string.

Example:

statement, err := query.Table("users").Build()
if err != nil {
	return err
}
_ = statement.SQL() // SELECT * FROM "users"

type Statementer

type Statementer interface {
	// BuildFor compiles a published definition for dialect, returning SQL and
	// ordered arguments or the definition's validation or dialect error.
	//
	// Example:
	//
	//	var definition query.Statementer = query.Table("users")
	//	statement, err := definition.BuildFor(query.Postgres)
	//	if err != nil {
	//		return err
	//	}
	//	_ = statement.Args()
	BuildFor(dialect Dialect) (Statement, error)
}

Statementer is implemented by everything that compiles to exactly one Statement: the DML Query, RawStatement, and each statement of a schema operation (arkoren.dev/foundation/query/schema.Operation). Mutable declaration drafts do not implement it; execution boundaries accept completed definitions and compile them for the database's actual dialect.

Example:

var definition query.Statementer = query.Table("users")
statement, err := definition.BuildFor(query.Postgres)
if err != nil {
	return err
}
_ = statement.SQL()

type ValuesOption

type ValuesOption func(*valuesConfig)

ValuesOption filters columns for one Values call. Obtain options from Only and Except; reusable options retain copied names, not struct field values. A supplied nil option panics when Values invokes it.

Example:

options := []query.ValuesOption{query.Only("name"), query.Except("id")}
values := query.Values(struct{ Name string }{Name: "Ada"}, options...)
_ = values["name"]

func Except

func Except(columns ...string) ValuesOption

Except drops the named columns. Names that are not columns of the struct are ignored. Several Except options add up, and Except applies after Only. With no columns, including an expanded nil or empty slice, it drops nothing.

Except copies the column-name slice when the option is created. Later changes to the caller's slice do not affect the option, which can be reused.

Example:

user := struct {
	ID   int
	Name string
}{ID: 7, Name: "Ada"}
values := query.Values(user, query.Except("id"))
_ = values["name"] // "Ada"; "id" is absent.

func Only

func Only(columns ...string) ValuesOption

Only keeps just the named columns. Names that are not columns of the struct are ignored. Several Only options add up.

With no columns, including an expanded nil or empty slice, Only adds no restriction: by itself it keeps every column and does not clear other Only options. Except still applies after Only.

Only copies the column-name slice when the option is created. Later changes to the caller's slice do not affect the option, which can be reused.

Example:

user := struct {
	ID   int
	Name string
}{ID: 7, Name: "Ada"}
values := query.Values(user, query.Only("name"))
_ = values["name"] // "Ada"; "id" is absent.

Directories

Path Synopsis
Package schema builds DDL: creating, altering, renaming, and dropping tables, with their columns, keys, indexes, and foreign keys, for the dialects of arkoren.dev/foundation/query.
Package schema builds DDL: creating, altering, renaming, and dropping tables, with their columns, keys, indexes, and foreign keys, for the dialects of arkoren.dev/foundation/query.

Jump to

Keyboard shortcuts

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