OMGDB DOCS
// Querying

Query Operators

Reference for the OMGDB find query language — comparison, element, logical, evaluation, and array operators, dotted paths, array semantics, and projection.


A filter is a JSON object matched against each document in a collection. Bare fields test equality; field values wrapping $-prefixed operators express richer predicates. Multiple fields in one filter are implicitly ANDed, and the empty filter {} matches every document.

You run a filter with find:

omgdb find app.omgdb users '{"age":{"$gte":30}}'

The filter is compiled and validated into an internal AST before matching, so structurally invalid filters and unknown operators are rejected up front with precise, structured errors an agent can repair from (see Errors and self-repair). For how filters choose an index or fall back to a scan, see indexes; to inspect the plan or debug a no-results query, see introspection.

The find command

omgdb find <store> <collection> [<filter>] [--limit N] [--skip N] [--sort '<spec>']
    [--project '<spec>'] [--after-id '<json>'] [--after-sort-key '<json>']
Argument / flagMeaning
<store>Path to the store directory (e.g. app.omgdb).
<collection>Collection (namespace) name.
<filter>MongoDB-style filter as a JSON string. Defaults to {} (match all).
--limit NPrint at most N matching documents.
--skip NSkip the first N matching documents before printing.
--sort '<spec>'Sort JSON ({"field":1,"other":-1}), applied before skip/limit. Sorts are deterministic, with _id tie-breaks.
--project '<spec>'Projection JSON; see Projection.
--after-id '<json>'Stateless pagination in _id order: only print matches whose _id sorts after this value. Cannot be combined with --sort.
--after-sort-key '<json>'Stateless pagination through a sorted scan: resume after this sort key. Requires --sort.

Each match prints as one line of canonical JSON. --after-id and --after-sort-key are mutually exclusive; for persistent cursors and the full pagination workflow see the CLI reference.

Comparison operators

OperatorDescriptionExample
$eqField equals the operand. This is the implicit operator for a bare value: {"name":"ana"} compiles to $eq.{"name":{"$eq":"ana"}}
$neField does not equal the operand. Also matches a missing field.{"age":{"$ne":25}}
$gtField is greater than the operand under the total value order.{"age":{"$gt":20}}
$gteField is greater than or equal to the operand.{"age":{"$gte":30}}
$ltField is less than the operand.{"age":{"$lt":30}}
$lteField is less than or equal to the operand.{"age":{"$lte":65}}
$inField equals any value in the operand array.{"role":{"$in":["admin","root"]}}
$ninField equals none of the values in the operand array. Also matches a missing field.{"role":{"$nin":["user"]}}

$in and $nin require an array operand; anything else is a compile error. Combining bounds on one field intersects them — {"age":{"$gt":20,"$lt":40}} matches ages strictly between 20 and 40.

{"name":"ana"}
{"age":{"$gte":30}}
{"age":{"$gt":20,"$lt":40}}
{"age":{"$ne":25}}
{"role":{"$in":["admin","root"]}}
{"role":{"$nin":["user"]}}

Note: $eq, $ne, $in, and $nin use value equality, where NaN never equals itself. The ordering operators ($gt/$gte/$lt/$lte) use the total order, which places NaN consistently. Equality and ordering can therefore disagree on NaN.

Element operators

OperatorDescriptionExample
$existsMatches on field presence (true) or absence (false).{"age":{"$exists":true}}
$typeField’s type matches a type name, a numeric BSON code, or any entry in an array of those.{"age":{"$type":"long"}}

$exists requires a boolean operand and only tests whether the (dotted) path resolves — it does not inspect the value’s type.

{"age":{"$exists":true}}
{"missing":{"$exists":false}}
{"age":{"$type":"long"}}
{"age":{"$type":"number"}}
{"v":{"$type":["bool","long"]}}
{"name":{"$type":2}}

Type names come from the value’s BSON-style type. Note that integers report as long and floats as double:

Value$type nameBSON code
nullnull10
booleanbool8
integer (i64)long18
float (f64)double1
stringstring2
binary databinData5
arrayarray4
object / sub-documentobject3
ObjectIdobjectId7
date / timestampdate9

The alias number matches both long and double. An unknown type name is a compile error with a did-you-mean suggestion (strnig suggests string), and int/decimal — types OMGDB never stores — are redirected explicitly: $type: OMGDB stores no `int` values (integers are `long`); use `long`, `double`, or `number` . That redirect matters to an agent: a naive {"$type":"int"} would otherwise compile into a predicate that can never match.

Logical operators

