Skip to main content
Version: 1.13.0

Query Builder API Reference

Query[T] is Quark's immutable fluent builder for model T. Builder methods return a cloned query; execution methods are covered in Querying and write methods in CRUD.

q := quark.For[User](ctx, client)

Core Builder Methods​

MethodPurpose
Where(column, operator string, value any)Adds an AND predicate.
WhereIn(column string, values []any)Adds column IN (...).
WhereInOf[T, V](q *Query[T], column string, values []V)Package-level typed-slice form of WhereIn (no []any conversion loop).
WhereBetween(column string, start, end any)Adds column BETWEEN ? AND ?.
WhereNot(column, operator string, value any)Adds NOT (column operator value).
Or(fn func(*Query[T]) *Query[T])Adds a grouped OR branch.
WhereJSON(column, path, operator string, value any)Adds a dialect JSON extraction predicate.
WhereSubquery(column, operator, subquery string)Adds a raw subquery predicate when raw SQL is enabled.
OrderBy(column, direction string)Adds ORDER BY. Direction is ASC/DESC (case-insensitive, "" = ASC); anything else fails with ErrInvalidQuery.
Limit(n int)Sets result limit.
Offset(n int)Sets offset.
Select(columns ...string)Selects model-table columns.
Distinct()Adds SELECT DISTINCT.
GroupBy(columns ...string)Adds GROUP BY.
Having(column, operator string, value any)Adds HAVING.
Join(table string) *JoinBuilder[T]Inner join; complete with .On(left, op, right) or .OnRaw(clause).
LeftJoin(table string) *JoinBuilder[T]Left join; same completion.
RightJoin(table string) *JoinBuilder[T]Right join; same completion.
Preload(relations ...string)Eager-loads relations by Go field name.
Apply(scopes ...Scope[T])Applies reusable scope functions.
Cache(ttl time.Duration, tags ...string)Enables per-query result caching.
Unscoped()Includes soft-deleted rows.

Where​

users, err := quark.For[User](ctx, client).
Where("active", "=", true).
Where("age", ">=", 18).
Limit(100).
List()

Supported operators:

OperatorNotes
=, !=, <>Equality and inequality.
<, <=, >, >=Comparisons.
LIKE, NOT LIKEPattern matching.
IN, NOT INUsually created through WhereIn.
BETWEEN, NOT BETWEENUsually created through WhereBetween.
IS NULL, IS NOT NULLNo bound value is emitted.

Column identifiers and operators are validated by SQLGuard. Values are passed as driver parameters.

When the query has at least one join, Where / OrderBy / GroupBy / Select / Col also accept a one-level qualified name (table.column); each segment is validated and quoted separately. See Qualified column names under a join.

WhereIn​

users, err := quark.For[User](ctx, client).
WhereIn("id", []any{1, 2, 3}).
List()

Avoid empty slices. Most SQL engines reject IN ().

For a typed slice, WhereInOf performs the []any conversion internally (a package-level function — Go methods cannot add a second type parameter):

ids := []int64{1, 2, 3}
users, err := quark.WhereInOf(quark.For[User](ctx, client), "id", ids).List()

WhereBetween​

orders, err := quark.For[Order](ctx, client).
WhereBetween("created_at", start, end).
List()

WhereNot​

users, err := quark.For[User](ctx, client).
WhereNot("banned", "=", true).
List()

Or​

users, err := quark.For[User](ctx, client).
Where("active", "=", true).
Or(func(q *quark.Query[User]) *quark.Query[User] {
return q.Where("role", "=", "admin").
Where("verified", "=", true)
}).
List()

The callback receives a blank query used only to collect the OR group. Conditions inside that callback are joined with AND.

WhereJSON​

users, err := quark.For[User](ctx, client).
WhereJSON("metadata", "plan", "=", "enterprise").
List()

The JSON expression is generated by the active dialect:

