OMGDB DOCS
// Querying

Update Operators

Reference for the OMGDB update operators — field, array, and positional operators with dotted paths, applied through immediate updates or the agent-safe mutation flow.


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 updatesupdate-one, update-many, find-one-and-update, and bulk-write apply 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 flowplan 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.

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

OperatorDescriptionExample
$setSets each named field to the given value, inserting it if absent.{"$set":{"role":"admin"}}
$unsetRemoves each named field. The value side of each entry is ignored.{"$unset":{"tmp":""}}
$incAdds a numeric delta to the field’s current value.{"$inc":{"age":1}}
$mulMultiplies the field’s current numeric value by a factor.{"$mul":{"n":3}}
$bitBitwise and/or/xor of an integer field with a mask.{"$bit":{"flags":{"or":4}}}
$minSets the field only if the given value is less than the current value.{"$min":{"lo":2}}
$maxSets the field only if the given value is greater than the current value.{"$max":{"hi":9}}
$renameRemoves a field and re-inserts its value under a new name.{"$rename":{"old":"new"}}
$currentDateSets 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, $mul on a missing field produces 0 — 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

OperatorDescriptionExample
$pushAppends value(s) to an array field, creating it if absent. Supports the $each, $position, $slice, and $sort modifiers.{"$push":{"tags":{"$each":["c","d"]}}}
$addToSetAdds value(s) only if not already present (set semantics). Supports $each.{"$addToSet":{"tags":"a"}}
$pullRemoves every element matching a value or a query condition.{"$pull":{"scores":{"$gt":80}}}
$pullAllRemoves every element equal to any value in the given array.{"$pullAll":{"tags":["a","b"]}}
$popRemoves 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:

ModifierEffect
$eachAppends every element of the given array, in order.
$positionInserts the $each elements at an index instead of the end (negative counts from the end).
$sliceTruncates the resulting array: positive keeps the head, negative keeps the tail, 0 empties it.
$sortSorts 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: $pull and $pullAll silently 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:

SegmentMeaningExample
a.b.cNested object fields (created on $set if absent).{"$set":{"addr.city":"athens"}}
items.0A 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 $rename source or target) is rejected up front with updates 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:

  1. Plan — compute matched documents and a before/after sample without writing.
  2. Apply — execute the plan by token inside a single transaction.
  3. 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.
  • $bit masks are integers only, and the target field must already exist as an integer.
  • $currentDate writes date values only — the timestamp type 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 ($sett suggests $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.

View this page as raw Markdown →