Queries
riverbase_core::query serves read models. A resource has a logical interface (what clients
see) and a physical binding (where the data lives). The engine lowers one onto the other and
adds server policy.
query_resource!
riverbase_core::query_resource! {
TodoQueryResource name "todo" {
meta {
title: "Todo",
description: "Todo items read model.",
default_order: [created.desc, id.desc],
scope: none,
}
fields {
field id { preset: Uuid, label: "Todo ID", identifier }
field title { preset: String, label: "Title" }
field done { preset: Boolean, label: "Done" }
field created { preset: Datetime, label: "Created" }
field version { preset: Integer, label: "Version", no_sort }
}
binding source "todo_items" { id => "_id", created => "_created" }
errors {
list: ("TDO-002", "Failed to list todo items."),
item: ("TDO-003", "Failed to fetch todo item by id."),
}
policy: public("internal catalog listing", "SEC-01"),
// scope_policy(ctx, url_scope) { … } optional server row filter
// tenant_scope: exempt("why", "sign-off"), optional opt-out of the _tenant filter
}
}
The macro also generates TodoQueryResourceKey, a typed resource key that entities and stores can
reference instead of a string.
Field flags
| Flag | Meaning |
|---|---|
identifier | The key for .item/{id} |
sortable / no_sort | Override the preset's default sortability |
label: "…" | Display label in .meta |
A non-empty meta { order_fields: [a, b] } is the only sort allowlist and supersedes field flags.
Policy
| Declaration | Behavior |
|---|---|
policy: required (default) | scope_policy must return a constraint, or the request is denied |
policy: public("reason", "sign-off") | Unrestricted on purpose |
policy: optional | Legacy; None is unrestricted |
scope_policy(ctx, url_scope) { … } returns RiverbaseResult<Option<Expr>> using physical
column names. See Permissions.
report_resource!
Like query_resource!, plus filters { … }, params { param name { type: Date, label: "…", required } },
and a handler (_self, ctx, session, request, query) { … } returning ReportOutput. Reports are
served at POST …/{report}.rept.
query_engine!
riverbase_core::query_engine! {
engine TodoQueryEngine {
meta { query_pool_size: 4 }
component: {},
}
resources { TodoQueryResource }
reports { TodoSummaryReportResource }
}
query_pool_size (alias actor_pool_size) is the semaphore size for concurrent reads.
The wire format
List parameters
| Param | Default | Meaning |
|---|---|---|
limit | 25 | Page size, capped by query_max_limit (500) |
page | 1 | 1-based page |
sort | resource default_order | CSV of field.asc / field.desc |
include / exclude | all fields | CSV of fields to return or omit |
query | none | URL-encoded JSON filter (below) |
qbase | none | A base filter ANDed with query; the client can only narrow it |
text | none | Full-text term, when the resource allows it |
count | true | false skips the exact total |
Filter statements
query is a JSON object whose keys are statements in the form field.operator, and whose
values are operands. ! instead of . negates.
{ "done": false } // field alone = default operator (eq)
{ "title.has": "milk" } // contains
{ "id.in": ["…", "…"] } // membership
{ "created.between": ["2026-01-01", "2026-02-01"] }
{ "title!has": "draft" } // NOT contains
{ ".or": [{ "notes.has": "2%" }, { "notes.has": "oat" }] } // composite group; "!or" negates
Presets and operators
| Preset | Operators (default first) |
|---|---|
Uuid | eq, in, ne |
String | has, eq, ne |
Integer, Number | eq, gt, lt, lte, gte |
Boolean | eq, ne |
Datetime, Date | eq, gt, lt, lte, gte, between |
Enum | eq, ne, in |
Array | eq, ov (overlap) |
Json | eq, ne |
Textsearch, None | eq |
Operands are validated and coerced per preset. For example, between needs a two-element range in
order.
Responses
.list returns an envelope:
{
"data": [{ "id": "…", "title": "Buy milk", "done": false }],
"pagination": { "limit": 25, "offset": 0, "page": 1, "total": 1, "pages": 1 }
}
.item returns the row object. .meta describes the interface (fields, filters per
field.operator, report params, composites), so UIs can build grids and filter bars without
hard-coding.
Execution pipeline
- Decode parameters into a
FrontendQuery. - Validate statements and sort against the interface.
- Lower to a
DataQuerywith physical columns. - Add
policy_filter: the_tenantaccess list ∧scope_policy. - Execute through the resource's executor; relabel to logical names.
Related: Command and query engines.