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
| Method | Purpose |
|---|---|
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:
| Operator | Notes |
|---|---|
=, !=, <> | Equality and inequality. |
<, <=, >, >= | Comparisons. |
LIKE, NOT LIKE | Pattern matching. |
IN, NOT IN | Usually created through WhereIn. |
BETWEEN, NOT BETWEEN | Usually created through WhereBetween. |
IS NULL, IS NOT NULL | No 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:
| Dialect | Function or operator |
|---|---|
| PostgreSQL | ::jsonb->> |
| MySQL | JSON_EXTRACT |
| MariaDB | JSON_VALUE |
| SQLite | JSON_EXTRACT |
| SQL Server | JSON_VALUE |
| Oracle | JSON_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:
| Relation | Tag |
|---|---|
| Has one | rel:"has_one" |
| Has many | rel:"has_many" |
| Belongs to | rel:"belongs_to" |
| Many to many | rel:"many_to_many" m2m:"join_table:this_fk:ref_fk" |
| Polymorphic | rel:"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.