update: an unknown modifier, and two paths that decide one field
Two refusals this server did not have, both measured.
**ConflictingUpdateOperators (40).** Two paths in one update where either is a
prefix of the other at a segment boundary leave the result depending on which
operator ran first, so mongod refuses rather than picking an order:
`{$set: {a: 2}, $inc: {a: 1}}`, `{$min: {a: 2}, $max: {a: 9}}`,
`{$set: {a: 2, "a.b": 3}}`, and `$rename` counts both its ends. Siblings are
fine, and the segment boundary is what makes them fine -- `a.b` and `a.bb`
share a string prefix and decide nothing about each other, which is the
mutation the test names.
The check is quadratic in the number of paths and bounded at 64, so it stays
on the stack. Past the bound it stops checking rather than refusing: missing
a conflict in a 65-field update is a better answer than refusing a legal one,
and no driver writes updates that wide.
**Unknown modifier (9).** A name not in the operator table was
`InvalidUpdate` -> BadValue; mongod answers FailedToParse and names it. What
matters is the option nobody took: skipping an unknown operator would make a
typo an update the client believes ran. A known operator handed something
that is not a document of fields is the same code.
One existing test changed answer and was wrong: it packed
`$set: {new: 5}` and `$rename: {new: "renamed"}` into one update and passed
only because there was no conflict check. mongod refuses that with 40 --
measured, then the test split into two updates.
operators corpus 95/102 -> **102/102**. The pinned crud corpus, the positional
corpus and the aggregation corpus are unmoved at 218/73, 51/0 and 70/0.
247/247 unit tests.
This commit is contained in:
@@ -66,6 +66,10 @@ pub const ErrorCode = enum(i32) {
|
||||
duplicate_key = 11000,
|
||||
namespace_exists = 48,
|
||||
failed_to_parse = 9,
|
||||
/// `ConflictingUpdateOperators`, measured on mongod 8.3.7: two paths in
|
||||
/// one update where either is a prefix of the other, so which of them
|
||||
/// decides the result would depend on the order the operators ran in.
|
||||
conflicting_update_operators = 40,
|
||||
internal_error = 1,
|
||||
/// "Unrecognized pipeline stage name". A `Location` code, so mongod names it
|
||||
/// `Location40324` rather than after any symbol.
|
||||
@@ -4326,6 +4330,26 @@ fn update_refusal(reply: *wire.Reply, err: anyerror, diag: update.Diagnostic) !v
|
||||
"$type expression ({{$type: 'timestamp/date'}}).",
|
||||
.{diag.other},
|
||||
)),
|
||||
error.UnknownModifier => return failed_to_parse(reply, try std.fmt.allocPrint(
|
||||
arena,
|
||||
"Unknown modifier: {s}. Expected a valid update modifier or pipeline-style " ++
|
||||
"update specified as an array",
|
||||
.{diag.segment},
|
||||
)),
|
||||
error.ModifierNeedsFields => return failed_to_parse(reply, try std.fmt.allocPrint(
|
||||
arena,
|
||||
"Modifiers operate on fields but we found type {s} instead",
|
||||
.{diag.segment},
|
||||
)),
|
||||
error.ConflictingUpdate => return reply.put_error(
|
||||
@intFromEnum(ErrorCode.conflicting_update_operators),
|
||||
"ConflictingUpdateOperators",
|
||||
try std.fmt.allocPrint(
|
||||
arena,
|
||||
"Updating the path '{s}' would create a conflict at '{s}'",
|
||||
.{ diag.path, diag.segment },
|
||||
),
|
||||
),
|
||||
error.PathNotViable => return reply.put_error(
|
||||
@intFromEnum(ErrorCode.path_not_viable),
|
||||
"PathNotViable",
|
||||
|
||||
Reference in New Issue
Block a user