4. Values and types
4.1 The value model
Documents have no schema. Every field holds one of six kinds:
| Kind | type() | Description |
|---|---|---|
| null | "null" | An explicit null |
| boolean | "bool" | true or false |
| number | "number" | A signed integer (i64), an unsigned integer (u64) or a finite float (f64). The physical kind is kept in storage |
| string | "string" | UTF-8 text |
| array | "array" | Ordered values of any kinds, mixed kinds allowed |
| object | "object" | A nested document |
A field can also be missing: absent from the document. Missing is not null (4.4).
4.2 _id
Every stored document has an _id, a UUID v7 such as 01900000-0000-7000-8000-000000000003.
insertandloadgenerate it when absent.loadaccepts an explicit_id(32 hex digits, with or without dashes): idempotent imports and upserts.where _id == "<uuid>"runs as a primary-key lookup instead of a scan.selectalways keeps_id.- Rows made by
groupcarry a deterministic synthetic_id.
Other reserved fields: _place and _app_instance (the scope of a document, written by the Core), _distance
(written by near), and the history fields _doc_id, _version, _op, _generation,
_at, _by, _by_device (chapter 8).
4.3 Comparison rules
- Integers and floats compare numerically:
age == 31.0matchesage: 31. - Strings compare lexicographically (by code point):
name >= "B" and name < "D". - No cross-kind coercion.
age == "31"is an error (operation "==" is incompatible with values …), notfalse.- Exception:
==and!=acceptnullon either side (nick? == null). - Exception:
x in [...]never fails on kinds: an item of another kind just does not match.
- Exception:
- Fields compare with fields:
where age >= profile.score,where o.user == u.name.
Order between kinds (used by sort, group keys, distinct, window partitions): missing and null first,
then booleans, numbers, strings, arrays, objects. A number and a numeric string are never compared by value.
4.4 Missing fields
A field read by a predicate or an expression must exist on every row that reaches it:
expression evaluation failed: field path "nickname" is missing
This is deliberate: a typo surfaces as an error instead of an empty result. Rows already streamed before the failing one have been sent, and the response ends in error.
The tools for optional fields:
| Tool | Meaning | Example |
|---|---|---|
field? | Reads null when missing | where nick? == "Zed", profile?.country |
exists(x) | true or false, never fails | where exists(nick) and nick != "" |
coalesce(a, b, …) | First present, non-null argument | coalesce(nick, name) |
optional(x) | Same as x? | optional(nick) |
lazy and, or, if, case | Unevaluated branches are not read | if(exists(x), x, 0) |
Stages that tolerate missing fields: projections (select field, drop, rename), near (drops rows without the
vector), unwind (passes rows without the array), group and pivot keys after a filter was pushed past them (read as
optional, null).
Pattern. A collection fed by files of several shapes, where some rows lack
date:on data | where date? != null | …keeps the dated rows and never fails.
4.5 Numbers
| Operation | Result |
|---|---|
+ - * % on integers | An integer while it fits in i64 (qty * 2 + 1 → 7), else a float |
| Anything with a float | A float |
/ | Always a float (7 / 2 → 3.5) |
| Division by zero | An error: division by zero; use div(a, b, fallback) for a safe division |
Unary - | Integer negation when possible |
sum (stage measure or function) | An integer while every term is one and the total fits, else a float |
On the wire, integers above 2³² travel as floats so that JavaScript clients never lose precision silently; integers beyond 2⁵³ are refused on the wire.
4.6 Text
Text is UTF-8. Positions count characters, not bytes; a negative start counts from the end. There is no + on
strings: use concat.
4.7 Dates
There is no date type. A date is either:
- an ISO-8601 string:
"2026-03-09","2026-03","2026-03-09T10:00:00Z","2026-03-09 10:00:00+02:00"; - a number of milliseconds since the Unix epoch.
Every computation is in UTC. ISO strings of the same shape sort and compare correctly as text (month >= "2026-02");
mixing shapes or kinds does not. The date functions accept both and return epoch milliseconds,
except date_format (text) and date_part (a number).