query!() { /* proc-macro */ }Expand description
Builds a query using the Toasty query language. The macro expands into
the equivalent method-chain calls on the query builder API. It does
not execute the query — chain .exec(&mut db).await? on the result to run
it.
§Syntax
query!(Source [FILTER expr] [ORDER BY .field ASC|DESC] [OFFSET n] [LIMIT n])Source is a model type path (e.g., User). All clauses are optional and
can appear in any combination, but must follow the order shown above when
present. All keywords are case-insensitive: FILTER, filter, and Filter
all work.
§Basic queries
With no clauses, query! returns all records of the given model.
// Returns all users — expands to User::all()
let _ = toasty::query!(User);§Filter expressions
The FILTER clause accepts an expression built from field comparisons,
boolean operators, and external references.
§Comparison operators
Dot-prefixed field paths (.name, .age) refer to fields on the source
model. The right-hand side is a literal or external reference.
| Operator | Expansion |
|---|---|
== | .eq(val) |
!= | .ne(val) |
> | .gt(val) |
>= | .ge(val) |
< | .lt(val) |
<= | .le(val) |
// Equality — expands to User::filter(User::fields().name().eq("Alice"))
let _ = toasty::query!(User FILTER .name == "Alice");
// Not equal
let _ = toasty::query!(User FILTER .name != "Bob");
// Greater than
let _ = toasty::query!(User FILTER .age > 18);
// Greater than or equal
let _ = toasty::query!(User FILTER .age >= 21);
// Less than
let _ = toasty::query!(User FILTER .age < 65);
// Less than or equal
let _ = toasty::query!(User FILTER .age <= 99);§Boolean operators
AND, OR, and NOT combine filter expressions. Precedence follows
standard boolean logic: NOT binds tightest, then AND, then OR.
// AND — both conditions must match
let _ = toasty::query!(User FILTER .name == "Alice" AND .age > 18);
// OR — either condition matches
let _ = toasty::query!(User FILTER .name == "Alice" OR .name == "Bob");
// NOT — negates the following expression
let _ = toasty::query!(User FILTER NOT .active == true);
// Combining all three
let _ = toasty::query!(User FILTER NOT .active == true AND (.name == "Alice" OR .age >= 21));§Operator precedence
Without parentheses, NOT binds tightest, then AND, then OR. Use
parentheses to override.
// Without parens: parsed as (.name == "A" AND .age > 0) OR .active == false
let _ = toasty::query!(User FILTER .name == "A" AND .age > 0 OR .active == false);
// With parens: forces OR to bind first
let _ = toasty::query!(User FILTER .name == "A" AND (.age > 0 OR .active == false));§Boolean and integer literals
Boolean fields can be compared against true and false literals.
Integer literals work as expected.
let _ = toasty::query!(User FILTER .active == true);
let _ = toasty::query!(User FILTER .active == false);
let _ = toasty::query!(User FILTER .age == 42);§Referencing surrounding code
#ident pulls a variable from the surrounding scope. #(expr) embeds an
arbitrary Rust expression.
// Variable reference — expands to User::filter(User::fields().name().eq(name))
let name = "Carl";
let _ = toasty::query!(User FILTER .name == #name);
// Expression reference
fn min_age() -> i64 { 18 }
let _ = toasty::query!(User FILTER .age > #(min_age()));§Dot-prefixed field paths
A leading . starts a field path rooted at the source model’s fields()
method. Chained dots navigate multi-segment paths.
// .name expands to User::fields().name()
let _ = toasty::query!(User FILTER .name == "Alice");
// Multiple fields in a single expression
let _ = toasty::query!(User FILTER .id == 1 AND .name == "X" AND .age > 0);§ORDER BY
Sort results by a field in ascending (ASC) or descending (DESC) order.
If no direction is specified, ascending is the default.
// Ascending order (explicit)
let _ = toasty::query!(User ORDER BY .name ASC);
// Descending order
let _ = toasty::query!(User ORDER BY .age DESC);
// Combined with filter
let _ = toasty::query!(User FILTER .active == true ORDER BY .name ASC);§LIMIT and OFFSET
LIMIT restricts the number of returned records. OFFSET skips a number
of records before returning. Both accept integer literals, #ident
variables, and #(expr) expressions.
// Return at most 10 records
let _ = toasty::query!(User LIMIT 10);
// Skip 20, then return 10
let _ = toasty::query!(User OFFSET 20 LIMIT 10);
// Variable pagination
let page_size = 25usize;
let _ = toasty::query!(User LIMIT #page_size);
// Expression pagination
let _ = toasty::query!(User LIMIT #(5 + 5));§Combining clauses
All clauses can be combined. When present, they must appear in this order:
FILTER, ORDER BY, OFFSET, LIMIT.
let _ = toasty::query!(User FILTER .active == true ORDER BY .name ASC LIMIT 10);
let _ = toasty::query!(User FILTER .age > 18 ORDER BY .age DESC OFFSET 0 LIMIT 50);§Case-insensitive keywords
All keywords — FILTER, AND, OR, NOT, ORDER, BY, ASC, DESC,
OFFSET, LIMIT — are matched case-insensitively. Any casing works.
// These are all equivalent
let _ = toasty::query!(User FILTER .name == "A");
let _ = toasty::query!(User filter .name == "A");
let _ = toasty::query!(User Filter .name == "A");§Expansion details
The macro translates each syntactic element into method-chain calls on the query builder.
§No filter
query!(User) → User::all()§Filter
query!(User FILTER .name == "A")
→ User::filter(User::fields().name().eq("A"))§Logical operators
query!(User FILTER .a == 1 AND .b == 2)
→ User::filter(User::fields().a().eq(1).and(User::fields().b().eq(2)))
query!(User FILTER .a == 1 OR .b == 2)
→ User::filter(User::fields().a().eq(1).or(User::fields().b().eq(2)))
query!(User FILTER NOT .a == 1)
→ User::filter((User::fields().a().eq(1)).not())§ORDER BY
query!(User ORDER BY .name ASC)
→ { let mut q = User::all(); q = q.order_by(User::fields().name().asc()); q }§LIMIT / OFFSET
query!(User LIMIT 10)
→ { let mut q = User::all(); q = q.limit(10); q }
query!(User OFFSET 5 LIMIT 10)
→ { let mut q = User::all(); q = q.limit(10); q = q.offset(5); q }Note: in the expansion, limit is called before offset because the
API requires it.
§External references
let x = "Carl";
query!(User FILTER .name == #x)
→ User::filter(User::fields().name().eq(x))
query!(User FILTER .age > #(compute()))
→ User::filter(User::fields().age().gt(compute()))§Errors
The macro produces compile-time errors for:
- Missing model path: the first token must be a valid type path.
- Unknown fields: dot-prefixed paths that don’t match a field on the
model produce a type error from the generated
fields()method. - Type mismatches: comparing a field to a value of the wrong type
produces a standard Rust type error (e.g.,
.age == "not a number"). - Unexpected tokens: tokens after the last recognized clause cause
"unexpected tokens after query". - Invalid clause order: placing
FILTERafterORDER BYorLIMITbeforeOFFSETcauses a parse error since the clauses are parsed in fixed order. - Missing
BYafterORDER: writingORDER .nameinstead ofORDER BY .nameproduces"expected 'BY' after 'ORDER'". - Invalid pagination value:
LIMITandOFFSETrequire an integer literal,#variable, or#(expression).