Update operators describe how to mutate matched documents. They mirror MongoDB’s
update syntax: an update spec is a JSON object whose top-level keys are operators
($set, $inc, $push, …), and each operator’s value is an object mapping field
paths to arguments. Operators are applied in the order they appear in the spec.
There are two ways to apply an update spec, and both use the operators on this page:
- Immediate updates —
update-one,update-many,find-one-and-update, andbulk-writeapply the spec atomically and fail-closed: a many-document update commits its complete selected set or appends nothing, and every command is bounded by explicit scan/mutation limits. - The agent-safe mutation flow — plan a change (a dry run that writes nothing), apply it by
token in a single transaction, then optionally roll it back to the recorded before-state. See
agent mutations for the
plan/apply/rollbackprotocol.
Either way, a change is appended to the op-log as new records — history, never an overwrite. For selecting which documents an update touches, see query operators.
Field operators
| Operator | Description | Example |
|---|---|---|
$set | Sets each named field to the given value, inserting it if absent. | {"$set":{"role":"admin"}} |
$unset | Removes each named field. The value side of each entry is ignored. | {"$unset":{"tmp":""}} |
$inc | Adds a numeric delta to the field’s current value. | {"$inc":{"age":1}} |
$mul | Multiplies the field’s current numeric value by a factor. | {"$mul":{"n":3}} |
$bit | Bitwise and/or/xor of an integer field with a mask. | {"$bit":{"flags":{"or":4}}} |
$min | Sets the field only if the given value is less than the current value. | {"$min":{"lo":2}} |
$max | Sets the field only if the given value is greater than the current value. | {"$max":{"hi":9}} |
$rename | Removes a field and re-inserts its value under a new name. | {"$rename":{"old":"new"}} |
$currentDate | Sets the field to the current timestamp as a date value. | {"$currentDate":{"updatedAt":true}} |
$set
Inserts or overwrites each named field with the supplied value.
{"$set":{"role":"admin"}}
$unset
Removes each named field from the document. Removing an absent field is a no-op,
and the value side of each entry ("" below) is ignored.
{"$unset":{"tmp":""}}
$inc
Adds the delta to the field’s current numeric value. An absent field is treated as
0, so $inc of 1 on a missing field results in 1.
{"$inc":{"age":1}}
Integer arithmetic uses checked addition: an i64 overflow (for example
i64::MAX + 1) returns a Malformed error — $inc would overflow i64: x + y —
rather than panicking or silently wrapping a corrupted value into the store. Mixed
integer/float operands are promoted to f64. A non-numeric current value or delta
yields $inc requires numeric values.
$inc on a field holding i64::MAX with delta 1 → Malformed error (not wrapped)
$mul
Multiplies the field’s current numeric value by the factor.
{"$mul":{"n":3}}
Like $inc, integer multiplication is checked: an i64 overflow returns a
Malformed error ($mul would overflow i64: x * y) and never wraps. Mixed
integer/float operands promote to f64.
Note: Because an absent field defaults to
0,$mulon a missing field produces0— it does not skip the field or fall back to the factor.
$bit
Applies one bitwise operation — exactly one of and, or, or xor — to an
existing integer field with an integer mask.
{"$bit":{"flags":{"or":4}}}
Unlike $inc/$mul, $bit does not auto-create the field: a missing field fails
with $bit requires an existing integer field `flags` , and a non-integer value
is rejected. A misspelled operation gets a did-you-mean hint (e.g. xr suggests
xor).
$min and $max
$min writes the supplied value only when it is strictly less than the current
value (or the field is absent). $max writes only when the supplied value is
strictly greater (or the field is absent). Comparison uses the same ordering as the
query engine.
{"$min":{"lo":2}, "$max":{"hi":9}}
If the existing value is already on the right side of the bound, it is kept
unchanged. For example, $min of 99 against an existing 5 leaves 5 in place;
$min of 2 against 5 lowers it to 2.
$rename
Removes the source field and re-inserts its value under the target name.
{"$rename":{"old":"new"}}
The target name must be a string, otherwise the update fails with
$rename target must be a string. If the source field is absent, $rename is a
no-op. It does not check whether the target already exists — an existing target is
overwritten. Both source and target may be dotted paths ("a.b" → "a.c"), but
they must differ, must not be parent/child of one another, and must not contain
positional array segments ($, $[], $[id]).
$currentDate
Sets each named field to the current timestamp as a date value. The per-field spec
is true or {"$type":"date"}:
{"$currentDate":{"updatedAt":true}}
{"$currentDate":{"updatedAt":{"$type":"date"}}}
MongoDB’s {"$type":"timestamp"} form is rejected with an explicit error
($currentDate $type `timestamp` is not supported; use `date` ) — OMGDB has one
date/timestamp type.
Array operators
| Operator | Description | Example |
|---|---|---|
$push | Appends value(s) to an array field, creating it if absent. Supports the $each, $position, $slice, and $sort modifiers. | {"$push":{"tags":{"$each":["c","d"]}}} |
$addToSet | Adds value(s) only if not already present (set semantics). Supports $each. | {"$addToSet":{"tags":"a"}} |
$pull | Removes every element matching a value or a query condition. | {"$pull":{"scores":{"$gt":80}}} |
$pullAll | Removes every element equal to any value in the given array. | {"$pullAll":{"tags":["a","b"]}} |
$pop | Removes one element: 1 removes the last, -1 removes the first. | {"$pop":{"tags":1}} |
$push
Appends value(s) to the array field, creating the array if the field is absent.
{"$push":{"tags":{"$each":["c","d"]}}}
Without a modifier, the single spec value is appended as one element. With
modifiers, $each is required and the others adjust the result:
| Modifier | Effect |
|---|---|
$each | Appends every element of the given array, in order. |
$position | Inserts the $each elements at an index instead of the end (negative counts from the end). |
$slice | Truncates the resulting array: positive keeps the head, negative keeps the tail, 0 empties it. |
$sort | Sorts the resulting array: 1/-1 for whole-element sorts, or {"field":1} to sort object elements by a field. |
Pushing onto a field that exists but is not an array fails with
array update on `field` which is not an array. An unknown modifier is rejected
with a did-you-mean hint.
$push {"$each":["c","d"]} onto ["a","b"] → ["a","b","c","d"]
$push {"xs":1} onto {} → {"xs":[1]}
$push {"n":1} onto {"n":5} → Malformed error (n is not an array)
$addToSet
Adds value(s) to the array only if not already present. Deduplication is by full value equality.
{"$addToSet":{"tags":"a"}}
An already-present value leaves the array unchanged. With $each, each candidate is
checked individually against the current array ($each is the only modifier
$addToSet accepts). Like $push, it creates the array if absent and errors on a
non-array field.
$addToSet "a" onto ["a","b"] → ["a","b"] (no-op, "a" already present)
$pull
Removes matching elements from the array field. The criteria can be a literal value (full value equality), an operator condition applied to each element, or a document sub-filter matched against object elements:
{"$pull":{"tags":"a"}}
{"$pull":{"scores":{"$gt":80}}}
{"$pull":{"items":{"qty":{"$lt":1}}}}
A criteria object whose keys all start with $ is treated as a per-element operator
condition; any other object is a sub-filter that only object elements can match —
the same shape convention as query $elemMatch.
Note:
$pulland$pullAllsilently no-op on an absent field, instead of auto-creating it like$push/$addToSet/$pop; all of them error on a non-array field.
$pullAll
Removes every element equal to any value in the supplied array (equality only —
use $pull for conditions):
{"$pullAll":{"tags":["a","b"]}}
The spec must be an array of values; anything else fails with
$pullAll requires an array of values.
$pop
Removes a single element from the end or start of the array.
{"$pop":{"tags":1}}
The direction must be 1 (remove the last element) or -1 (remove the first); any
other value fails with $pop value must be 1 (last) or -1 (first). $pop on
an empty array is a safe no-op. It auto-creates an absent field as an empty array and
errors on a non-array field.
$pop 1 on ["a","b"] → ["a"] (removes last)
$pop -1 on ["a","b"] → ["b"] (removes first)
Dotted paths and positional operators
Every operator’s field name is a dotted update path. Segments descend into embedded documents by key and into arrays by numeric index, and three positional segments address array elements without hard-coding an index:
| Segment | Meaning | Example |
|---|---|---|
a.b.c | Nested object fields (created on $set if absent). | {"$set":{"addr.city":"athens"}} |
items.0 | A numeric array index. | {"$set":{"items.0.qty":5}} |
items.$ | Query-bound positional: the first element the filter matched. | {"$set":{"items.$.qty":5}} |
items.$[] | All-elements positional: applies to every element. | {"$inc":{"scores.$[]":1}} |
items.$[i] | Filtered positional: elements matching the arrayFilters entry for i. | {"$set":{"items.$[i].qty":0}} |
The query-bound $ requires the update’s filter to identify exactly one condition
on that array path — a multi-field element match must use query $elemMatch so the
binding is unambiguous.
$[id] is driven by MongoDB-style arrayFilters: each filter document references
exactly one identifier, every $[id] used in the update must have a matching
filter, and every supplied filter must be used. plan-update accepts them with
--array-filters:
omgdb plan-update app.omgdb orders '{"status":"open"}' \
'{"$set":{"items.$[cheap].flagged":true}}' \
--array-filters '[{"cheap.price":{"$lt":10}}]'
Note: Updates can never touch
_id. Any update path beginning with_id(including a$renamesource or target) is rejected up front withupdates cannot modify immutable `_id` path, before any document is matched.
Applying updates through the agent-safe flow
Update operators are consumed by the mutation protocol rather than written directly. A typical round trip:
- Plan — compute matched documents and a before/after sample without writing.
- Apply — execute the plan by token inside a single transaction.
- Rollback — restore the recorded before-state if needed.
let plan = plan_update(
&s,
"u",
&obj(r#"{"name":"ada"}"#),
&obj(r#"{"$set":{"role":"admin"},"$inc":{"age":1}}"#),
)?;
// plan reports matched == 1; the store is still unchanged (age 30, no role)
let result = apply_change(&mut s, &token)?;
// after apply: age == 31, role == "admin"
rollback(&mut s, &change_id)?; // restores age 30, role removed
See agent mutations for the full plan / apply / rollback reference, including the NDJSON plan and audit files.
Caveats
- Operator ordering. Operators are applied in the spec’s iteration order, so the result of multiple operators touching the same field depends on that order.
$bitmasks are integers only, and the target field must already exist as an integer.$currentDatewrites date values only — thetimestamptype is rejected by name.- An unrecognized operator key returns an unknown-operator error with a did-you-mean
suggestion when a known operator is within edit distance 2 (
$settsuggests$set); a non-object update or a non-object operator spec returns a malformed error.
For selecting the documents an update applies to, see query operators.