Skip to main content

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​

FlagMeaning
identifierThe key for .item/{id}
sortable / no_sortOverride 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​

DeclarationBehavior
policy: required (default)scope_policy must return a constraint, or the request is denied
policy: public("reason", "sign-off")Unrestricted on purpose
policy: optionalLegacy; 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​

ParamDefaultMeaning
limit25Page size, capped by query_max_limit (500)
page11-based page
sortresource default_orderCSV of field.asc / field.desc
include / excludeall fieldsCSV of fields to return or omit
querynoneURL-encoded JSON filter (below)
qbasenoneA base filter ANDed with query; the client can only narrow it
textnoneFull-text term, when the resource allows it
counttruefalse 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​

PresetOperators (default first)
Uuideq, in, ne
Stringhas, eq, ne
Integer, Numbereq, gt, lt, lte, gte
Booleaneq, ne
Datetime, Dateeq, gt, lt, lte, gte, between
Enumeq, ne, in
Arrayeq, ov (overlap)
Jsoneq, ne
Textsearch, Noneeq

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​

  1. Decode parameters into a FrontendQuery.
  2. Validate statements and sort against the interface.
  3. Lower to a DataQuery with physical columns.
  4. Add policy_filter: the _tenant access list ∧ scope_policy.
  5. Execute through the resource's executor; relabel to logical names.

Related: Command and query engines.