OperatorDescriptionExample
$andAll listed sub-filters must match.{"$and":[{"a":5},{"b":"x"}]}
$orAt least one sub-filter must match.{"$or":[{"a":1},{"b":"x"}]}
$norNone of the listed sub-filters may match.{"$nor":[{"a":1},{"b":"y"}]}
$notNegates a field-level operator expression.{"a":{"$not":{"$gt":10}}}

$and, $or, and $nor each require an array of filter objects. Because every filter is already an implicit AND of its fields, $and is only needed when you want multiple conditions on the same field path that cannot be expressed in one object.

{"$and":[{"a":5},{"b":"x"}]}
{"$or":[{"a":1},{"b":"x"}]}
{"$nor":[{"a":1},{"b":"y"}]}
{"a":{"$not":{"$gt":10}}}

$not is a field-level operator: it wraps an operator-expression object (every key starting with $) and matches when that inner expression does not. It cannot wrap a bare scalar — {"a":{"$not":5}} is rejected as malformed.

Note: The planner accelerates equality, scalar ranges, and $in (planned as a union of equality buckets) on indexed top-level fields, including compound-index shapes. Predicates nested inside a top-level $or, $nor, or $not are generally not accelerated and fall back to a scan; partial indexes add some nuance. See indexes for the exact eligibility rules — omgdb explain always tells you the plan for a concrete query.

Evaluation operators

OperatorDescriptionExample
$regexString field matches the regular expression.{"name":{"$regex":"^Ada"}}
$modInteger (or truncated float) field modulo divisor equals remainder.{"n":{"$mod":[4,2]}}
$exprAn aggregation-style expression over the whole document is truthy.{"$expr":{"$gt":["$spent","$budget"]}}

$regex uses Rust’s regex crate and is unanchored, so it is a substring match unless you anchor the pattern with ^ / $. An invalid pattern is a compile error. Flags are supplied with a sibling $options string in the same field object:

FlagEffect
iCase-insensitive
mMulti-line (^/$ match at line boundaries)
sDot matches newline (dotall)
xIgnore whitespace (extended / verbose mode)
{"name":{"$regex":"^Ada"}}
{"name":{"$regex":"^ada","$options":"i"}}
{"tags":{"$regex":"rust","$options":"i"}}

$mod requires a two-element [divisor, remainder] array of finite whole numbers (4 and 4.0 both work; 1.5 does not) with a non-zero divisor ([0,1] is rejected). It applies to integer fields directly and to float fields by truncating toward zero; non-numeric fields simply do not match.

{"n":{"$mod":[4,2]}}

Note: $options is only a modifier for $regex, read from the same field object. A standalone $options (with no $regex) is rejected at compile time with $options requires $regex, an unsupported flag names itself (unsupported $options flag `q` (supported: i, m, s, x)), and a non-string $options value is a compile error.

$expr

$expr lifts the aggregation expression engine into a filter, which is how you compare one field against another — something the plain operators cannot express:

{"$expr":{"$gt":["$spent","$budget"]}}
{"dept":"a","$expr":{"$gt":["$spent","$budget"]}}
{"$expr":{"$eq":[{"$toUpper":"$dept"},"A"]}}

$expr appears as a top-level filter key and may sit alongside ordinary field conditions. The expression supports the documented expression operators plus the $$ROOT and $$CURRENT system variables (any other $$-variable is a compile error), and unknown expression operators get did-you-mean suggestions like everything else.

Note: $expr is evaluated per document and is never index-accelerated — an $expr-only filter is always a full scan. Other predicates in the same filter can still use an index; the $expr part is then applied as a re-filter on the candidates.

Array operators

OperatorDescriptionExample
$sizeField is an array of exactly the given length.{"tags":{"$size":3}}
$allField is an array containing every listed value.{"tags":{"$all":["a","c"]}}
$elemMatchField is an array with at least one element satisfying the criteria.{"items":{"$elemMatch":{"qty":{"$gt":5}}}}

$size requires a non-negative integer and matches only when the field is an array. $all requires an array operand and tests plain element membership, again only on array fields.

$elemMatch requires an object operand and has two forms, chosen by the operand’s shape:

  • Document form — the operand is a sub-filter (with plain field keys), matched against each object element: {"items":{"$elemMatch":{"qty":{"$gt":5}}}}.
  • Scalar form — the operand is an operator expression (every key starts with $), matched against each scalar element: {"tags":{"$elemMatch":{"$eq":"b"}}}.
{"tags":{"$size":3}}
{"tags":{"$all":["a","c"]}}
{"items":{"$elemMatch":{"qty":{"$gt":5}}}}
{"tags":{"$elemMatch":{"$eq":"b"}}}

