Skip to main content
Version: 0.7.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 (...).
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.
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, on string)Adds inner join.
LeftJoin(table, on string)Adds left join.
RightJoin(table, on string)Adds right join.
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.

WhereIn

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

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

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
PostgreSQL::jsonb->>
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", "users.id = orders.user_id").
Where("status", "=", "paid").
Limit(100).
List()

table is validated and quoted. on is a raw SQL fragment and should be static. 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", "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.