M3: pipeline-style updates #9
11
PLAN.md
11
PLAN.md
@@ -467,7 +467,7 @@ answers, and the trade is only acceptable because the lie is removed first.
|
|||||||
| M1 | **Cursors + wire polish** | getMore / killCursors / batchSize; server-side cursor state with idle timeout; sessions plumbing (lsid accepted) as drivers send it; hello advertisement updates; **`moreToCome` on requests** (see the bug below); command-monitoring assertions in the spec runner | crud spec suite green; e2e green |
|
| M1 | **Cursors + wire polish** | getMore / killCursors / batchSize; server-side cursor state with idle timeout; sessions plumbing (lsid accepted) as drivers send it; hello advertisement updates; **`moreToCome` on requests** (see the bug below); command-monitoring assertions in the spec runner | crud spec suite green; e2e green |
|
||||||
| M2 | **The `aggregate` command surface** | `$out` and `$merge` (7 of the 13 failures), and refusing every pipeline construct the engine does not implement instead of answering `0` (amendment A6). The other 6 failures are blocked on M2.5, M4 and M8 — see `docs/M2_DESIGN_REVIEW.md` §7 | `aggregate-*.json`: 0 fail among the 7 reachable cases |
|
| M2 | **The `aggregate` command surface** | `$out` and `$merge` (7 of the 13 failures), and refusing every pipeline construct the engine does not implement instead of answering `0` (amendment A6). The other 6 failures are blocked on M2.5, M4 and M8 — see `docs/M2_DESIGN_REVIEW.md` §7 | `aggregate-*.json`: 0 fail among the 7 reachable cases |
|
||||||
| M2.5 | **The aggregation engine** | expression evaluator, per-stage document iterator, the accumulators, `$unwind`; `$lookup`/`$facet` explicitly out of the first cut (amendment A6) | a purpose-built stage corpus, every expectation measured against mongod |
|
| M2.5 | **The aggregation engine** | expression evaluator, per-stage document iterator, the accumulators, `$unwind`; `$lookup`/`$facet` explicitly out of the first cut (amendment A6) | a purpose-built stage corpus, every expectation measured against mongod |
|
||||||
| M3 | **Update operators + index types** | `distinct` (**done**); positional paths refused rather than destructive (**done**); `$`/`$[]`/`$[<ident>]` + `arrayFilters` (**done**); $setOnInsert, $addToSet, $mul, $min/$max, $pop, $pullAll, $currentDate + `$push`'s modifiers (**done**); then pipeline updates; partial + hashed indexes | `tests/spec/positional/` 0 fail (51 cases) and `tests/spec/operators/` 0 fail (102 cases), both recorded from mongod — **green**; the named gate could not see either, see below; remaining crud coverage; e2e3/e2e4 green |
|
| M3 | **Update operators + index types** | `distinct` (**done**); positional paths refused rather than destructive (**done**); `$`/`$[]`/`$[<ident>]` + `arrayFilters` (**done**); $setOnInsert, $addToSet, $mul, $min/$max, $pop, $pullAll, $currentDate + `$push`'s modifiers (**done**); pipeline-style updates (**done**); then partial + hashed indexes | `tests/spec/positional/` 0 fail (51 cases) and `tests/spec/operators/` 0 fail (125 cases), both recorded from mongod — **green**; the named gate could not see either, see below; remaining crud coverage; e2e3/e2e4 green |
|
||||||
| M4 | **Sessions + transactions** | logical sessions, snapshot isolation on the mmap engine, write concern at commit | sessions + transactions spec suites green |
|
| M4 | **Sessions + transactions** | logical sessions, snapshot isolation on the mmap engine, write concern at commit | sessions + transactions spec suites green |
|
||||||
| M5 | **Change streams** | change feed + resume tokens (likely log-seq based), getMore integration | change-streams spec suite green |
|
| M5 | **Change streams** | change feed + resume tokens (likely log-seq based), getMore integration | change-streams spec suite green |
|
||||||
| M6 | **Admin/ops commands** | dbStats, collStats, serverStatus, ping, buildInfo, listDatabases filters, dropDatabase durability (log it) | mongosh UX smoke; e2e green |
|
| M6 | **Admin/ops commands** | dbStats, collStats, serverStatus, ping, buildInfo, listDatabases filters, dropDatabase durability (log it) | mongosh UX smoke; e2e green |
|
||||||
@@ -1139,6 +1139,15 @@ has to be its own commit with its own re-recorded scorecard.
|
|||||||
in `build_upsert_doc`; the pinned crud corpus never noticed because its
|
in `build_upsert_doc`; the pinned crud corpus never noticed because its
|
||||||
upsert cases match `upsertedId` loosely.
|
upsert cases match `upsertedId` loosely.
|
||||||
|
|
||||||
|
Pipeline-style updates went into the same corpus rather than an eighth
|
||||||
|
directory -- another shape of update document, sharing the recorder and the
|
||||||
|
masking. 23 cases, and the one that decides the design: **the `_id` survives
|
||||||
|
every stage**, including `$replaceRoot` and `$project: {_id: 0}`, because a
|
||||||
|
pipeline update rewrites a document rather than replacing one document with
|
||||||
|
another. A stage setting it to something else is ImmutableField (66). That
|
||||||
|
work also gave `aggregate` `$replaceWith`, which it never had, since the
|
||||||
|
stage compiler is shared.
|
||||||
|
|
||||||
Left open, measured and deliberate: `$bit` is not implemented (mongod has it,
|
Left open, measured and deliberate: `$bit` is not implemented (mongod has it,
|
||||||
no driver in `tests/` sends it, and it is not in the M3 row); `$sort` inside
|
no driver in `tests/` sends it, and it is not in the M3 row); `$sort` inside
|
||||||
`$push` accepts only a one-field key document, where mongod allows several;
|
`$push` accepts only a one-field key document, where mongod allows several;
|
||||||
|
|||||||
387
src/commands.zig
387
src/commands.zig
@@ -74,6 +74,12 @@ pub const ErrorCode = enum(i32) {
|
|||||||
/// "Unrecognized pipeline stage name". A `Location` code, so mongod names it
|
/// "Unrecognized pipeline stage name". A `Location` code, so mongod names it
|
||||||
/// `Location40324` rather than after any symbol.
|
/// `Location40324` rather than after any symbol.
|
||||||
location_unrecognized_stage = 40324,
|
location_unrecognized_stage = 40324,
|
||||||
|
/// `Location40323`, measured on mongod 8.3.7: an array element that packs
|
||||||
|
/// two stages into one document, where a pipeline stage is one field.
|
||||||
|
location_stage_needs_one_field = 40323,
|
||||||
|
/// `ImmutableField`, measured: what a pipeline-style update answers when a
|
||||||
|
/// stage leaves the document with a different `_id` than it started with.
|
||||||
|
immutable_field = 66,
|
||||||
index_options_conflict = 85,
|
index_options_conflict = 85,
|
||||||
cannot_create_index = 67,
|
cannot_create_index = 67,
|
||||||
invalid_index_specification_option = 197,
|
invalid_index_specification_option = 197,
|
||||||
@@ -1954,7 +1960,18 @@ fn cmd_update(ctx: *Context, msg: *wire.Message, reply: *wire.Reply) !void {
|
|||||||
// by the dispatch epilogue once the collection lock is released.
|
// by the dispatch epilogue once the collection lock is released.
|
||||||
for (specs, 0..) |*spec, si| {
|
for (specs, 0..) |*spec, si| {
|
||||||
const q = doc_arg(spec.get("q")) orelse return bad_value(reply, "update spec requires q");
|
const q = doc_arg(spec.get("q")) orelse return bad_value(reply, "update spec requires q");
|
||||||
const u_doc = doc_arg(spec.get("u")) orelse return bad_value(reply, "update spec requires u");
|
// `u` is a document of operators, a replacement document, or an array
|
||||||
|
// of aggregation stages. The third is a different machine from the
|
||||||
|
// first two and stays separate all the way down.
|
||||||
|
const u_value = spec.get("u") orelse return bad_value(reply, "update spec requires u");
|
||||||
|
const u_pipeline: ?[]const bson.Value = switch (u_value) {
|
||||||
|
.array => |a| a,
|
||||||
|
else => null,
|
||||||
|
};
|
||||||
|
const u_doc = if (u_pipeline == null)
|
||||||
|
doc_arg(u_value) orelse return bad_value(reply, "update spec requires u")
|
||||||
|
else
|
||||||
|
&.{};
|
||||||
const multi = bool_arg(spec.get("multi")) orelse false;
|
const multi = bool_arg(spec.get("multi")) orelse false;
|
||||||
const upsert = bool_arg(spec.get("upsert")) orelse false;
|
const upsert = bool_arg(spec.get("upsert")) orelse false;
|
||||||
// `sort` on an update spec picks *which* match to write when the filter
|
// `sort` on an update spec picks *which* match to write when the filter
|
||||||
@@ -1969,7 +1986,7 @@ fn cmd_update(ctx: *Context, msg: *wire.Message, reply: *wire.Reply) !void {
|
|||||||
// A replacement describes one document, so there is no coherent meaning
|
// A replacement describes one document, so there is no coherent meaning
|
||||||
// for applying it to many: every match would end up identical apart from
|
// for applying it to many: every match would end up identical apart from
|
||||||
// its `_id`. MongoDB rejects the combination rather than doing that.
|
// its `_id`. MongoDB rejects the combination rather than doing that.
|
||||||
if (multi and update.is_replacement(u_doc)) {
|
if (u_pipeline == null and multi and update.is_replacement(u_doc)) {
|
||||||
return failed_to_parse(reply, "multi update is not supported for replacement-style update");
|
return failed_to_parse(reply, "multi update is not supported for replacement-style update");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1988,7 +2005,13 @@ fn cmd_update(ctx: *Context, msg: *wire.Message, reply: *wire.Reply) !void {
|
|||||||
// binds is refused whether or not it would have matched anything, and
|
// binds is refused whether or not it would have matched anything, and
|
||||||
// an array filter the update never uses is refused even when the whole
|
// an array filter the update never uses is refused even when the whole
|
||||||
// command was a no-op. Both measured.
|
// command was a no-op. Both measured.
|
||||||
update.validate(u_doc, opts) catch |err| return update_refusal(reply, err, diag);
|
if (u_pipeline == null) {
|
||||||
|
update.validate(u_doc, opts) catch |err| return update_refusal(reply, err, diag);
|
||||||
|
} else if (opts.array_filters.len > 0) {
|
||||||
|
// Not "ignored": an identifier a pipeline cannot spell would be a
|
||||||
|
// silently different update from the one the client wrote.
|
||||||
|
return failed_to_parse(reply, "arrayFilters may not be specified for pipeline-style updates");
|
||||||
|
}
|
||||||
|
|
||||||
var matched: std.ArrayListUnmanaged(u64) = .empty;
|
var matched: std.ArrayListUnmanaged(u64) = .empty;
|
||||||
defer matched.deinit(ctx.gpa);
|
defer matched.deinit(ctx.gpa);
|
||||||
@@ -1996,8 +2019,7 @@ fn cmd_update(ctx: *Context, msg: *wire.Message, reply: *wire.Reply) !void {
|
|||||||
|
|
||||||
if (matched.items.len == 0) {
|
if (matched.items.len == 0) {
|
||||||
if (upsert) {
|
if (upsert) {
|
||||||
const new_doc = build_upsert_doc(reply, ctx, q, u_doc, opts) catch |err|
|
const new_doc = (try build_upsert_doc(reply, ctx, q, u_doc, u_pipeline, opts, &diag)) orelse return;
|
||||||
return update_refusal(reply, err, diag);
|
|
||||||
ctx.engine.insert(db_name, coll_name, new_doc, ctx.oid_gen) catch |err| switch (err) {
|
ctx.engine.insert(db_name, coll_name, new_doc, ctx.oid_gen) catch |err| switch (err) {
|
||||||
error.DuplicateKey, error.DuplicateKeyIndex => return duplicate_key_error(ctx, reply, db_name, coll_name, new_doc),
|
error.DuplicateKey, error.DuplicateKeyIndex => return duplicate_key_error(ctx, reply, db_name, coll_name, new_doc),
|
||||||
else => return err,
|
else => return err,
|
||||||
@@ -2018,9 +2040,14 @@ fn cmd_update(ctx: *Context, msg: *wire.Message, reply: *wire.Reply) !void {
|
|||||||
// Work on a copy: the log write must precede any visible change,
|
// Work on a copy: the log write must precede any visible change,
|
||||||
// and a rejected update must not corrupt the stored document.
|
// and a rejected update must not corrupt the stored document.
|
||||||
const doc = try doc_tree(reply.arena_alloc(), coll, off);
|
const doc = try doc_tree(reply.arena_alloc(), coll, off);
|
||||||
const copy = try clone_doc(reply, doc);
|
const copy = if (u_pipeline) |stages|
|
||||||
update.apply(copy, &.{ .arena = undefined, .pairs = u_doc }, opts) catch |err|
|
(try apply_update_pipeline(ctx, reply, stages, doc, coll)) orelse return
|
||||||
return update_refusal(reply, err, diag);
|
else blk: {
|
||||||
|
const c = try clone_doc(reply, doc);
|
||||||
|
update.apply(c, &.{ .arena = undefined, .pairs = u_doc }, opts) catch |err|
|
||||||
|
return update_refusal(reply, err, diag);
|
||||||
|
break :blk c;
|
||||||
|
};
|
||||||
const written = ctx.engine.replace(db_name, coll_name, copy, ctx.oid_gen) catch |err| switch (err) {
|
const written = ctx.engine.replace(db_name, coll_name, copy, ctx.oid_gen) catch |err| switch (err) {
|
||||||
error.DuplicateKey, error.DuplicateKeyIndex => {
|
error.DuplicateKey, error.DuplicateKeyIndex => {
|
||||||
const e = try reply.arena_alloc().alloc(bson.Pair, 3);
|
const e = try reply.arena_alloc().alloc(bson.Pair, 3);
|
||||||
@@ -2110,8 +2137,19 @@ fn cmd_find_and_modify(ctx: *Context, msg: *wire.Message, reply: *wire.Reply) !v
|
|||||||
.now_ms = std.Io.Timestamp.now(ctx.io, .real).toMilliseconds(),
|
.now_ms = std.Io.Timestamp.now(ctx.io, .real).toMilliseconds(),
|
||||||
.diag = &diag,
|
.diag = &diag,
|
||||||
};
|
};
|
||||||
if (doc_arg(msg.body.get("update"))) |u| {
|
// The same three shapes `update`'s `u` takes, read once here.
|
||||||
update.validate(u, opts) catch |err| return update_refusal(reply, err, diag);
|
const u_pipeline: ?[]const bson.Value = switch (msg.body.get("update") orelse bson.Value.null) {
|
||||||
|
.array => |a| a,
|
||||||
|
else => null,
|
||||||
|
};
|
||||||
|
const u_doc: []const bson.Pair = if (u_pipeline != null) &.{} else doc_arg(msg.body.get("update")) orelse &.{};
|
||||||
|
if (u_pipeline == null) {
|
||||||
|
if (do_update and doc_arg(msg.body.get("update")) == null) {
|
||||||
|
return bad_value(reply, "update must be a document");
|
||||||
|
}
|
||||||
|
update.validate(u_doc, opts) catch |err| return update_refusal(reply, err, diag);
|
||||||
|
} else if (opts.array_filters.len > 0) {
|
||||||
|
return failed_to_parse(reply, "arrayFilters may not be specified for pipeline-style updates");
|
||||||
}
|
}
|
||||||
|
|
||||||
var matched: std.ArrayListUnmanaged(u64) = .empty;
|
var matched: std.ArrayListUnmanaged(u64) = .empty;
|
||||||
@@ -2137,9 +2175,7 @@ fn cmd_find_and_modify(ctx: *Context, msg: *wire.Message, reply: *wire.Reply) !v
|
|||||||
var value: bson.Value = .null;
|
var value: bson.Value = .null;
|
||||||
|
|
||||||
if (target == null and do_update and upsert) {
|
if (target == null and do_update and upsert) {
|
||||||
const u_doc = doc_arg(msg.body.get("update")) orelse return bad_value(reply, "update must be a document");
|
const new_doc = (try build_upsert_doc(reply, ctx, q, u_doc, u_pipeline, opts, &diag)) orelse return;
|
||||||
const new_doc = build_upsert_doc(reply, ctx, q, u_doc, opts) catch |err|
|
|
||||||
return update_refusal(reply, err, diag);
|
|
||||||
ctx.engine.insert(db_name, coll_name, new_doc, ctx.oid_gen) catch |err| switch (err) {
|
ctx.engine.insert(db_name, coll_name, new_doc, ctx.oid_gen) catch |err| switch (err) {
|
||||||
error.DuplicateKey, error.DuplicateKeyIndex => return duplicate_key_error(ctx, reply, db_name, coll_name, new_doc),
|
error.DuplicateKey, error.DuplicateKeyIndex => return duplicate_key_error(ctx, reply, db_name, coll_name, new_doc),
|
||||||
else => return err,
|
else => return err,
|
||||||
@@ -2153,11 +2189,15 @@ fn cmd_find_and_modify(ctx: *Context, msg: *wire.Message, reply: *wire.Reply) !v
|
|||||||
value = try project_doc(reply, target.?, proj_pairs);
|
value = try project_doc(reply, target.?, proj_pairs);
|
||||||
_ = try ctx.engine.remove_by_id(db_name, coll_name, target.?.get("_id") orelse unreachable);
|
_ = try ctx.engine.remove_by_id(db_name, coll_name, target.?.get("_id") orelse unreachable);
|
||||||
} else if (target != null and do_update) {
|
} else if (target != null and do_update) {
|
||||||
const u_doc = doc_arg(msg.body.get("update")) orelse return bad_value(reply, "update must be a document");
|
|
||||||
const before = try bson.copy_pairs(arena, target.?.pairs);
|
const before = try bson.copy_pairs(arena, target.?.pairs);
|
||||||
const copy = try clone_doc(reply, target.?);
|
const copy = if (u_pipeline) |stages|
|
||||||
update.apply(copy, &.{ .arena = undefined, .pairs = u_doc }, opts) catch |err|
|
(try apply_update_pipeline(ctx, reply, stages, target.?, coll)) orelse return
|
||||||
return update_refusal(reply, err, diag);
|
else blk: {
|
||||||
|
const c = try clone_doc(reply, target.?);
|
||||||
|
update.apply(c, &.{ .arena = undefined, .pairs = u_doc }, opts) catch |err|
|
||||||
|
return update_refusal(reply, err, diag);
|
||||||
|
break :blk c;
|
||||||
|
};
|
||||||
// findAndModify reports `n` (matched) and `updatedExisting`, neither of
|
// findAndModify reports `n` (matched) and `updatedExisting`, neither of
|
||||||
// which distinguishes a no-op, so whether it wrote is not needed here.
|
// which distinguishes a no-op, so whether it wrote is not needed here.
|
||||||
_ = try ctx.engine.replace(db_name, coll_name, copy, ctx.oid_gen);
|
_ = try ctx.engine.replace(db_name, coll_name, copy, ctx.oid_gen);
|
||||||
@@ -2487,6 +2527,7 @@ fn cmd_aggregate(ctx: *Context, msg: *wire.Message, reply: *wire.Reply) !void {
|
|||||||
end = trees.items.len;
|
end = trees.items.len;
|
||||||
} else if (std.mem.eql(u8, stage_name, "$addFields") or std.mem.eql(u8, stage_name, "$set") or
|
} else if (std.mem.eql(u8, stage_name, "$addFields") or std.mem.eql(u8, stage_name, "$set") or
|
||||||
std.mem.eql(u8, stage_name, "$unset") or std.mem.eql(u8, stage_name, "$replaceRoot") or
|
std.mem.eql(u8, stage_name, "$unset") or std.mem.eql(u8, stage_name, "$replaceRoot") or
|
||||||
|
std.mem.eql(u8, stage_name, "$replaceWith") or
|
||||||
std.mem.eql(u8, stage_name, "$unwind") or std.mem.eql(u8, stage_name, "$project"))
|
std.mem.eql(u8, stage_name, "$unwind") or std.mem.eql(u8, stage_name, "$project"))
|
||||||
{
|
{
|
||||||
// The stages that rewrite a document, all one shape: read the
|
// The stages that rewrite a document, all one shape: read the
|
||||||
@@ -2841,13 +2882,17 @@ fn path_in_pairs(pairs: []const bson.Pair, path: []const u8) ?bson.Value {
|
|||||||
/// not build a million trees to read one field.
|
/// not build a million trees to read one field.
|
||||||
fn stream_path(
|
fn stream_path(
|
||||||
gpa: std.mem.Allocator,
|
gpa: std.mem.Allocator,
|
||||||
coll: *const Collection,
|
/// Null when the stream is materialized. An `.offsets` stream is the only
|
||||||
|
/// form that needs one, because only it reads the slab -- which is what
|
||||||
|
/// lets a pipeline-style update evaluate expressions against a document
|
||||||
|
/// held in memory, before the collection it will be inserted into exists.
|
||||||
|
coll: ?*const Collection,
|
||||||
src: Stream,
|
src: Stream,
|
||||||
i: usize,
|
i: usize,
|
||||||
path: []const u8,
|
path: []const u8,
|
||||||
) !?bson.Value {
|
) !?bson.Value {
|
||||||
return switch (src) {
|
return switch (src) {
|
||||||
.offsets => |o| try query_path_value_bytes(gpa, coll.doc_bytes(o[i]), path),
|
.offsets => |o| try query_path_value_bytes(gpa, coll.?.doc_bytes(o[i]), path),
|
||||||
.docs => |d| path_in_pairs(d[i].pairs, path),
|
.docs => |d| path_in_pairs(d[i].pairs, path),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -3232,6 +3277,13 @@ fn compile_rewrite(
|
|||||||
};
|
};
|
||||||
return Rewrite{ .replace_root = (try compile_expr(reply, arena, new_root, "$replaceRoot's newRoot")) orelse return null };
|
return Rewrite{ .replace_root = (try compile_expr(reply, arena, new_root, "$replaceRoot's newRoot")) orelse return null };
|
||||||
}
|
}
|
||||||
|
// `$replaceWith` is `$replaceRoot` with the expression in place of the
|
||||||
|
// `{newRoot: ...}` wrapper -- one stage under two spellings, like
|
||||||
|
// `$addFields` and `$set`. Reached from `aggregate` as well as from a
|
||||||
|
// pipeline-style update, since the compiler is shared.
|
||||||
|
if (std.mem.eql(u8, name, "$replaceWith")) {
|
||||||
|
return Rewrite{ .replace_root = (try compile_expr(reply, arena, spec, "$replaceWith's expression")) orelse return null };
|
||||||
|
}
|
||||||
const d = doc_arg(spec) orelse {
|
const d = doc_arg(spec) orelse {
|
||||||
try bad_value(reply, "$addFields requires a document");
|
try bad_value(reply, "$addFields requires a document");
|
||||||
return null;
|
return null;
|
||||||
@@ -3498,7 +3550,7 @@ fn report_eval_error(reply: *wire.Reply, err: EvalError) !void {
|
|||||||
/// Where an expression reads its document from.
|
/// Where an expression reads its document from.
|
||||||
const EvalCtx = struct {
|
const EvalCtx = struct {
|
||||||
arena: std.mem.Allocator,
|
arena: std.mem.Allocator,
|
||||||
coll: *const Collection,
|
coll: ?*const Collection,
|
||||||
src: Stream,
|
src: Stream,
|
||||||
i: usize,
|
i: usize,
|
||||||
};
|
};
|
||||||
@@ -4426,6 +4478,117 @@ fn wrong_type_dynamic(
|
|||||||
return reply.put_error(@intFromEnum(ErrorCode.type_mismatch), "TypeMismatch", text);
|
return reply.put_error(@intFromEnum(ErrorCode.type_mismatch), "TypeMismatch", text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The stages an update may be written out of. Every one rewrites a single
|
||||||
|
/// document into a single document, which is what makes them usable here:
|
||||||
|
/// `$match` could drop it, `$group` and `$unwind` could change how many there
|
||||||
|
/// are, and `$sort` means nothing to one. mongod refuses those four by name
|
||||||
|
/// rather than by shape, and so does this.
|
||||||
|
const update_pipeline_stages = [_][]const u8{
|
||||||
|
"$addFields", "$set", "$project", "$unset", "$replaceRoot", "$replaceWith",
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Apply a pipeline-style update to one document, returning the new one.
|
||||||
|
///
|
||||||
|
/// Returns null having written the error reply, like the other `*_arg`
|
||||||
|
/// helpers. `coll` may be null: the stages are fed a materialized document, so
|
||||||
|
/// nothing reads the slab, and the upsert path has no collection yet.
|
||||||
|
fn apply_update_pipeline(
|
||||||
|
ctx: *Context,
|
||||||
|
reply: *wire.Reply,
|
||||||
|
stages: []const bson.Value,
|
||||||
|
doc: *const bson.Document,
|
||||||
|
coll: ?*const Collection,
|
||||||
|
) !?*const bson.Document {
|
||||||
|
const arena = reply.arena_alloc();
|
||||||
|
// The `_id` the document came in with. Every stage may drop it -- a
|
||||||
|
// `$replaceRoot` almost always does -- and it comes back afterwards,
|
||||||
|
// because a pipeline update rewrites a document rather than replacing one
|
||||||
|
// document with another. Measured on all six stages.
|
||||||
|
const original_id = doc.get("_id");
|
||||||
|
var current = doc;
|
||||||
|
for (stages) |stage| {
|
||||||
|
const spec = doc_arg(stage) orelse {
|
||||||
|
try failed_to_parse(reply, "each element of a pipeline update must be a document");
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
if (spec.len != 1) {
|
||||||
|
try reply.put_error(
|
||||||
|
@intFromEnum(ErrorCode.location_stage_needs_one_field),
|
||||||
|
"Location40323",
|
||||||
|
"A pipeline stage specification object must contain exactly one field.",
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const name = spec[0].key;
|
||||||
|
if (!contains_name(&update_pipeline_stages, name)) {
|
||||||
|
if (is_known_pipeline_stage(name)) {
|
||||||
|
try reply.put_error(
|
||||||
|
@intFromEnum(ErrorCode.invalid_options),
|
||||||
|
"InvalidOptions",
|
||||||
|
try std.fmt.allocPrint(arena, "{s} is not allowed to be used within an update", .{name}),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
try reply.put_error(
|
||||||
|
@intFromEnum(ErrorCode.location_unrecognized_stage),
|
||||||
|
"Location40324",
|
||||||
|
try std.fmt.allocPrint(arena, "Unrecognized pipeline stage name: '{s}'", .{name}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const rewrite = try compile_rewrite(ctx, reply, arena, name, spec[0].value) orelse return null;
|
||||||
|
var built: std.ArrayListUnmanaged(*const bson.Document) = .empty;
|
||||||
|
defer built.deinit(ctx.gpa);
|
||||||
|
const ec: EvalCtx = .{ .arena = arena, .coll = coll, .src = .{ .docs = (¤t)[0..1] }, .i = 0 };
|
||||||
|
apply_rewrite(ec, rewrite, current, &built, ctx.gpa) catch |err| {
|
||||||
|
try report_eval_error(reply, err);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
// Every stage here is 1->1, so this holds by construction; it is an
|
||||||
|
// assertion rather than a branch because a stage that broke it would
|
||||||
|
// silently drop or duplicate the document being updated.
|
||||||
|
assert(built.items.len == 1);
|
||||||
|
current = built.items[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (original_id) |id| {
|
||||||
|
if (current.get("_id")) |now| {
|
||||||
|
if (bson.compare(id, now) != .eq) {
|
||||||
|
try reply.put_error(
|
||||||
|
@intFromEnum(ErrorCode.immutable_field),
|
||||||
|
"ImmutableField",
|
||||||
|
"After applying the update, the (immutable) field '_id' was found to have been altered",
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const with_id = try arena.alloc(bson.Pair, current.pairs.len + 1);
|
||||||
|
with_id[0] = .{ .key = "_id", .value = id };
|
||||||
|
@memcpy(with_id[1..], current.pairs);
|
||||||
|
const owned = try arena.create(bson.Document);
|
||||||
|
owned.* = .{ .arena = std.heap.ArenaAllocator.init(arena), .pairs = with_id };
|
||||||
|
current = owned;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains_name(names: []const []const u8, name: []const u8) bool {
|
||||||
|
for (names) |n| if (std.mem.eql(u8, n, name)) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether a name is a stage this server knows anywhere, which is the whole of
|
||||||
|
/// the difference between "not here" (72) and "not at all" (40324).
|
||||||
|
fn is_known_pipeline_stage(name: []const u8) bool {
|
||||||
|
const known = [_][]const u8{
|
||||||
|
"$match", "$group", "$sort", "$limit", "$skip",
|
||||||
|
"$unwind", "$count", "$out", "$merge", "$lookup",
|
||||||
|
"$facet", "$sample", "$sortByCount", "$documents",
|
||||||
|
};
|
||||||
|
return contains_name(&known, name);
|
||||||
|
}
|
||||||
|
|
||||||
/// Build the document for an upsert: equality fields from the filter, then
|
/// Build the document for an upsert: equality fields from the filter, then
|
||||||
/// the update operators applied. Owned by the reply arena.
|
/// the update operators applied. Owned by the reply arena.
|
||||||
///
|
///
|
||||||
@@ -4441,8 +4604,10 @@ fn build_upsert_doc(
|
|||||||
ctx: *Context,
|
ctx: *Context,
|
||||||
q: []const bson.Pair,
|
q: []const bson.Pair,
|
||||||
u_doc: []const bson.Pair,
|
u_doc: []const bson.Pair,
|
||||||
|
u_pipeline: ?[]const bson.Value,
|
||||||
opts: update.Options,
|
opts: update.Options,
|
||||||
) !*bson.Document {
|
diag: *update.Diagnostic,
|
||||||
|
) !?*const bson.Document {
|
||||||
const arena = reply.arena_alloc();
|
const arena = reply.arena_alloc();
|
||||||
var pairs: std.ArrayListUnmanaged(bson.Pair) = .empty;
|
var pairs: std.ArrayListUnmanaged(bson.Pair) = .empty;
|
||||||
defer pairs.deinit(arena);
|
defer pairs.deinit(arena);
|
||||||
@@ -4458,20 +4623,34 @@ fn build_upsert_doc(
|
|||||||
// Apply update operators to build the final doc; _id handled by insert.
|
// Apply update operators to build the final doc; _id handled by insert.
|
||||||
// `inserting` is what `$setOnInsert` asks about, and this is the only
|
// `inserting` is what `$setOnInsert` asks about, and this is the only
|
||||||
// caller that answers yes.
|
// caller that answers yes.
|
||||||
var insert_opts = opts;
|
// A pipeline runs over the document the filter implies, exactly as it
|
||||||
insert_opts.inserting = true;
|
// would over a stored one; operators get the `inserting` bit, which is
|
||||||
try update.apply(owned, &.{ .arena = undefined, .pairs = u_doc }, insert_opts);
|
// what `$setOnInsert` asks about and this is the only caller that answers
|
||||||
// After the operators, because `$setOnInsert` may supply the `_id` itself
|
// yes.
|
||||||
// and a generated one would then be the wrong answer. At the front,
|
var built: *const bson.Document = owned;
|
||||||
// because that is where MongoDB stores it and where the `_id_` index
|
if (u_pipeline) |stages| {
|
||||||
// descends on it.
|
built = (try apply_update_pipeline(ctx, reply, stages, owned, null)) orelse return null;
|
||||||
if (owned.get("_id") == null) {
|
} else {
|
||||||
const with_id = try arena.alloc(bson.Pair, owned.pairs.len + 1);
|
var insert_opts = opts;
|
||||||
with_id[0] = .{ .key = "_id", .value = .{ .object_id = ctx.oid_gen.new(ctx.io) } };
|
insert_opts.inserting = true;
|
||||||
@memcpy(with_id[1..], owned.pairs);
|
update.apply(owned, &.{ .arena = undefined, .pairs = u_doc }, insert_opts) catch |err| {
|
||||||
owned.pairs = with_id;
|
try update_refusal(reply, err, diag.*);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return owned;
|
// After the update, because `$setOnInsert` may supply the `_id` itself and
|
||||||
|
// a generated one would then be the wrong answer. At the front, because
|
||||||
|
// that is where MongoDB stores it and where the `_id_` index descends on
|
||||||
|
// it.
|
||||||
|
if (built.get("_id") == null) {
|
||||||
|
const with_id = try arena.alloc(bson.Pair, built.pairs.len + 1);
|
||||||
|
with_id[0] = .{ .key = "_id", .value = .{ .object_id = ctx.oid_gen.new(ctx.io) } };
|
||||||
|
@memcpy(with_id[1..], built.pairs);
|
||||||
|
const owned_id = try arena.create(bson.Document);
|
||||||
|
owned_id.* = .{ .arena = std.heap.ArenaAllocator.init(arena), .pairs = with_id };
|
||||||
|
built = owned_id;
|
||||||
|
}
|
||||||
|
return built;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_sort_keys(reply: *wire.Reply, value: ?bson.Value) ![]const query.SortKey {
|
fn parse_sort_keys(reply: *wire.Reply, value: ?bson.Value) ![]const query.SortKey {
|
||||||
@@ -7155,3 +7334,143 @@ test "an upsert reports the _id it generated" {
|
|||||||
const upserted2 = bson.get_pair(reply2.pairs.items, "upserted").?.array;
|
const upserted2 = bson.get_pair(reply2.pairs.items, "upserted").?.array;
|
||||||
try testing.expectEqual(@as(i32, 9), bson.get_pair(upserted2[0].doc, "_id").?.int32);
|
try testing.expectEqual(@as(i32, 9), bson.get_pair(upserted2[0].doc, "_id").?.int32);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "a pipeline-style update keeps the _id its stages dropped" {
|
||||||
|
// `$replaceRoot` almost always drops the `_id`, and a pipeline update
|
||||||
|
// rewrites a document rather than replacing one document with another --
|
||||||
|
// so the `_id` comes back. Mutation check: delete the restore block in
|
||||||
|
// `apply_update_pipeline` and the document becomes unfindable by the id
|
||||||
|
// it was stored under.
|
||||||
|
var threaded: std.Io.Threaded = .init_single_threaded;
|
||||||
|
defer threaded.deinit();
|
||||||
|
const io = threaded.io();
|
||||||
|
var tdb = try TestDb.init(io);
|
||||||
|
defer tdb.deinit();
|
||||||
|
var ctx = tdb.ctx(io);
|
||||||
|
|
||||||
|
try dispatch_insert(&tdb, io, "pl", &.{
|
||||||
|
.{ .doc = &.{
|
||||||
|
.{ .key = "_id", .value = .{ .int32 = 1 } },
|
||||||
|
.{ .key = "x", .value = .{ .int32 = 1 } },
|
||||||
|
.{ .key = "t", .value = .{ .doc = &.{.{ .key = "u", .value = .{ .int32 = 7 } }} } },
|
||||||
|
} },
|
||||||
|
});
|
||||||
|
|
||||||
|
const stages = [_]bson.Value{
|
||||||
|
.{ .doc = &.{.{ .key = "$replaceRoot", .value = .{ .doc = &.{
|
||||||
|
.{ .key = "newRoot", .value = .{ .string = "$t" } },
|
||||||
|
} } }} },
|
||||||
|
.{ .doc = &.{.{ .key = "$addFields", .value = .{ .doc = &.{
|
||||||
|
.{ .key = "foo", .value = .{ .int32 = 1 } },
|
||||||
|
} } }} },
|
||||||
|
};
|
||||||
|
const updates = [_]bson.Value{.{ .doc = &.{
|
||||||
|
.{ .key = "q", .value = .{ .doc = &.{} } },
|
||||||
|
.{ .key = "u", .value = .{ .array = &stages } },
|
||||||
|
} }};
|
||||||
|
try testing.expectEqual(@as(?i32, null), try run_for_code(&ctx, "update", .{ .string = "pl" }, &.{
|
||||||
|
.{ .key = "updates", .value = .{ .array = &updates } },
|
||||||
|
}));
|
||||||
|
|
||||||
|
var reply = wire.Reply.init(testing.allocator);
|
||||||
|
defer reply.deinit();
|
||||||
|
const ids = try distinct_values(&tdb, io, &reply, "pl", &.{
|
||||||
|
.{ .key = "key", .value = .{ .string = "_id" } },
|
||||||
|
});
|
||||||
|
try testing.expectEqual(@as(usize, 1), ids.len);
|
||||||
|
try testing.expectEqual(@as(i32, 1), ids[0].int32);
|
||||||
|
// And the stages did run, in order: `u` came from `$t`, `foo` from the
|
||||||
|
// stage after it. A second reply, because `put` appends and a reused one
|
||||||
|
// would answer with the first call's `values`.
|
||||||
|
var reply2 = wire.Reply.init(testing.allocator);
|
||||||
|
defer reply2.deinit();
|
||||||
|
const us = try distinct_values(&tdb, io, &reply2, "pl", &.{
|
||||||
|
.{ .key = "key", .value = .{ .string = "u" } },
|
||||||
|
});
|
||||||
|
try testing.expectEqual(@as(i32, 7), us[0].int32);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "a pipeline-style update refuses what it cannot be written out of" {
|
||||||
|
var threaded: std.Io.Threaded = .init_single_threaded;
|
||||||
|
defer threaded.deinit();
|
||||||
|
const io = threaded.io();
|
||||||
|
var tdb = try TestDb.init(io);
|
||||||
|
defer tdb.deinit();
|
||||||
|
var ctx = tdb.ctx(io);
|
||||||
|
|
||||||
|
try dispatch_insert(&tdb, io, "plr", &.{
|
||||||
|
.{ .doc = &.{
|
||||||
|
.{ .key = "_id", .value = .{ .int32 = 1 } },
|
||||||
|
.{ .key = "x", .value = .{ .int32 = 1 } },
|
||||||
|
} },
|
||||||
|
});
|
||||||
|
|
||||||
|
// A real stage refused here (72) reads differently from a name that is no
|
||||||
|
// stage at all (40324), and mongod distinguishes them -- so this does.
|
||||||
|
const cases = [_]struct { stage: bson.Pair, code: i32 }{
|
||||||
|
.{ .stage = .{ .key = "$match", .value = .{ .doc = &.{} } }, .code = 72 },
|
||||||
|
.{ .stage = .{ .key = "$group", .value = .{ .doc = &.{} } }, .code = 72 },
|
||||||
|
.{ .stage = .{ .key = "$unwind", .value = .{ .string = "$x" } }, .code = 72 },
|
||||||
|
.{ .stage = .{ .key = "$bogus", .value = .{ .doc = &.{} } }, .code = 40324 },
|
||||||
|
};
|
||||||
|
for (cases) |c| {
|
||||||
|
const one = [_]bson.Pair{c.stage};
|
||||||
|
const stages = [_]bson.Value{.{ .doc = &one }};
|
||||||
|
const updates = [_]bson.Value{.{ .doc = &.{
|
||||||
|
.{ .key = "q", .value = .{ .doc = &.{} } },
|
||||||
|
.{ .key = "u", .value = .{ .array = &stages } },
|
||||||
|
} }};
|
||||||
|
try testing.expectEqual(@as(?i32, c.code), try run_for_code(&ctx, "update", .{ .string = "plr" }, &.{
|
||||||
|
.{ .key = "updates", .value = .{ .array = &updates } },
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Two stages in one element, and a stage that changes the `_id`.
|
||||||
|
const packed_stage = [_]bson.Value{.{ .doc = &.{
|
||||||
|
.{ .key = "$addFields", .value = .{ .doc = &.{.{ .key = "a", .value = .{ .int32 = 1 } }} } },
|
||||||
|
.{ .key = "$unset", .value = .{ .string = "x" } },
|
||||||
|
} }};
|
||||||
|
const packed_updates = [_]bson.Value{.{ .doc = &.{
|
||||||
|
.{ .key = "q", .value = .{ .doc = &.{} } },
|
||||||
|
.{ .key = "u", .value = .{ .array = &packed_stage } },
|
||||||
|
} }};
|
||||||
|
try testing.expectEqual(@as(?i32, 40323), try run_for_code(&ctx, "update", .{ .string = "plr" }, &.{
|
||||||
|
.{ .key = "updates", .value = .{ .array = &packed_updates } },
|
||||||
|
}));
|
||||||
|
|
||||||
|
const reid = [_]bson.Value{.{ .doc = &.{.{ .key = "$addFields", .value = .{ .doc = &.{
|
||||||
|
.{ .key = "_id", .value = .{ .int32 = 9 } },
|
||||||
|
} } }} }};
|
||||||
|
const reid_updates = [_]bson.Value{.{ .doc = &.{
|
||||||
|
.{ .key = "q", .value = .{ .doc = &.{} } },
|
||||||
|
.{ .key = "u", .value = .{ .array = &reid } },
|
||||||
|
} }};
|
||||||
|
try testing.expectEqual(@as(?i32, 66), try run_for_code(&ctx, "update", .{ .string = "plr" }, &.{
|
||||||
|
.{ .key = "updates", .value = .{ .array = &reid_updates } },
|
||||||
|
}));
|
||||||
|
|
||||||
|
// `arrayFilters` has nothing to bind to in a pipeline, and is refused
|
||||||
|
// rather than ignored.
|
||||||
|
const ok_stage = [_]bson.Value{.{ .doc = &.{.{ .key = "$addFields", .value = .{ .doc = &.{
|
||||||
|
.{ .key = "a", .value = .{ .int32 = 1 } },
|
||||||
|
} } }} }};
|
||||||
|
const af_updates = [_]bson.Value{.{ .doc = &.{
|
||||||
|
.{ .key = "q", .value = .{ .doc = &.{} } },
|
||||||
|
.{ .key = "u", .value = .{ .array = &ok_stage } },
|
||||||
|
.{ .key = "arrayFilters", .value = .{ .array = &.{.{ .doc = &.{
|
||||||
|
.{ .key = "i.b", .value = .{ .int32 = 1 } },
|
||||||
|
} }} } },
|
||||||
|
} }};
|
||||||
|
try testing.expectEqual(@as(?i32, 9), try run_for_code(&ctx, "update", .{ .string = "plr" }, &.{
|
||||||
|
.{ .key = "updates", .value = .{ .array = &af_updates } },
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Nothing above wrote: the document is the one that was inserted.
|
||||||
|
var reply = wire.Reply.init(testing.allocator);
|
||||||
|
defer reply.deinit();
|
||||||
|
const xs = try distinct_values(&tdb, io, &reply, "plr", &.{
|
||||||
|
.{ .key = "key", .value = .{ .string = "x" } },
|
||||||
|
});
|
||||||
|
try testing.expectEqual(@as(usize, 1), xs.len);
|
||||||
|
try testing.expectEqual(@as(i32, 1), xs[0].int32);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
# The update-operator corpus
|
# The update-operator corpus
|
||||||
|
|
||||||
PLAN §3 lists eight update operators for M3 — `$setOnInsert`, `$addToSet`,
|
What an update *document* may be, measured. PLAN §3 lists eight operators for
|
||||||
`$mul`, `$min`, `$max`, `$pop`, `$pullAll`, `$currentDate` — and the pinned
|
M3 — `$setOnInsert`, `$addToSet`, `$mul`, `$min`, `$max`, `$pop`, `$pullAll`,
|
||||||
crud corpus says almost nothing about any of them. A probe running the
|
`$currentDate` — and pipeline-style updates beside them; the pinned crud
|
||||||
|
corpus says almost nothing about any of it. A probe running the
|
||||||
identical update against mongod 8.3.7 and this server found:
|
identical update against mongod 8.3.7 and this server found:
|
||||||
|
|
||||||
- all eight answering `bad update`, code 2, one message for every question;
|
- all eight answering `bad update`, code 2, one message for every question;
|
||||||
@@ -62,6 +63,7 @@ Recorded against mongod 8.3.7. Green:
|
|||||||
|
|
||||||
```
|
```
|
||||||
array-ops.json 26 pass 0 fail 0 skip
|
array-ops.json 26 pass 0 fail 0 skip
|
||||||
|
pipeline.json 23 pass 0 fail 0 skip
|
||||||
current-date.json 11 pass 0 fail 0 skip
|
current-date.json 11 pass 0 fail 0 skip
|
||||||
modifiers.json 14 pass 0 fail 0 skip
|
modifiers.json 14 pass 0 fail 0 skip
|
||||||
numeric.json 21 pass 0 fail 0 skip
|
numeric.json 21 pass 0 fail 0 skip
|
||||||
@@ -69,8 +71,8 @@ push-modifiers.json 21 pass 0 fail 0 skip
|
|||||||
set-on-insert.json 9 pass 0 fail 0 skip
|
set-on-insert.json 9 pass 0 fail 0 skip
|
||||||
```
|
```
|
||||||
|
|
||||||
It was recorded red — 18 pass / 84 fail against a server with none of these
|
It was recorded red — 18 pass / 84 fail for the operators, 0 / 23 for the
|
||||||
operators — and driven green by seven commits. The 18 that passed then were
|
pipeline file — and driven green by eight commits. The 18 that passed then were
|
||||||
the shapes this server already answered mongod's way, mostly refusals that
|
the shapes this server already answered mongod's way, mostly refusals that
|
||||||
happened to agree.
|
happened to agree.
|
||||||
|
|
||||||
@@ -103,7 +105,13 @@ None of this is guessable, and several rows contradict the obvious reading:
|
|||||||
| `$currentDate` with anything but a bool or `{$type: date\|timestamp}` | BadValue (2) |
|
| `$currentDate` with anything but a bool or `{$type: date\|timestamp}` | BadValue (2) |
|
||||||
| `$setOnInsert` writing `_id` on an insert | **allowed**, unlike `$set` |
|
| `$setOnInsert` writing `_id` on an insert | **allowed**, unlike `$set` |
|
||||||
| an unknown modifier | FailedToParse (9), not BadValue |
|
| an unknown modifier | FailedToParse (9), not BadValue |
|
||||||
|
| a pipeline update's `_id` | **always survives**, through `$replaceRoot` and through `$project: {_id: 0}` |
|
||||||
|
| a stage changing `_id` to something else | ImmutableField (66) |
|
||||||
|
| `$match`, `$group`, `$sort`, `$unwind` in an update | InvalidOptions (72) — real stages, refused *here* |
|
||||||
|
| a name that is no stage at all | 40324; two stages in one array element, 40323 |
|
||||||
|
| `arrayFilters` beside a pipeline | FailedToParse (9) |
|
||||||
|
|
||||||
One case was authored and then removed: `{b: 1, $set: {c: 1}}` is rejected by
|
Three cases were authored and then removed, all for the same reason: the
|
||||||
the driver before it reaches a server, so there is no server answer to record
|
driver rejects `{b: 1, $set: {c: 1}}`, an empty pipeline and a pipeline
|
||||||
and the case would have asserted nothing.
|
holding a non-document before any of them reaches a server, so there is no
|
||||||
|
server answer to record and the cases would have asserted nothing.
|
||||||
|
|||||||
1
tests/spec/operators/pipeline.json
Normal file
1
tests/spec/operators/pipeline.json
Normal file
File diff suppressed because one or more lines are too long
163
tests/spec/operators/sources/pipeline.json
Normal file
163
tests/spec/operators/sources/pipeline.json
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
{
|
||||||
|
"_comment": [
|
||||||
|
"Inputs only. Expectations are measured -- see record.js.",
|
||||||
|
"Pipeline-style updates: an update document that is an *array* of",
|
||||||
|
"aggregation stages rather than a document of operators. The pinned crud",
|
||||||
|
"corpus has five cases, one per command, and none of them is a refusal --",
|
||||||
|
"so which stages are allowed, what happens to `_id`, and what an upsert",
|
||||||
|
"does are all unmeasured there.",
|
||||||
|
"Two shapes were authored and removed: an empty pipeline and an array",
|
||||||
|
"holding a non-document. The driver rejects both before they reach a",
|
||||||
|
"server ('Update document requires atomic operators'), so there is no",
|
||||||
|
"server answer to record."
|
||||||
|
],
|
||||||
|
"documents": [
|
||||||
|
{ "_id": 1, "x": 1, "y": 1, "t": { "u": { "v": 1 } } },
|
||||||
|
{ "_id": 2, "x": 2, "y": 1 }
|
||||||
|
],
|
||||||
|
"cases": [
|
||||||
|
{
|
||||||
|
"description": "$replaceRoot keeps the _id it was given",
|
||||||
|
"operation": "updateOne",
|
||||||
|
"arguments": {
|
||||||
|
"filter": { "_id": 1 },
|
||||||
|
"update": [{ "$replaceRoot": { "newRoot": "$t" } }]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "$replaceWith is the same stage under its short name",
|
||||||
|
"operation": "updateOne",
|
||||||
|
"arguments": { "filter": { "_id": 1 }, "update": [{ "$replaceWith": "$t" }] }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "$project keeps the _id it did not mention",
|
||||||
|
"operation": "updateOne",
|
||||||
|
"arguments": { "filter": { "_id": 1 }, "update": [{ "$project": { "x": 1 } }] }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "$project excluding _id keeps it anyway",
|
||||||
|
"operation": "updateOne",
|
||||||
|
"arguments": { "filter": { "_id": 1 }, "update": [{ "$project": { "_id": 0, "x": 1 } }] }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "$addFields adds to the document",
|
||||||
|
"operation": "updateOne",
|
||||||
|
"arguments": { "filter": { "_id": 1 }, "update": [{ "$addFields": { "foo": 1 } }] }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "$set is $addFields under its other name",
|
||||||
|
"operation": "updateOne",
|
||||||
|
"arguments": { "filter": { "_id": 1 }, "update": [{ "$set": { "foo": "$x" } }] }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "$unset of one field",
|
||||||
|
"operation": "updateOne",
|
||||||
|
"arguments": { "filter": { "_id": 1 }, "update": [{ "$unset": "y" }] }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "$unset of several fields",
|
||||||
|
"operation": "updateOne",
|
||||||
|
"arguments": { "filter": { "_id": 1 }, "update": [{ "$unset": ["y", "t"] }] }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "the stages run in order, each on the last one's output",
|
||||||
|
"operation": "updateOne",
|
||||||
|
"arguments": {
|
||||||
|
"filter": { "_id": 1 },
|
||||||
|
"update": [{ "$replaceRoot": { "newRoot": "$t" } }, { "$addFields": { "foo": 1 } }]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "an expression reads the document the stage was handed",
|
||||||
|
"operation": "updateOne",
|
||||||
|
"arguments": {
|
||||||
|
"filter": { "_id": 1 },
|
||||||
|
"update": [{ "$addFields": { "z": { "$add": ["$x", "$y"] } } }]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "a stage may restate the _id it already has",
|
||||||
|
"operation": "updateOne",
|
||||||
|
"arguments": { "filter": { "_id": 1 }, "update": [{ "$addFields": { "_id": 1 } }] }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "a stage may not change the _id",
|
||||||
|
"operation": "updateOne",
|
||||||
|
"arguments": { "filter": { "_id": 1 }, "update": [{ "$addFields": { "_id": 9 } }] }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "updateMany runs the pipeline on every match",
|
||||||
|
"operation": "updateMany",
|
||||||
|
"arguments": {
|
||||||
|
"filter": {},
|
||||||
|
"update": [{ "$project": { "x": 1 } }, { "$addFields": { "foo": 1 } }]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "findOneAndUpdate with a pipeline",
|
||||||
|
"operation": "findOneAndUpdate",
|
||||||
|
"arguments": {
|
||||||
|
"filter": { "_id": 1 },
|
||||||
|
"update": [{ "$project": { "x": 1 } }, { "$addFields": { "foo": 1 } }],
|
||||||
|
"returnDocument": "after"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "an upsert runs the pipeline on the document the filter implies",
|
||||||
|
"documents": [],
|
||||||
|
"operation": "updateOne",
|
||||||
|
"arguments": {
|
||||||
|
"filter": { "k": 1 },
|
||||||
|
"update": [{ "$addFields": { "a": 1 } }],
|
||||||
|
"upsert": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "a pipeline that writes nothing new still reports a match",
|
||||||
|
"operation": "updateOne",
|
||||||
|
"arguments": { "filter": { "_id": 1 }, "update": [{ "$addFields": { "x": 1 } }] }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "$match is not allowed inside an update",
|
||||||
|
"operation": "updateOne",
|
||||||
|
"arguments": { "filter": { "_id": 1 }, "update": [{ "$match": { "x": 1 } }] }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "$group is not allowed inside an update",
|
||||||
|
"operation": "updateOne",
|
||||||
|
"arguments": { "filter": { "_id": 1 }, "update": [{ "$group": { "_id": null } }] }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "$sort is not allowed inside an update",
|
||||||
|
"operation": "updateOne",
|
||||||
|
"arguments": { "filter": { "_id": 1 }, "update": [{ "$sort": { "x": 1 } }] }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "$unwind is not allowed inside an update",
|
||||||
|
"operation": "updateOne",
|
||||||
|
"arguments": { "filter": { "_id": 1 }, "update": [{ "$unwind": "$t" }] }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "a stage name that is not a stage at all",
|
||||||
|
"operation": "updateOne",
|
||||||
|
"arguments": { "filter": { "_id": 1 }, "update": [{ "$bogus": {} }] }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "two stages packed into one array element",
|
||||||
|
"operation": "updateOne",
|
||||||
|
"arguments": {
|
||||||
|
"filter": { "_id": 1 },
|
||||||
|
"update": [{ "$addFields": { "a": 1 }, "$unset": "y" }]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "arrayFilters alongside a pipeline",
|
||||||
|
"operation": "updateOne",
|
||||||
|
"arguments": {
|
||||||
|
"filter": { "_id": 1 },
|
||||||
|
"update": [{ "$addFields": { "a": 1 } }],
|
||||||
|
"arrayFilters": [{ "i.b": 1 }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
# hasServerConnectionId, and maxTimeMS in an expected command (CSOT rewrites
|
# hasServerConnectionId, and maxTimeMS in an expected command (CSOT rewrites
|
||||||
# it -- the only assertion this runner declines to make).
|
# it -- the only assertion this runner declines to make).
|
||||||
|
|
||||||
total 218 pass 73 fail 196 skip 175 files 0 errored
|
total 228 pass 63 fail 196 skip 175 files 0 errored
|
||||||
|
|
||||||
# per-file: name pass fail skip
|
# per-file: name pass fail skip
|
||||||
aggregate-allowdiskuse.json 3 0 0
|
aggregate-allowdiskuse.json 3 0 0
|
||||||
@@ -54,13 +54,13 @@ bulkWrite-update-validation.json 3 0 0
|
|||||||
bulkWrite-updateMany-dots_and_dollars.json 0 0 4
|
bulkWrite-updateMany-dots_and_dollars.json 0 0 4
|
||||||
bulkWrite-updateMany-hint-unacknowledged.json 2 0 0
|
bulkWrite-updateMany-hint-unacknowledged.json 2 0 0
|
||||||
bulkWrite-updateMany-let.json 0 1 1
|
bulkWrite-updateMany-let.json 0 1 1
|
||||||
bulkWrite-updateMany-pipeline.json 0 1 0
|
bulkWrite-updateMany-pipeline.json 1 0 0
|
||||||
bulkWrite-updateMany-rawdata.json 0 1 1
|
bulkWrite-updateMany-rawdata.json 1 0 1
|
||||||
bulkWrite-updateOne-dots_and_dollars.json 0 0 4
|
bulkWrite-updateOne-dots_and_dollars.json 0 0 4
|
||||||
bulkWrite-updateOne-hint-unacknowledged.json 2 0 0
|
bulkWrite-updateOne-hint-unacknowledged.json 2 0 0
|
||||||
bulkWrite-updateOne-let.json 0 1 1
|
bulkWrite-updateOne-let.json 0 1 1
|
||||||
bulkWrite-updateOne-pipeline.json 0 1 0
|
bulkWrite-updateOne-pipeline.json 1 0 0
|
||||||
bulkWrite-updateOne-rawdata.json 0 1 1
|
bulkWrite-updateOne-rawdata.json 1 0 1
|
||||||
bulkWrite-updateOne-sort.json 1 0 1
|
bulkWrite-updateOne-sort.json 1 0 1
|
||||||
bulkWrite.json 10 0 0
|
bulkWrite.json 10 0 0
|
||||||
bypassDocumentValidation.json 4 5 0
|
bypassDocumentValidation.json 4 5 0
|
||||||
@@ -143,15 +143,15 @@ findOneAndReplace-upsert.json 2 2 0
|
|||||||
findOneAndReplace.json 4 2 0
|
findOneAndReplace.json 4 2 0
|
||||||
findOneAndUpdate-arrayFilters.json 3 0 0
|
findOneAndUpdate-arrayFilters.json 3 0 0
|
||||||
findOneAndUpdate-collation.json 0 1 0
|
findOneAndUpdate-collation.json 0 1 0
|
||||||
findOneAndUpdate-comment.json 0 2 1
|
findOneAndUpdate-comment.json 2 0 1
|
||||||
findOneAndUpdate-dots_and_dollars.json 0 0 4
|
findOneAndUpdate-dots_and_dollars.json 0 0 4
|
||||||
findOneAndUpdate-errorResponse.json 0 1 1
|
findOneAndUpdate-errorResponse.json 0 1 1
|
||||||
findOneAndUpdate-hint-serverError.json 0 0 2
|
findOneAndUpdate-hint-serverError.json 0 0 2
|
||||||
findOneAndUpdate-hint-unacknowledged.json 2 0 2
|
findOneAndUpdate-hint-unacknowledged.json 2 0 2
|
||||||
findOneAndUpdate-hint.json 2 0 0
|
findOneAndUpdate-hint.json 2 0 0
|
||||||
findOneAndUpdate-let.json 0 1 1
|
findOneAndUpdate-let.json 0 1 1
|
||||||
findOneAndUpdate-pipeline.json 0 1 0
|
findOneAndUpdate-pipeline.json 1 0 0
|
||||||
findOneAndUpdate-rawdata.json 0 1 1
|
findOneAndUpdate-rawdata.json 1 0 1
|
||||||
findOneAndUpdate.json 5 3 0
|
findOneAndUpdate.json 5 3 0
|
||||||
insertMany-comment.json 2 0 1
|
insertMany-comment.json 2 0 1
|
||||||
insertMany-dots_and_dollars.json 3 1 1
|
insertMany-dots_and_dollars.json 3 1 1
|
||||||
@@ -179,7 +179,7 @@ updateMany-dots_and_dollars.json 0 0 4
|
|||||||
updateMany-hint-unacknowledged.json 2 0 0
|
updateMany-hint-unacknowledged.json 2 0 0
|
||||||
updateMany-hint.json 2 0 0
|
updateMany-hint.json 2 0 0
|
||||||
updateMany-let.json 0 1 1
|
updateMany-let.json 0 1 1
|
||||||
updateMany-pipeline.json 0 1 0
|
updateMany-pipeline.json 1 0 0
|
||||||
updateMany-rawdata.json 1 0 1
|
updateMany-rawdata.json 1 0 1
|
||||||
updateMany-validation.json 1 0 0
|
updateMany-validation.json 1 0 0
|
||||||
updateMany.json 4 0 0
|
updateMany.json 4 0 0
|
||||||
@@ -191,7 +191,7 @@ updateOne-errorResponse.json 0 0 1
|
|||||||
updateOne-hint-unacknowledged.json 2 0 0
|
updateOne-hint-unacknowledged.json 2 0 0
|
||||||
updateOne-hint.json 2 0 0
|
updateOne-hint.json 2 0 0
|
||||||
updateOne-let.json 0 1 1
|
updateOne-let.json 0 1 1
|
||||||
updateOne-pipeline.json 0 1 0
|
updateOne-pipeline.json 1 0 0
|
||||||
updateOne-rawdata.json 1 0 1
|
updateOne-rawdata.json 1 0 1
|
||||||
updateOne-sort.json 1 0 1
|
updateOne-sort.json 1 0 1
|
||||||
updateOne-validation.json 1 0 0
|
updateOne-validation.json 1 0 0
|
||||||
@@ -237,19 +237,15 @@ bulkWrite-updateMany-dots_and_dollars.json SKIP Updating document to set top-lev
|
|||||||
bulkWrite-updateMany-dots_and_dollars.json SKIP Updating document to set dollar-prefixed key in embedded doc on 5.0+ server needs server >= 5.0
|
bulkWrite-updateMany-dots_and_dollars.json SKIP Updating document to set dollar-prefixed key in embedded doc on 5.0+ server needs server >= 5.0
|
||||||
bulkWrite-updateMany-dots_and_dollars.json SKIP Updating document to set dotted key in embedded doc on 5.0+ server needs server >= 5.0
|
bulkWrite-updateMany-dots_and_dollars.json SKIP Updating document to set dotted key in embedded doc on 5.0+ server needs server >= 5.0
|
||||||
bulkWrite-updateMany-let.json SKIP BulkWrite updateMany with let option needs server >= 5.0
|
bulkWrite-updateMany-let.json SKIP BulkWrite updateMany with let option needs server >= 5.0
|
||||||
bulkWrite-updateMany-let.json FAIL BulkWrite updateMany with let option unsupported (server-side error) bulkWrite: error message "update spec requires u" does not contain "'update.let' is an unknown field"
|
bulkWrite-updateMany-let.json FAIL BulkWrite updateMany with let option unsupported (server-side error) bulkWrite: expected an error, the operation succeeded
|
||||||
bulkWrite-updateMany-pipeline.json FAIL UpdateMany in bulk write using pipelines MongoBulkWriteError: update spec requires u
|
|
||||||
bulkWrite-updateMany-rawdata.json SKIP BulkWrite updateMany with rawData option needs server >= 8.2.0
|
bulkWrite-updateMany-rawdata.json SKIP BulkWrite updateMany with rawData option needs server >= 8.2.0
|
||||||
bulkWrite-updateMany-rawdata.json FAIL BulkWrite updateMany with rawData option on less than 8.2.0 - ignore argument MongoBulkWriteError: update spec requires u
|
|
||||||
bulkWrite-updateOne-dots_and_dollars.json SKIP Updating document to set top-level dollar-prefixed key on 5.0+ server needs server >= 5.0
|
bulkWrite-updateOne-dots_and_dollars.json SKIP Updating document to set top-level dollar-prefixed key on 5.0+ server needs server >= 5.0
|
||||||
bulkWrite-updateOne-dots_and_dollars.json SKIP Updating document to set top-level dotted key on 5.0+ server needs server >= 5.0
|
bulkWrite-updateOne-dots_and_dollars.json SKIP Updating document to set top-level dotted key on 5.0+ server needs server >= 5.0
|
||||||
bulkWrite-updateOne-dots_and_dollars.json SKIP Updating document to set dollar-prefixed key in embedded doc on 5.0+ server needs server >= 5.0
|
bulkWrite-updateOne-dots_and_dollars.json SKIP Updating document to set dollar-prefixed key in embedded doc on 5.0+ server needs server >= 5.0
|
||||||
bulkWrite-updateOne-dots_and_dollars.json SKIP Updating document to set dotted key in embedded doc on 5.0+ server needs server >= 5.0
|
bulkWrite-updateOne-dots_and_dollars.json SKIP Updating document to set dotted key in embedded doc on 5.0+ server needs server >= 5.0
|
||||||
bulkWrite-updateOne-let.json SKIP BulkWrite updateOne with let option needs server >= 5.0
|
bulkWrite-updateOne-let.json SKIP BulkWrite updateOne with let option needs server >= 5.0
|
||||||
bulkWrite-updateOne-let.json FAIL BulkWrite updateOne with let option unsupported (server-side error) bulkWrite: error message "update spec requires u" does not contain "'update.let' is an unknown field"
|
bulkWrite-updateOne-let.json FAIL BulkWrite updateOne with let option unsupported (server-side error) bulkWrite: expected an error, the operation succeeded
|
||||||
bulkWrite-updateOne-pipeline.json FAIL UpdateOne in bulk write using pipelines MongoBulkWriteError: update spec requires u
|
|
||||||
bulkWrite-updateOne-rawdata.json SKIP BulkWrite updateOne with rawData option needs server >= 8.2.0
|
bulkWrite-updateOne-rawdata.json SKIP BulkWrite updateOne with rawData option needs server >= 8.2.0
|
||||||
bulkWrite-updateOne-rawdata.json FAIL BulkWrite updateOne with rawData option on less than 8.2.0 - ignore argument MongoBulkWriteError: update spec requires u
|
|
||||||
bulkWrite-updateOne-sort.json SKIP BulkWrite updateOne with sort option needs server >= 8.0
|
bulkWrite-updateOne-sort.json SKIP BulkWrite updateOne with sort option needs server >= 8.0
|
||||||
bypassDocumentValidation.json FAIL Aggregate with $out passes bypassDocumentValidation: false events client0[0].command.bypassDocumentValidation: missing from actual
|
bypassDocumentValidation.json FAIL Aggregate with $out passes bypassDocumentValidation: false events client0[0].command.bypassDocumentValidation: missing from actual
|
||||||
bypassDocumentValidation.json FAIL BulkWrite passes bypassDocumentValidation: false events client0[0].command.bypassDocumentValidation: missing from actual
|
bypassDocumentValidation.json FAIL BulkWrite passes bypassDocumentValidation: false events client0[0].command.bypassDocumentValidation: missing from actual
|
||||||
@@ -359,8 +355,6 @@ findOneAndReplace-upsert.json FAIL FindOneAndReplace when no documents match wit
|
|||||||
findOneAndReplace.json FAIL FindOneAndReplace when many documents match returning the document after modification findOneAndReplace.x: expected 32, got 22
|
findOneAndReplace.json FAIL FindOneAndReplace when many documents match returning the document after modification findOneAndReplace.x: expected 32, got 22
|
||||||
findOneAndReplace.json FAIL FindOneAndReplace when one document matches returning the document after modification findOneAndReplace.x: expected 32, got 22
|
findOneAndReplace.json FAIL FindOneAndReplace when one document matches returning the document after modification findOneAndReplace.x: expected 32, got 22
|
||||||
findOneAndUpdate-collation.json FAIL FindOneAndUpdate when many documents match with collation returning the document before modification findOneAndUpdate: expected a document, got null
|
findOneAndUpdate-collation.json FAIL FindOneAndUpdate when many documents match with collation returning the document before modification findOneAndUpdate: expected a document, got null
|
||||||
findOneAndUpdate-comment.json FAIL findOneAndUpdate with string comment MongoServerError: update must be a document
|
|
||||||
findOneAndUpdate-comment.json FAIL findOneAndUpdate with document comment MongoServerError: update must be a document
|
|
||||||
findOneAndUpdate-comment.json SKIP findOneAndUpdate with comment - pre 4.4 needs server <= 4.2.99
|
findOneAndUpdate-comment.json SKIP findOneAndUpdate with comment - pre 4.4 needs server <= 4.2.99
|
||||||
findOneAndUpdate-dots_and_dollars.json SKIP Updating document to set top-level dollar-prefixed key on 5.0+ server needs server >= 5.0
|
findOneAndUpdate-dots_and_dollars.json SKIP Updating document to set top-level dollar-prefixed key on 5.0+ server needs server >= 5.0
|
||||||
findOneAndUpdate-dots_and_dollars.json SKIP Updating document to set top-level dotted key on 5.0+ server needs server >= 5.0
|
findOneAndUpdate-dots_and_dollars.json SKIP Updating document to set top-level dotted key on 5.0+ server needs server >= 5.0
|
||||||
@@ -373,9 +367,7 @@ findOneAndUpdate-hint-unacknowledged.json SKIP Unacknowledged findOneAndUpdate w
|
|||||||
findOneAndUpdate-hint-unacknowledged.json SKIP Unacknowledged findOneAndUpdate with hint document fails with client-side error on pre-4.4 server needs server <= 4.2.99
|
findOneAndUpdate-hint-unacknowledged.json SKIP Unacknowledged findOneAndUpdate with hint document fails with client-side error on pre-4.4 server needs server <= 4.2.99
|
||||||
findOneAndUpdate-let.json SKIP findOneAndUpdate with let option needs server >= 5.0
|
findOneAndUpdate-let.json SKIP findOneAndUpdate with let option needs server >= 5.0
|
||||||
findOneAndUpdate-let.json FAIL findOneAndUpdate with let option unsupported (server-side error) findOneAndUpdate: expected an error, the operation succeeded
|
findOneAndUpdate-let.json FAIL findOneAndUpdate with let option unsupported (server-side error) findOneAndUpdate: expected an error, the operation succeeded
|
||||||
findOneAndUpdate-pipeline.json FAIL FindOneAndUpdate using pipelines MongoServerError: update must be a document
|
|
||||||
findOneAndUpdate-rawdata.json SKIP findOneAndUpdate with rawData option needs server >= 8.2.0
|
findOneAndUpdate-rawdata.json SKIP findOneAndUpdate with rawData option needs server >= 8.2.0
|
||||||
findOneAndUpdate-rawdata.json FAIL findOneAndUpdate with rawData option on less than 8.2.0 - ignore argument MongoServerError: update must be a document
|
|
||||||
findOneAndUpdate.json FAIL FindOneAndUpdate when many documents match returning the document after modification findOneAndUpdate.x: expected 23, got 22
|
findOneAndUpdate.json FAIL FindOneAndUpdate when many documents match returning the document after modification findOneAndUpdate.x: expected 23, got 22
|
||||||
findOneAndUpdate.json FAIL FindOneAndUpdate when one document matches returning the document after modification findOneAndUpdate.x: expected 23, got 22
|
findOneAndUpdate.json FAIL FindOneAndUpdate when one document matches returning the document after modification findOneAndUpdate.x: expected 23, got 22
|
||||||
findOneAndUpdate.json FAIL FindOneAndUpdate when no documents match with upsert returning the document after modification findOneAndUpdate: expected a document, got null
|
findOneAndUpdate.json FAIL FindOneAndUpdate when no documents match with upsert returning the document after modification findOneAndUpdate: expected a document, got null
|
||||||
@@ -404,8 +396,7 @@ updateMany-dots_and_dollars.json SKIP Updating document to set top-level dotted
|
|||||||
updateMany-dots_and_dollars.json SKIP Updating document to set dollar-prefixed key in embedded doc on 5.0+ server needs server >= 5.0
|
updateMany-dots_and_dollars.json SKIP Updating document to set dollar-prefixed key in embedded doc on 5.0+ server needs server >= 5.0
|
||||||
updateMany-dots_and_dollars.json SKIP Updating document to set dotted key in embedded doc on 5.0+ server needs server >= 5.0
|
updateMany-dots_and_dollars.json SKIP Updating document to set dotted key in embedded doc on 5.0+ server needs server >= 5.0
|
||||||
updateMany-let.json SKIP updateMany with let option needs server >= 5.0
|
updateMany-let.json SKIP updateMany with let option needs server >= 5.0
|
||||||
updateMany-let.json FAIL updateMany with let option unsupported (server-side error) updateMany: error message "update spec requires u" does not contain "'update.let' is an unknown field"
|
updateMany-let.json FAIL updateMany with let option unsupported (server-side error) updateMany: expected an error, the operation succeeded
|
||||||
updateMany-pipeline.json FAIL UpdateMany using pipelines MongoServerError: update spec requires u
|
|
||||||
updateMany-rawdata.json SKIP updateMany with rawData option needs server >= 8.2.0
|
updateMany-rawdata.json SKIP updateMany with rawData option needs server >= 8.2.0
|
||||||
updateOne-collation.json FAIL UpdateOne when one document matches with collation updateOne.matchedCount: expected 1, got 0
|
updateOne-collation.json FAIL UpdateOne when one document matches with collation updateOne.matchedCount: expected 1, got 0
|
||||||
updateOne-comment.json SKIP UpdateOne with comment - pre 4.4 needs server <= 4.2.99
|
updateOne-comment.json SKIP UpdateOne with comment - pre 4.4 needs server <= 4.2.99
|
||||||
@@ -415,7 +406,6 @@ updateOne-dots_and_dollars.json SKIP Updating document to set dollar-prefixed ke
|
|||||||
updateOne-dots_and_dollars.json SKIP Updating document to set dotted key in embedded doc on 5.0+ server needs server >= 5.0
|
updateOne-dots_and_dollars.json SKIP Updating document to set dotted key in embedded doc on 5.0+ server needs server >= 5.0
|
||||||
updateOne-errorResponse.json SKIP update operations support errorResponse assertions runner: failPoint
|
updateOne-errorResponse.json SKIP update operations support errorResponse assertions runner: failPoint
|
||||||
updateOne-let.json SKIP UpdateOne with let option needs server >= 5.0
|
updateOne-let.json SKIP UpdateOne with let option needs server >= 5.0
|
||||||
updateOne-let.json FAIL UpdateOne with let option unsupported (server-side error) updateOne: error message "update spec requires u" does not contain "'update.let' is an unknown field"
|
updateOne-let.json FAIL UpdateOne with let option unsupported (server-side error) updateOne: expected an error, the operation succeeded
|
||||||
updateOne-pipeline.json FAIL UpdateOne using pipelines MongoServerError: update spec requires u
|
|
||||||
updateOne-rawdata.json SKIP UpdateOne with rawData option needs server >= 8.2.0
|
updateOne-rawdata.json SKIP UpdateOne with rawData option needs server >= 8.2.0
|
||||||
updateOne-sort.json SKIP UpdateOne with sort option needs server >= 8.0
|
updateOne-sort.json SKIP UpdateOne with sort option needs server >= 8.0
|
||||||
|
|||||||
Reference in New Issue
Block a user