Query composition
RAQ treats every query as a relation that can become the input to another relational operation. That is what composition means here: build a larger query by wrapping or combining smaller queries.
Nothing runs while the query is being composed. The result is one serializable tree that can be inspected, rewritten, validated, and finally executed.
Start with one relation
relation creates the smallest useful query:
import {
limit,
order,
project,
relation,
where,
} from "@raquery/query";
const products = relation"products";It means “the public relation named products.” It does not fetch that relation.
Every operation below accepts a relation and returns another relation, so each result can be passed directly to the next operation:
const inStock = whereproducts, "stock > 0";
const affordable = whereinStock, "price < 200";
const visibleFields = projectaffordable,"id", "name", "price";
const cheapestFirst = ordervisibleFields, "price asc";
const firstPage = limitcheapestFirst, 20;The names make the stages easier to discuss, but they are not materialized datasets. firstPage is a single query tree:
limit 20
└─ order price asc
└─ project id, name, price
└─ where price < 200
└─ where stock > 0
└─ relation productsThe same composition can be written inline:
const firstPage = limit
order
project
where
whererelation"products", "stock > 0",
"price < 200"
,
"id", "name", "price"
,
"price asc"
,
20
;These forms produce the same tree. Prefer named stages when they make the intent clearer.
Composition can branch
Some operations combine two relations instead of wrapping one. A join, union, difference, or intersection is still a relation, so composition continues after the branches meet:
import { join, project, relation, where } from "@raquery/query";
const stockedProducts = whererelation"products", "stock > 0";
const productCategories = join
stockedProducts,
relation"categories",
"products.category_id == categories.category_id"
;
const listing = projectproductCategories,"name", "category_name", "price";The relation-qualified names in the join condition identify which input owns each attribute. The predicate is parsed into expression nodes; it is not pasted into SQL.
Client and server compose the same kind of value
Composition does not stop at the network boundary. Suppose the client sends:
project name, price
└─ where price < 200
└─ relation productsThe server catalog can define public products as a projection over physical tables. Catalog binding replaces the public leaf with that server-owned tree:
project name, price client
└─ where price < 200 client
└─ project public product fields server catalog
└─ join product_id == inventory_id server catalog
├─ physical products
└─ physical inventoryAuthorization can then add another relational operation—for example, a trusted store filter around the physical inventory source. The client query, catalog definition, and policy become one tree before validation and SQL compilation.
This is different from concatenating SQL strings or running several queries and combining their arrays. Each layer contributes structured relational nodes, and the final compiler sees the whole query.
The tree is the contract
Builder calls return plain serializable values. For example:
whererelation"products", "price > 100"becomes, in abbreviated form:
{
"type": "select",
"input": { "type": "source", "name": "products" },
"predicate": {
"type": "gt",
"left": { "type": "attr", "name": "price" },
"right": { "type": "value", "value": 100 }
}
}Because composition produces data rather than executable code, RAQ can validate source names and attributes, bind values as SQL parameters, apply server-owned rewrites, evaluate supported trees locally, and compare trees for reuse.
Catalogs define the boundary
Composition is deliberately not unrestricted database access. A catalog maps a public relation name to a server-owned relational expression. Clients may compose over the public shape, while physical tables, tenant constraints, and private fields remain under server control.
See Catalogs for the complete binding and authorization path, or Query language for the available relational operations.
Conservative reuse
RAQ may derive one materialized query from another only when it can prove the relationship from their trees. Unsupported or ambiguous cases return unknown and fall back to fetching. Composition makes this analysis possible because the operations and their order remain explicit.