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
names to arguments. Operators are applied in the order they appear in the spec.
Updates are never applied blindly. They run through the agent-safe mutation flow —
you 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 / rollback
protocol. 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}} |
$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"}} |
$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.
$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.
Array operators
| Operator | Description | Example |
|---|---|---|
$push | Appends value(s) to an array field, creating it if absent. Supports $each. | {"$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 exactly equal to the given value. | {"$pull":{"tags":"a"}} |
$pop | Removes one element: 1 removes the last, -1 removes the first. | {"$pop":{"tags":1}} |
The only array modifier is $each, shared by $push and $addToSet. When the spec
is an object containing $each: [...], all of those elements are used; otherwise the
spec value itself is added as a single element.
$push
Appends value(s) to the array field, creating the array if the field is absent.
{"$push":{"tags":{"$each":["c","d"]}}}
With $each, every element is appended in order. Without $each, the single spec
value is appended as one element. Pushing onto a field that exists but is not an
array fails with array update on \field` which is not an array`.
$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. 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 from the array field every element exactly equal to the given value.
{"$pull":{"tags":"a"}}
Matching is by full value equality.
Limitation:
$pullsupports only equality removal against a literal value. It does not accept a query condition such as{"$gt": 5}like MongoDB — a condition object would be compared by full value equality and almost never match.$pullis also the one array operator that silently no-ops on an absent or non-array field, instead of erroring like$push/$addToSet/$pop.
$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 of -1 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)
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
Note: During planning the original
_idis re-inserted into every after-document, so no update operator can ever change a document’s identifier.
See agent mutations for the full plan / apply / rollback reference, including the NDJSON plan and audit files.
Caveats
- No dot-path or nested-field traversal. Every operator treats the field name as a
literal top-level key. There is no
a.b.ctraversal and no positional array operators ($,$[]). - Limited array modifiers. Only
$eachis implemented.$position,$slice, and$sortare not available. $currentDateis not implemented. Using it returns an unknown-operator error.- 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.
- An unrecognized operator key returns an unknown-operator error; 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.