query

Macro query 

Source
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.

OperatorExpansion
==.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 FILTER after ORDER BY or LIMIT before OFFSET causes a parse error since the clauses are parsed in fixed order.
  • Missing BY after ORDER: writing ORDER .name instead of ORDER BY .name produces "expected 'BY' after 'ORDER'".
  • Invalid pagination value: LIMIT and OFFSET require an integer literal, #variable, or #(expression).