update: $push's modifiers are modifiers

`$slice`, `$position` and `$sort` were parsed, accepted and dropped.
`{$each: [3, 4], $slice: -3}` appended both values, sliced nothing and
answered ok: 1 with modifiedCount: 1 -- the same class of wrong answer the
positional operators were, and the reason `tests/spec/operators/` exists
rather than a list of TODOs.

Measured, and the order is the whole of it: insert at `$position`, then
`$sort` the array *including* the new elements, then `$slice` the result.

  - `$slice: n` keeps the first n, `$slice: -n` the **last** n. That half is
    what a capped log depends on and is the one easy to write backwards; the
    test mutates exactly it.
  - `$position` counts back from the end when negative, and clamps at the
    front rather than wrapping.
  - `$sort: 1` orders whole elements in BSON order; `$sort: {a: 1}` orders on
    a field of them, and an element without it sorts as null -- the rank a
    missing field has everywhere else here.
  - **without `$each` there are no modifiers at all**: `{$push: {t: {$slice:
    1}}}` pushes the document `{$slice: 1}` as a value. That is what makes
    `$each` the flag rather than a member of the set, and it is measured, not
    reasoned.

An unknown `$`-prefixed key beside `$each`, or a `$slice`/`$position` that is
not a number, is BadValue -- refused rather than ignored, which is the point.

push-modifiers.json 6/21 -> 21/21. 240/240 unit tests.
This commit is contained in:
A.Shakhmatov
2026-08-10 21:16:14 +03:00
parent 066b32617e
commit 482ddb3d1a
2 changed files with 297 additions and 18 deletions

View File

@@ -4308,6 +4308,11 @@ fn update_refusal(reply: *wire.Reply, err: anyerror, diag: update.Diagnostic) !v
"TypeMismatch",
"The argument to $each in $addToSet must be an array",
) else return bad_value(reply, "The argument to $each in $push must be an array"),
error.BadPushModifier => return bad_value(reply, try std.fmt.allocPrint(
arena,
"Unrecognized or invalid $push modifier '{s}' at field '{s}'",
.{ diag.segment, diag.path },
)),
error.PathNotViable => return reply.put_error(
@intFromEnum(ErrorCode.path_not_viable),
"PathNotViable",