Limitation: $all does not support the nested $elemMatch-inside-$all form — it only checks plain element membership. An $elemMatch operand mixing $-keys and plain keys is treated as the document sub-filter form.

Dotted-path traversal

A field key is split on . into path segments. Each segment descends into an embedded document by key, or into an array by numeric index:

{"addr.city":"athens"}
{"items.0.sku":"x"}

addr.city reads the city field of the addr sub-document; items.0.sku reads the sku field of the first array element. Descending through a scalar or a missing key resolves to nothing, so the predicate fails to match — unless the operator tolerates absence ($exists:false, $ne, $nin, $not).

Note: Secondary indexes cover top-level fields, so a dotted-path predicate like {"addr.city":"athens"} always scans. (Partial-index filter predicates may use dotted paths — see indexes.)

Array-contains semantics

A scalar predicate on an array field matches if any element satisfies it (MongoDB’s multikey semantics). This applies to equality, the comparison operators, $in, and $regex:

{"tags":"rag"}
{"tags":{"$in":["db"]}}
{"tags":{"$regex":"rust","$options":"i"}}

{"tags":"rag"} matches a document whose tags array contains "rag". {"age":{"$gt":20}} matches if any element of an array-valued age field exceeds 20. $size, $all, and $elemMatch, by contrast, operate on the array as a whole and require the field itself to be an array.

Implicit AND

Multiple top-level fields in a filter are ANDed together — all conditions must hold:

{"role":"admin","age":{"$gte":18}}

This matches documents where role equals "admin" and age is at least 18. The empty filter {} is an AND of zero conditions and therefore matches every document.

Value ordering

The comparison operators (and range queries and sorting) use a single total order across all value types. Numbers compare numerically across i64/f64; values of different types compare by a fixed type rank:

null < number < string < object < array < bytes < bool < date < objectId

Arrays compare element-wise then by length; objects compare by (key, value) pairs in order then by length. NaN is ordered consistently (via total_cmp), which is why the ordering operators stay total even though $eq treats NaN as unequal to itself.

Projection

Pass --project with a JSON spec to control which fields each result includes. A value of 1 (or true) includes a field; 0 (or false) excludes it; and three array operators shape projected array fields.

omgdb find app.omgdb users '{"role":"admin"}' --project '{"name":1}'
{"name":1}                          // include name (and _id by default)
{"name":1,"_id":0}                  // include name, suppress _id
{"age":0}                           // exclude age, keep everything else
{"addr.city":1}                     // dotted path: include a nested field
{"comments":{"$slice":-2}}          // keep the last two array elements
{"comments":{"$slice":[1,3]}}       // skip 1, keep 3
{"items":{"$elemMatch":{"qty":{"$gt":5}}}}  // first element matching the sub-filter
{"items.$":1}                       // first element the query matched
{"name":1,"age":0}                  // ERROR: cannot mix inclusion and exclusion

Rules:

  • _id is kept by default and does not set inclusion/exclusion mode; suppress it explicitly with "_id":0.
  • Inclusion and exclusion modes cannot be mixed (aside from _id). Any value other than an integer, boolean, or a $slice/$elemMatch object is rejected.
  • Dotted paths project nested object fields in both modes.
  • $slice takes an integer (positive keeps the head, negative keeps the tail) or [skip, limit] with a positive limit.
  • $elemMatch returns only the first array element matching its sub-filter.
  • Positional $ ({"items.$":1}) is query-bound: it returns the first array element matched by the filter’s condition on that array path. It requires such a condition, at most one positional projection may appear, it must end the path, and ambiguous bindings are rejected rather than guessed (use query $elemMatch for multi-field element conditions).

Errors and self-repair

Filters are validated at compile time. Two error kinds are surfaced:

  • Malformed query — a structurally invalid operand, e.g. $in without an array, $exists without a boolean, $mod with a zero divisor, $options without $regex, or an invalid $regex pattern. The message names the exact problem.
  • Unknown query operator — an unrecognised $-prefixed token. The message echoes the exact bad token and, when a close match exists, adds a “did you mean” suggestion (nearest known operator within edit distance 2).
{"age":{"$gtee":1}}
// error: unknown query operator: $gtee (did you mean `$gte`?)

The recognised operators are: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $exists, $type, $not, $and, $or, $nor, $expr, $size, $all, $mod, $elemMatch, $regex.

To debug a query that returns nothing, omgdb diagnose reports the per-predicate selectivity (how many documents each top-level field condition matches alone, plus the observed value range for a condition that matches nothing), and omgdb explain describes whether the query will use an index or a full scan. See introspection and indexes. For multi-stage transformations beyond filtering, see aggregation.

View this page as raw Markdown →