Skip to content

Composition

Compose compile-time SQL templates and extend them at call sites.

Updated View as Markdown

A function returning QueryBuilder[T] defines an unevaluated read template. The compiler splices it into each consumer and renders one prepared statement. There is no runtime SQL string or runtime QueryBuilder object.

fn active_bookings(): QueryBuilder[Booking] {
    return FROM Booking
        WHERE active
        ORDER BY created_at DESC
}

fn recent(): []Booking ! QueryError {
    return active_bookings()
        WHERE created_at >= cutoff
        LIMIT 20?
}

This is static composition. The producer is checked as a query template, each call is specialized at its use site, and only the final combined query reaches the runtime. It cannot be stored, returned as an opaque runtime value, or assembled from strings.

Producers

The producer query must be an unterminated FROM read whose row type agrees with QueryBuilder[T]. Terminal LIMIT, FOR UPDATE, AS FRAME, ONE FROM, and producer-side projections are rejected because they would decide a final result shape before the consumer is known.

Predicates and stable ordering are useful producer responsibilities:

fn visible(): QueryBuilder[Booking] {
    return FROM Booking
        WHERE deleted_at == None
        ORDER BY created_at DESC
}

Keep a builder focused on one reusable policy such as tenancy, visibility, or base joins. A builder does not bypass model visibility or datasource routing.

Parameters

Parameterized builders are implemented:

fn priced(minimum: int, enabled: bool): QueryBuilder[Booking] {
    return FROM Booking
        WHERE price >= minimum if enabled
}

fn affordable(maximum: int): []Booking ! QueryError {
    return priced(10, true)
        WHERE price <= maximum?
}

Arguments are substituted by symbol identity, so a same-named local at the consumer does not capture the producer parameter.

The arguments are ordinary Atoll expressions. Their values become bound statement parameters when the combined query executes; they are not rendered as SQL text. Each call site receives its own substitution, so two consumers can specialize the same builder differently without sharing mutable state.

Execution

A bare builder call followed by ? executes the template:

rows := active_bookings()?

Adding clauses continues the query until a terminal or ? consumes it. Composition can extend predicates, joins, ordering, grouping, paging, and supported projections on the consumer side.

Clauses are merged structurally. Producer and consumer predicates both apply; consumer ordering and paging follow the same ordering rules as a query written in one place. The compiler validates the final plan after expansion, so an otherwise valid producer can still fail at a consumer whose added clauses make the complete plan ambiguous or unsupported.

fn by_customer(wanted_customer: int): QueryBuilder[Booking] {
    return FROM Booking
        WHERE customer_id == wanted_customer
}

fn recent_open(customer_id: int): []Booking ! QueryError {
    return by_customer(customer_id)
        WHERE status == Open
        ORDER BY created_at DESC
        LIMIT 10?
}

Result typing

The builder’s T must match the model read by its producer. Consumption returns the same shapes as a direct read. A matching checked struct context can also trigger single-row aggregate collapse; see Aggregates.

The builder declaration itself does not execute, suspend, or produce QueryError. Those properties appear at the consuming expression, which has the same Result and suspension behavior as a direct query.

Restrictions

Composition inherits every standalone query gate. Guarded joins, conditional assignments, unsupported engine functions, and query shapes without a runtime carrier still diagnose at compilation. A template cannot conceal an unsafe or unrenderable fragment.

Recursive builder calls and dynamic selection among builders are not a runtime query-construction mechanism. When an entire query must differ by storage engine, use a when block; when only values differ, pass parameters.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close