Quarry Docs
GitHub
Guide

Dynamic Filters

Quarry optional predicates disappear when their values are absent, so one query can handle many request shapes without special-case branching.

Quarry logo

Optional filters

Optional helpers treat empty inputs as omissions. That keeps the query shape stable while letting callers pass zero values, nil pointers, or empty slices.

q := qq.Select("*").From("users").Where(
    quarry.Eq("tenant_id", 42),
    quarry.OptionalEq("status", nil),
    quarry.OptionalILike("email", ""),
)
SELECT * FROM users WHERE tenant_id = $1
[]any{42}
status := "active"
q := qq.Select("*").From("users").Where(
    quarry.Eq("tenant_id", 42),
    quarry.OptionalEq("status", &status),
    quarry.OptionalILike("email", "%bob%"),
)
SELECT * FROM users WHERE tenant_id = $1 AND status = $2 AND email ILIKE $3
[]any{42, "active", "%bob%"}

Grouped predicates

q := qq.Select("*").From("users").Where(
    quarry.Eq("tenant_id", 42),
    quarry.Or(
        quarry.OptionalILike("email", "%bob%"),
        quarry.OptionalILike("name", "%bob%"),
    ),
    quarry.OptionalEq("status", nil),
)
Grouping rule Empty children disappear. Nested single-child groups collapse. Nested multi-child groups keep parentheses for correctness.

Sorting and paging

Safe sorting

OrderBySafe and OrderBySafeDefault only accept fragments from a trusted lookup table.

Paging helpers

Page, LimitDefault, and OffsetDefault keep request pagination readable.

q := qq.Select("id", "email").
    From("users").
    OrderBySafeDefault("newest", quarry.SortMap{
        "newest": "created_at DESC",
        "email":  "email ASC",
    }, "newest").
    Page(1, 50)

Partial updates

q := qq.Update("users").
    SetOptional("name", params.Name).
    SetOptional("email", params.Email).
    SetIf(params.Enabled != nil, "enabled", *params.Enabled).
    Where(quarry.Eq("id", params.ID))
Truthful behavior If every SET value disappears, the builder returns an error instead of rendering invalid SQL.

Raw SQL escape hatch

Raw SQL stays available when the query is clearer by hand. Values still bind safely.

q := qq.Select(quarry.Raw("COUNT(*) FILTER (WHERE status = ?)", "active")).
    From("users").
    Where(quarry.Raw("created_at >= ?", since))