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, LLM-friendly errors (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] [--project '<spec>']
| Argument / flag | Meaning |
|---|---|
<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 N | Print at most N matching documents. |
--project '<spec>' | Projection JSON; see Projection. |
Each match prints as one line of canonical JSON.
Comparison operators
| Operator | Description | Example |
|---|---|---|
$eq | Field equals the operand. This is the implicit operator for a bare value: {"name":"ana"} compiles to $eq. | {"name":{"$eq":"ana"}} |
$ne | Field does not equal the operand. Also matches a missing field. | {"age":{"$ne":25}} |
$gt | Field is greater than the operand under the total value order. | {"age":{"$gt":20}} |
$gte | Field is greater than or equal to the operand. | {"age":{"$gte":30}} |
$lt | Field is less than the operand. | {"age":{"$lt":30}} |
$lte | Field is less than or equal to the operand. | {"age":{"$lte":65}} |
$in | Field equals any value in the operand array. | {"role":{"$in":["admin","root"]}} |
$nin | Field 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$ninuse value equality, whereNaNnever equals itself. The ordering operators ($gt/$gte/$lt/$lte) use the total order, which placesNaNconsistently. Equality and ordering can therefore disagree onNaN.
Element operators
| Operator | Description | Example |
|---|---|---|
$exists | Matches on field presence (true) or absence (false). | {"age":{"$exists":true}} |
$type | Field’s type name equals the operand string. | {"age":{"$type":"long"}} |
$exists requires a boolean operand and only tests whether the (dotted) path resolves — it does not inspect the value’s type. $type requires a single type-name string.
{"age":{"$exists":true}}
{"missing":{"$exists":false}}
{"age":{"$type":"long"}}
Type names come from the value’s BSON-style type. Note that integers report as long and floats as double:
| Value | $type name |
|---|---|
null | null |
| boolean | bool |
integer (i64) | long |
float (f64) | double |
| string | string |
| binary data | binData |
| array | array |
| object / sub-document | object |
| ObjectId | objectId |
| date / timestamp | date |
Limitation:
$typeaccepts only a single type-name string. The array-of-types form ({"$type":["long","double"]}) and numeric BSON type codes are not supported, andint/numberare not recognised — onlylongmatches an integer field.
Logical operators
| Operator | Description | Example |
|---|---|---|
$and | All listed sub-filters must match. | {"$and":[{"a":5},{"b":"x"}]} |
$or | At least one sub-filter must match. | {"$or":[{"a":1},{"b":"x"}]} |
$nor | None of the listed sub-filters may match. | {"$nor":[{"a":1},{"b":"y"}]} |
$not | Negates 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: Index acceleration only inspects the root implicit AND of single-field predicates. A top-level
$or,$nor, or$not, or any equality/range nested inside a logical operator, forces a full collection scan. See indexes.
Evaluation operators
| Operator | Description | Example |
|---|---|---|
$regex | String field matches the regular expression. | {"name":{"$regex":"^Ada"}} |
$mod | Integer (or truncated float) field modulo divisor equals remainder. | {"n":{"$mod":[4,2]}} |
$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:
| Flag | Effect |
|---|---|
i | Case-insensitive |
m | Multi-line (^/$ match at line boundaries) |
s | Dot matches newline (dotall) |
x | Ignore whitespace (extended / verbose mode) |
{"name":{"$regex":"^Ada"}}
{"name":{"$regex":"^ada","$options":"i"}}
{"tags":{"$regex":"rust","$options":"i"}}
$mod requires a two-element [divisor, remainder] integer array 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:
$optionsis only a modifier for$regex, read from the same field object. A standalone$options(with no$regex) compiles to a no-op that always matches; a non-string$optionsvalue is ignored.
Array operators
| Operator | Description | Example |
|---|---|---|
$size | Field is an array of exactly the given length. | {"tags":{"$size":3}} |
$all | Field is an array containing every listed value. | {"tags":{"$all":["a","c"]}} |
$elemMatch | Field 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:
$alldoes not support the nested$elemMatch-inside-$allform — it only checks plain element membership. An$elemMatchoperand 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: Dotted-path predicates are never index-accelerated (an index is keyed on a single top-level field), so a filter like
{"addr.city":"athens"}always scans.
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 top-level fields each result includes. A value of 1 (or true) includes a field; 0 (or false) excludes it.
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
{"name":1,"age":0} // ERROR: cannot mix inclusion and exclusion
Rules:
_idis 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 or boolean is rejected; in practice use1/truefor inclusion and0/falsefor exclusion (any non-zero integer is treated as inclusion).
Limitation: Projection is top-level only — it cannot include or exclude nested dotted fields, and array projection operators (
$slice, positional$,$elemMatch) are not implemented.
Errors and self-repair
Filters are validated at compile time. Two error kinds are surfaced:
- Malformed query — a structurally invalid operand, e.g.
$inwithout an array,$existswithout a boolean,$modwith a zero divisor, or an invalid$regexpattern. 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, $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.