DialectFunction or operator
PostgreSQLjsonb_extract_path_text
MySQLJSON_EXTRACT
MariaDBJSON_VALUE
SQLiteJSON_EXTRACT
SQL ServerJSON_VALUE
OracleJSON_VALUE

WhereSubquery​

limits := quark.DefaultLimits()
limits.AllowRawQueries = true

client, err := quark.New("postgres", dsn,
quark.WithLimits(limits),
)

users, err := quark.For[User](ctx, client).
WhereSubquery("id", "IN", "SELECT user_id FROM orders WHERE total > 100").
List()

If AllowRawQueries is false, the returned query stores ErrInvalidQuery and execution fails.

Ordering and Pagination​

users, err := quark.For[User](ctx, client).
OrderBy("created_at", "DESC").
OrderBy("id", "ASC").
Limit(20).
Offset(40).
List()

OrderBy treats any direction other than DESC or desc as ascending.

SQL Server and Oracle require an ORDER BY when using offset/fetch. If you use Limit or Offset without an order, Quark adds a fallback order.

Select and Distinct​

users, err := quark.For[User](ctx, client).
Select("id", "email", "name").
Distinct().
Limit(100).
List()

Select validates each argument as a simple SQL identifier. It is not a raw SQL expression API. Use aggregate helpers, views, or RawQuery for expressions, aliases, and dotted identifiers.

GroupBy and Having​

rows, err := quark.For[Order](ctx, client).
Select("status").
GroupBy("status").
Having("status", "!=", "cancelled").
List()

Grouped aggregate projections such as COUNT(*) AS count are not currently accepted by Select because SQLGuard validates simple identifiers. Use a database view or controlled raw SQL for those reports.

Joins​

orders, err := quark.For[Order](ctx, client).
Join("users").On("users.id", "=", "orders.user_id").
Where("status", "=", "paid").
Limit(100).
List()

table is validated and quoted. .On(left, op, right) is the structured form (both identifiers validated by SQLGuard); .OnRaw(clause) is the escape hatch for the identifier-only grammar ValidateJoinOn accepts. Where and Select still accept simple identifiers only; they do not accept users.active or orders.total AS amount.

orders, err := quark.For[Order](ctx, client).
LeftJoin("payments").On("payments.order_id", "=", "orders.id").
List()

RightJoin is available, but SQLite does not support every right-join shape across versions. Prefer LeftJoin when portability matters.

Preload​

users, err := quark.For[User](ctx, client).
Preload("Profile", "Posts", "Roles").
Limit(50).
List()

Use the Go relation field name. Preload("Posts") is correct for a field named Posts; Preload("posts") is not.

Supported relation types:

RelationTag
Has onerel:"has_one"
Has manyrel:"has_many"
Belongs torel:"belongs_to"
Many to manyrel:"many_to_many" m2m:"join_table:this_fk:ref_fk"
Polymorphicrel:"polymorphic" polymorphic:"type_col:type_value" join:"parent_id"

Apply and Scope​

type Scope[T any] func(*quark.Query[T]) *quark.Query[T]
var ActiveUsers = quark.Scope[User](func(q *quark.Query[User]) *quark.Query[User] {
return q.Where("active", "=", true)
})

users, err := quark.For[User](ctx, client).
Apply(ActiveUsers).
Limit(100).
List()

Scopes are applied in the order passed to Apply.

Cache​

users, err := quark.For[User](ctx, client).
Where("active", "=", true).
Cache(5*time.Minute, "users", "users:active").
List()

If no tags are supplied, Quark tags the entry with the table name. If custom tags are supplied, include the table tag yourself when you want writes to invalidate the entry automatically.

Unscoped​

allUsers, err := quark.For[User](ctx, client).
Unscoped().
List()

Unscoped disables the automatic deleted_at IS NULL predicate for models that have a deleted_at column.

Immutability​

base := quark.For[User](ctx, client).Where("active", "=", true)

admins := base.Where("role", "=", "admin")
editors := base.Where("role", "=", "editor")

base remains only active = true. This clone pattern makes query composition predictable and helps avoid state leaks across request paths.