update: replacement-style writes

`replaceOne`, `findOneAndReplace` and `bulkWrite`'s `replaceOne` all failed
with "bad update". `update.apply` rejected any update document whose first key
was not `$`-prefixed, so a replacement document -- which by definition has no
operators -- could not get through at all.

MongoDB decides on the first field and nothing else: `$`-prefixed means
operators, anything else means the document *is* the new content. An empty
document is a replacement too, and a legal one. `is_replacement` says which,
`apply_replacement` does the work, and because all three call sites already go
through `apply`, that one branch covers the update command, findAndModify and
the upsert builder.

What a replacement means, precisely:

  - every field is replaced except `_id`, which is immutable and keeps its
    position at the front, where it is stored and where the `_id_` index
    descends on it;
  - a replacement may restate the same `_id` but not a different one -- that
    is `ImmutableId`, because otherwise a rewrite would silently change a
    document's identity while the index entry kept the old key;
  - when the target has no `_id` yet, the replacement supplies it. That is the
    upsert path: `build_upsert_doc` seeds a document from the filter's
    equalities, so `replaceOne({_id: 99}, {u: 1}, {upsert: true})` inserts
    `{_id: 99, u: 1}` and not a generated ObjectId;
  - a mixed document is refused from either side, rather than guessed at.

Two options on update specs are refused rather than ignored:

  - `multi` with a replacement (FailedToParse). A replacement describes one
    document; applying it to many would leave every match identical apart from
    its `_id`.
  - `sort`, a MongoDB 8.0 addition this server does not implement. Ignoring it
    is the worst of the three answers -- `sort` chooses *which* match to write,
    so the client would silently get a different document than it asked for.

  spec scorecard   131 pass / 161 fail  ->  161 pass / 131 fail
  e2e.js           35 checks -> 45

Sixteen spec files improved and none regressed. The two `-sort` files briefly
did: they had been passing on their "server-side error" case, which our
"bad update" failure satisfied by accident, and passing for the wrong reason is
how a gap survives a scorecard.

Five mutations, each verified red: seeding the replacement from the old pairs,
dropping the `_id` comparison, dropping the `_id` a replacement supplies, and
removing the mixed-document guard from either loop.

Note `nModified` is still wrong for a write that changes nothing -- MongoDB
counts a document as modified only if applying the update altered it. That is
the remaining bulkWrite failure and is fixed next, separately.
This commit is contained in:
2026-08-03 23:52:22 +03:00
parent 504179acd1
commit 53f88e6d3b
4 changed files with 304 additions and 66 deletions

View File

@@ -872,6 +872,21 @@ fn cmd_update(ctx: *Context, msg: *wire.Message, reply: *wire.Reply) !void {
const u_doc = doc_arg(spec.get("u")) orelse return bad_value(reply, "update spec requires u");
const multi = bool_arg(spec.get("multi")) orelse false;
const upsert = bool_arg(spec.get("upsert")) orelse false;
// `sort` on an update spec picks *which* match to write when the filter
// matches several -- a MongoDB 8.0 addition. This server advertises 4.4,
// and ignoring the field would be the worst of the three possible
// answers: the client asked for a specific document and would silently
// get a different one. So refuse it, which is also what a real 4.4 does
// with an unknown update-spec field.
if (spec.get("sort") != null) {
return failed_to_parse(reply, "Unknown option to update: sort");
}
// A replacement describes one document, so there is no coherent meaning
// for applying it to many: every match would end up identical apart from
// its `_id`. MongoDB rejects the combination rather than doing that.
if (multi and update.is_replacement(u_doc)) {
return failed_to_parse(reply, "multi update is not supported for replacement-style update");
}
var matched: std.ArrayListUnmanaged(u64) = .empty;
defer matched.deinit(ctx.gpa);
@@ -1605,6 +1620,10 @@ fn bad_value(reply: *wire.Reply, msg: []const u8) !void {
return reply.put_error(@intFromEnum(ErrorCode.bad_value), "BadValue", msg);
}
fn failed_to_parse(reply: *wire.Reply, msg: []const u8) !void {
return reply.put_error(@intFromEnum(ErrorCode.failed_to_parse), "FailedToParse", msg);
}
/// The E11000 text, shared by the top-level error reply and the per-document
/// `writeErrors` entries of a batch insert. Uses the collection's
/// dup_index (set by a rejected unique-index write) when the conflict came

View File

@@ -10,17 +10,88 @@ pub const UpdateError = error{ ImmutableId, InvalidUpdate, OutOfMemory };
const max_path_segments = 16;
/// Apply an update document (whose fields are operator documents) to `doc`.
/// Whether an update document is a *replacement* rather than a set of
/// operators. MongoDB decides on the first field and nothing else: a
/// `$`-prefixed one means operators. An empty document is a replacement, and a
/// legal one -- it strips every field but `_id`.
///
/// This distinction is the whole of `replaceOne`, `findOneAndReplace` and
/// `bulkWrite`'s `replaceOne`. Without it `apply` rejected every update whose
/// first key was not `$`-prefixed, so all three failed with "bad update".
pub fn is_replacement(pairs: []const bson.Pair) bool {
if (pairs.len == 0) return true;
return !is_operator_key(pairs[0].key);
}
fn is_operator_key(key: []const u8) bool {
return key.len > 0 and key[0] == '$';
}
/// Apply an update document to `doc`: a replacement, or a set of operators.
pub fn apply(doc: *bson.Document, update: *const bson.Document) UpdateError!void {
if (is_replacement(update.pairs)) return apply_replacement(doc, update.pairs);
const arena = doc.arena.allocator();
var pairs = try copy_to_list(bson.Pair, arena, doc.pairs);
for (update.pairs) |op| {
if (op.key.len == 0 or op.key[0] != '$') return error.InvalidUpdate;
// The first field decided this is an operator update, so a field that
// is not one is a mixed document -- which MongoDB rejects rather than
// guessing at.
if (!is_operator_key(op.key)) return error.InvalidUpdate;
try apply_operator(arena, &pairs, op.key, op.value);
}
doc.pairs = try pairs.toOwnedSlice(arena);
}
/// Replace every field of `doc` with `replacement`'s, except `_id`.
///
/// `_id` is immutable, so it survives and keeps its position at the front (which
/// is also where MongoDB stores it, and what the `_id_` index descends on). A
/// replacement carrying an `_id` is allowed only when it is the *same* `_id`;
/// anything else is an attempt to change a document's identity by rewriting it.
///
/// `doc` may legitimately have no `_id` yet: that is the upsert path, where the
/// caller has seeded the document from the filter's equalities and `insert`
/// generates an ObjectId afterwards. Then the replacement's own `_id`, if it has
/// one, is what the new document gets.
fn apply_replacement(doc: *bson.Document, replacement: []const bson.Pair) UpdateError!void {
const arena = doc.arena.allocator();
const old_id = doc.get("_id");
var pairs: std.ArrayListUnmanaged(bson.Pair) = .empty;
errdefer pairs.deinit(arena);
try pairs.ensureTotalCapacity(arena, replacement.len + 1);
if (old_id) |id| {
try pairs.append(arena, .{
.key = try arena.dupe(u8, "_id"),
.value = try bson.copy_value(arena, id),
});
}
for (replacement) |p| {
// A replacement is data, not instructions. `{$set: {...}}` reaching here
// would mean the first field was not an operator and a later one was,
// i.e. a mixed document.
if (is_operator_key(p.key)) return error.InvalidUpdate;
if (std.mem.eql(u8, p.key, "_id")) {
if (old_id) |id| {
if (bson.compare(id, p.value) != .eq) return error.ImmutableId;
continue; // already at the front
}
// No stored `_id`: this replacement supplies it.
try pairs.insert(arena, 0, .{
.key = try arena.dupe(u8, "_id"),
.value = try bson.copy_value(arena, p.value),
});
continue;
}
try pairs.append(arena, .{
.key = try arena.dupe(u8, p.key),
.value = try bson.copy_value(arena, p.value),
});
}
doc.pairs = try pairs.toOwnedSlice(arena);
}
fn apply_operator(
arena: std.mem.Allocator,
pairs: *std.ArrayListUnmanaged(bson.Pair),
@@ -417,10 +488,128 @@ test "$set nested creation and _id protection" {
})));
}
test "non-operator update rejected" {
test "a replacement keeps _id and drops every other field" {
// Mutation check: seed `apply_replacement`'s list from `doc.pairs` (as the
// operator path does) instead of starting empty. Red -- `gone` survives, and
// a replacement that does not remove fields is not a replacement.
var doc = bson.Document{ .arena = std.heap.ArenaAllocator.init(testing.allocator), .pairs = &.{} };
defer doc.arena.deinit();
doc.pairs = try doc.arena.allocator().dupe(bson.Pair, &.{
.{ .key = "_id", .value = .{ .int32 = 7 } },
.{ .key = "gone", .value = .{ .int32 = 1 } },
.{ .key = "also_gone", .value = .{ .string = "x" } },
});
try apply(&doc, &doc_of(&.{
.{ .key = "fresh", .value = .{ .int32 = 42 } },
}));
try testing.expectEqual(@as(usize, 2), doc.pairs.len);
// _id survives, and stays at the front where it is stored.
try testing.expectEqualStrings("_id", doc.pairs[0].key);
try testing.expectEqual(@as(i64, 7), doc.pairs[0].value.int32);
try testing.expectEqual(@as(i64, 42), bson.get_pair(doc.pairs, "fresh").?.int32);
try testing.expect(bson.get_pair(doc.pairs, "gone") == null);
try testing.expect(bson.get_pair(doc.pairs, "also_gone") == null);
}
test "an empty replacement leaves a document holding only its _id" {
var doc = bson.Document{ .arena = std.heap.ArenaAllocator.init(testing.allocator), .pairs = &.{} };
defer doc.arena.deinit();
doc.pairs = try doc.arena.allocator().dupe(bson.Pair, &.{
.{ .key = "_id", .value = .{ .int32 = 7 } },
.{ .key = "a", .value = .{ .int32 = 1 } },
});
// Legal, and the reason `is_replacement` treats an empty document as one
// rather than as a no-op set of operators.
try apply(&doc, &doc_of(&.{}));
try testing.expectEqual(@as(usize, 1), doc.pairs.len);
try testing.expectEqualStrings("_id", doc.pairs[0].key);
}
test "a replacement may repeat the _id it is replacing, but not change it" {
// Mutation check: drop the `bson.compare` in `apply_replacement`. Red on the
// second half -- a replacement would silently rewrite a document's identity,
// and since the `_id_` index is keyed on it, the stored entry and the stored
// document would disagree.
var doc = bson.Document{ .arena = std.heap.ArenaAllocator.init(testing.allocator), .pairs = &.{} };
defer doc.arena.deinit();
const original = [_]bson.Pair{
.{ .key = "_id", .value = .{ .int32 = 7 } },
.{ .key = "a", .value = .{ .int32 = 1 } },
};
doc.pairs = try doc.arena.allocator().dupe(bson.Pair, &original);
// The same _id, restated: accepted.
try apply(&doc, &doc_of(&.{
.{ .key = "_id", .value = .{ .int32 = 7 } },
.{ .key = "b", .value = .{ .int32 = 2 } },
}));
try testing.expectEqual(@as(i64, 7), doc.pairs[0].value.int32);
try testing.expectEqual(@as(i64, 2), bson.get_pair(doc.pairs, "b").?.int32);
// And only once, not twice.
try testing.expectEqual(@as(usize, 2), doc.pairs.len);
// A different _id: refused.
try testing.expectError(error.ImmutableId, apply(&doc, &doc_of(&.{
.{ .key = "_id", .value = .{ .int32 = 8 } },
})));
// Equal across numeric types is the same _id, matching the canonical key
// encoding the _id_ index descends on.
try apply(&doc, &doc_of(&.{
.{ .key = "_id", .value = .{ .double = 7.0 } },
.{ .key = "c", .value = .{ .int32 = 3 } },
}));
try testing.expectEqual(@as(i64, 3), bson.get_pair(doc.pairs, "c").?.int32);
}
test "a replacement supplies the _id when the document has none" {
// The upsert path: the caller seeds a document from the filter's equalities
// and the replacement carries the _id.
//
// Mutation check: make the `old_id == null` arm `continue` without adding the
// pair. Red -- the upserted document loses the _id the client asked for and
// gets a generated ObjectId instead, so a retried upsert inserts twice.
var doc = bson.Document{ .arena = std.heap.ArenaAllocator.init(testing.allocator), .pairs = &.{} };
defer doc.arena.deinit();
try apply(&doc, &doc_of(&.{
.{ .key = "a", .value = .{ .int32 = 1 } },
.{ .key = "_id", .value = .{ .int32 = 99 } },
}));
try testing.expectEqual(@as(usize, 2), doc.pairs.len);
try testing.expectEqualStrings("_id", doc.pairs[0].key);
try testing.expectEqual(@as(i64, 99), doc.pairs[0].value.int32);
}
test "a mixed update document is refused from either side" {
// Mutation check: drop the `is_operator_key` guard in `apply`'s operator loop
// (first case) or in `apply_replacement`'s loop (second). Each leaves one
// half green and the other red.
var doc = bson.Document{ .arena = std.heap.ArenaAllocator.init(testing.allocator), .pairs = &.{} };
defer doc.arena.deinit();
// Starts with an operator, so operators are expected throughout.
try testing.expectError(error.InvalidUpdate, apply(&doc, &doc_of(&.{
.{ .key = "$set", .value = .{ .doc = &.{.{ .key = "a", .value = .{ .int32 = 1 } }} } },
.{ .key = "plain", .value = .{ .int32 = 1 } },
})));
// Starts with data, so it is a replacement and an operator has no meaning.
try testing.expectError(error.InvalidUpdate, apply(&doc, &doc_of(&.{
.{ .key = "plain", .value = .{ .int32 = 1 } },
.{ .key = "$set", .value = .{ .doc = &.{.{ .key = "a", .value = .{ .int32 = 1 } }} } },
})));
}
test "is_replacement decides on the first field only" {
try testing.expect(is_replacement(&.{}));
try testing.expect(is_replacement(&.{.{ .key = "a", .value = .{ .int32 = 1 } }}));
try testing.expect(!is_replacement(&.{.{ .key = "$set", .value = .{ .doc = &.{} } }}));
// An empty key cannot be an operator, so it reads as data -- and the
// replacement path then stores it, which is what MongoDB does with it.
try testing.expect(is_replacement(&.{.{ .key = "", .value = .{ .int32 = 1 } }}));
}

View File

@@ -68,6 +68,66 @@ async function main() {
check('upsert', ups.upsertedCount === 1 && ups.matchedCount === 0, ups);
check('upsert doc exists', (await users.findOne({ name: 'erin' }))?.age === 28);
// --- replacement-style writes ---
// A replacement is data, not instructions: it replaces every field except
// `_id`. Distinct enough from operator updates to be worth its own block --
// these all used to fail with "bad update", because the update path required
// a $-prefixed first field.
const repl = db.collection('repl');
await repl.drop().catch(() => {});
await repl.insertMany([{ _id: 1, a: 1, gone: 'x' }, { _id: 2, a: 2 }, { _id: 3, a: 3 }]);
const rp = await repl.replaceOne({ _id: 1 }, { fresh: 9 });
check('replaceOne modifies one', rp.modifiedCount === 1, JSON.stringify(rp));
const rpDoc = await repl.findOne({ _id: 1 });
check(
'replaceOne keeps _id and drops the old fields',
rpDoc._id === 1 && rpDoc.fresh === 9 && rpDoc.a === undefined && rpDoc.gone === undefined,
JSON.stringify(rpDoc),
);
const farRaw = await repl.findOneAndReplace({ _id: 2 }, { swapped: true }, { returnDocument: 'after' });
const far = farRaw.value ?? farRaw;
check('findOneAndReplace returns the new document', far.swapped === true && far._id === 2, JSON.stringify(farRaw));
const bulkRep = await repl.bulkWrite([{ replaceOne: { filter: { _id: 3 }, replacement: { via: 'bulk' } } }]);
check('bulkWrite replaceOne', bulkRep.modifiedCount === 1, String(bulkRep.modifiedCount));
const rpUp = await repl.replaceOne({ _id: 99 }, { u: 1 }, { upsert: true });
check('replacement upsert takes the _id from the filter', String(rpUp.upsertedId) === '99', JSON.stringify(rpUp.upsertedId));
const rpUpDoc = await repl.findOne({ _id: 99 });
check(
'upserted document is the replacement plus _id',
rpUpDoc && rpUpDoc.u === 1 && Object.keys(rpUpDoc).length === 2,
JSON.stringify(rpUpDoc),
);
let idChangeRefused = false;
try { await repl.replaceOne({ _id: 1 }, { _id: 1234, x: 1 }); } catch { idChangeRefused = true; }
check('a replacement may not change _id', idChangeRefused);
const rpEmpty = await repl.replaceOne({ _id: 1 }, {});
const rpEmptyDoc = await repl.findOne({ _id: 1 });
check(
'an empty replacement leaves only _id',
rpEmpty.modifiedCount === 1 && Object.keys(rpEmptyDoc).length === 1,
JSON.stringify(rpEmptyDoc),
);
// Raw command: the driver refuses updateMany-with-a-replacement client side,
// so going through it would test the driver rather than this server.
let multiRefused = null;
try {
await db.command({ update: 'repl', updates: [{ q: {}, u: { a: 1 }, multi: true }] });
} catch (e) { multiRefused = e; }
check(
'multi with a replacement is refused with FailedToParse',
multiRefused?.code === 9 && /replacement-style/.test(multiRefused.message),
`code=${multiRefused?.code} ${multiRefused?.message?.slice(0, 50)}`,
);
const multiOps = await db.command({ update: 'repl', updates: [{ q: {}, u: { $set: { touched: 1 } }, multi: true }] });
check('multi with operators still runs', multiOps.ok === 1 && multiOps.n === 4, JSON.stringify(multiOps));
// --- findOneAndUpdate (findAndModify) ---
const fam = await users.findOneAndUpdate(
{ name: 'bob' },

View File

@@ -13,7 +13,7 @@
# semantics; ignoring them makes some cases pass that a full runner would
# fail, so treat `pass` as an upper bound until M1 wires events up.
total 131 pass 161 fail 195 skip 175 files 0 errored
total 161 pass 131 fail 195 skip 175 files 0 errored
# per-file: name pass fail skip
aggregate-allowdiskuse.json 3 0 0
@@ -28,7 +28,7 @@ aggregate-write-readPreference.json 0 0 4
aggregate.json 5 0 2
bulkWrite-arrayFilters.json 0 3 0
bulkWrite-collation.json 0 2 0
bulkWrite-comment.json 0 2 1
bulkWrite-comment.json 2 0 1
bulkWrite-delete-hint-serverError.json 0 0 2
bulkWrite-delete-hint.json 2 0 0
bulkWrite-deleteMany-hint-unacknowledged.json 0 2 2
@@ -39,12 +39,12 @@ bulkWrite-deleteOne-let.json 0 1 1
bulkWrite-deleteOne-rawdata.json 1 0 1
bulkWrite-errorResponse.json 0 0 1
bulkWrite-insertOne-dots_and_dollars.json 3 1 1
bulkWrite-replaceOne-dots_and_dollars.json 1 2 1
bulkWrite-replaceOne-dots_and_dollars.json 2 1 1
bulkWrite-replaceOne-hint-unacknowledged.json 0 2 0
bulkWrite-replaceOne-let.json 0 1 1
bulkWrite-replaceOne-rawdata.json 1 0 1
bulkWrite-replaceOne-sort.json 1 0 1
bulkWrite-update-hint.json 2 1 0
bulkWrite-update-hint.json 3 0 0
bulkWrite-update-validation.json 3 0 0
bulkWrite-updateMany-dots_and_dollars.json 0 0 4
bulkWrite-updateMany-hint-unacknowledged.json 0 2 0
@@ -57,8 +57,8 @@ bulkWrite-updateOne-let.json 0 1 1
bulkWrite-updateOne-pipeline.json 0 1 0
bulkWrite-updateOne-rawdata.json 0 1 1
bulkWrite-updateOne-sort.json 1 0 1
bulkWrite.json 5 5 0
bypassDocumentValidation.json 6 3 0
bulkWrite.json 8 2 0
bypassDocumentValidation.json 8 1 0
client-bulkWrite-delete-options.json 0 0 2
client-bulkWrite-delete-rawdata.json 0 0 2
client-bulkWrite-errorResponse.json 0 0 1
@@ -127,15 +127,15 @@ findOneAndDelete-let.json 0 1 1
findOneAndDelete-rawdata.json 1 0 1
findOneAndDelete.json 3 0 0
findOneAndReplace-collation.json 0 1 0
findOneAndReplace-comment.json 0 2 1
findOneAndReplace-dots_and_dollars.json 1 2 1
findOneAndReplace-comment.json 2 0 1
findOneAndReplace-dots_and_dollars.json 2 1 1
findOneAndReplace-hint-serverError.json 0 0 2
findOneAndReplace-hint-unacknowledged.json 0 2 2
findOneAndReplace-hint.json 0 2 0
findOneAndReplace-hint.json 2 0 0
findOneAndReplace-let.json 0 1 1
findOneAndReplace-rawdata.json 0 1 1
findOneAndReplace-upsert.json 0 4 0
findOneAndReplace.json 2 4 0
findOneAndReplace-rawdata.json 1 0 1
findOneAndReplace-upsert.json 2 2 0
findOneAndReplace.json 4 2 0
findOneAndUpdate-arrayFilters.json 0 3 0
findOneAndUpdate-collation.json 0 1 0
findOneAndUpdate-comment.json 0 2 1
@@ -158,15 +158,15 @@ insertOne-errorResponse.json 0 0 1
insertOne-rawdata.json 1 0 1
insertOne.json 1 0 0
replaceOne-collation.json 0 1 0
replaceOne-comment.json 0 2 1
replaceOne-dots_and_dollars.json 2 2 1
replaceOne-comment.json 2 0 1
replaceOne-dots_and_dollars.json 3 1 1
replaceOne-hint-unacknowledged.json 0 2 0
replaceOne-hint.json 0 2 0
replaceOne-hint.json 2 0 0
replaceOne-let.json 0 1 1
replaceOne-rawdata.json 0 1 1
replaceOne-rawdata.json 1 0 1
replaceOne-sort.json 1 0 1
replaceOne-validation.json 1 0 0
replaceOne.json 1 4 0
replaceOne.json 5 0 0
updateMany-arrayFilters.json 0 3 0
updateMany-collation.json 0 1 0
updateMany-comment.json 2 0 1
@@ -188,7 +188,7 @@ updateOne-hint.json 2 0 0
updateOne-let.json 0 1 1
updateOne-pipeline.json 0 1 0
updateOne-rawdata.json 1 0 1
updateOne-sort.json 0 1 1
updateOne-sort.json 1 0 1
updateOne-validation.json 1 0 0
updateOne.json 4 0 0
@@ -215,9 +215,7 @@ bulkWrite-arrayFilters.json FAIL BulkWrite updateOne with arrayFilters outcome c
bulkWrite-arrayFilters.json FAIL BulkWrite updateMany with arrayFilters outcome crud-tests.test[0].y: expected an array, got {"$[i]":{"b":2}}
bulkWrite-arrayFilters.json FAIL BulkWrite with arrayFilters outcome crud-tests.test[0].y: expected an array, got {"$[i]":{"b":2}}
bulkWrite-collation.json FAIL BulkWrite with delete operations and collation bulkWrite.deletedCount: expected 4, got 0
bulkWrite-collation.json FAIL BulkWrite with update operations and collation MongoBulkWriteError: internal error
bulkWrite-comment.json FAIL BulkWrite with string comment MongoBulkWriteError: bad update
bulkWrite-comment.json FAIL BulkWrite with document comment MongoBulkWriteError: bad update
bulkWrite-collation.json FAIL BulkWrite with update operations and collation bulkWrite.matchedCount: expected 6, got 2
bulkWrite-comment.json SKIP BulkWrite with comment - pre 4.4 needs server <= 4.2.99
bulkWrite-delete-hint-serverError.json SKIP * needs server <= 4.3.3
bulkWrite-deleteMany-hint-unacknowledged.json SKIP Unacknowledged deleteMany with hint string fails with client-side error on pre-4.4 server needs server <= 4.2.99
@@ -237,16 +235,14 @@ bulkWrite-deleteOne-rawdata.json SKIP BulkWrite deleteOne with rawData option ne
bulkWrite-errorResponse.json SKIP bulkWrite operations support errorResponse assertions runner: failPoint
bulkWrite-insertOne-dots_and_dollars.json SKIP Inserting document with top-level dollar-prefixed key on 5.0+ server needs server >= 5.0
bulkWrite-insertOne-dots_and_dollars.json FAIL Inserting document with top-level dollar-prefixed key on pre-5.0 server yields server-side error bulkWrite: expected an error, the operation succeeded
bulkWrite-replaceOne-dots_and_dollars.json FAIL Replacing document with top-level dotted key on 3.6+ server MongoBulkWriteError: bad update
bulkWrite-replaceOne-dots_and_dollars.json SKIP Replacing document with dollar-prefixed key in embedded doc on 5.0+ server needs server >= 5.0
bulkWrite-replaceOne-dots_and_dollars.json FAIL Replacing document with dotted key in embedded doc on 3.6+ server MongoBulkWriteError: bad update
bulkWrite-replaceOne-hint-unacknowledged.json FAIL Unacknowledged replaceOne with hint string on 4.2+ server MongoBulkWriteError: bad update
bulkWrite-replaceOne-hint-unacknowledged.json FAIL Unacknowledged replaceOne with hint document on 4.2+ server MongoBulkWriteError: bad update
bulkWrite-replaceOne-dots_and_dollars.json FAIL Replacing document with dollar-prefixed key in embedded doc on pre-5.0 server yields server-side error bulkWrite: expected an error, the operation succeeded
bulkWrite-replaceOne-hint-unacknowledged.json FAIL Unacknowledged replaceOne with hint string on 4.2+ server bulkWrite: unexpected extra keys ["insertedCount","matchedCount","modifiedCount","deletedCount","upsertedCount","upsertedIds","insertedIds"]
bulkWrite-replaceOne-hint-unacknowledged.json FAIL Unacknowledged replaceOne with hint document on 4.2+ server bulkWrite: unexpected extra keys ["insertedCount","matchedCount","modifiedCount","deletedCount","upsertedCount","upsertedIds","insertedIds"]
bulkWrite-replaceOne-let.json SKIP BulkWrite replaceOne with let option needs server >= 5.0
bulkWrite-replaceOne-let.json FAIL BulkWrite replaceOne with let option unsupported (server-side error) bulkWrite: expected an error, the operation succeeded
bulkWrite-replaceOne-rawdata.json SKIP BulkWrite replaceOne with rawData option needs server >= 8.2.0
bulkWrite-replaceOne-sort.json SKIP BulkWrite replaceOne with sort option needs server >= 8.0
bulkWrite-update-hint.json FAIL BulkWrite replaceOne with update hints MongoBulkWriteError: bad update
bulkWrite-updateMany-dots_and_dollars.json SKIP Updating document to set top-level dollar-prefixed key on 5.0+ server needs server >= 5.0
bulkWrite-updateMany-dots_and_dollars.json SKIP Updating document to set top-level dotted key 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
@@ -270,14 +266,9 @@ bulkWrite-updateOne-pipeline.json FAIL UpdateOne in bulk write using pipelines M
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.json FAIL BulkWrite with replaceOne operations MongoBulkWriteError: bad update
bulkWrite.json FAIL BulkWrite with updateOne operations bulkWrite.modifiedCount: expected 1, got 2
bulkWrite.json FAIL BulkWrite with updateMany operations bulkWrite.modifiedCount: expected 2, got 4
bulkWrite.json FAIL BulkWrite with mixed ordered operations MongoBulkWriteError: internal error
bulkWrite.json FAIL BulkWrite with mixed unordered operations MongoBulkWriteError: internal error
bypassDocumentValidation.json FAIL Aggregate with $out passes bypassDocumentValidation: false MongoServerError: Unrecognized pipeline stage name: '$out'
bypassDocumentValidation.json FAIL FindOneAndReplace passes bypassDocumentValidation: false MongoServerError: bad update
bypassDocumentValidation.json FAIL ReplaceOne passes bypassDocumentValidation: false MongoServerError: bad update
client-bulkWrite-delete-options.json SKIP * needs server >= 8.0
client-bulkWrite-delete-rawdata.json SKIP client bulk write delete with rawData option needs server >= 8.2.0
client-bulkWrite-delete-rawdata.json SKIP client bulk write delete with rawData option on less than 8.2.0 - ignore argument needs server >= 8.0
@@ -313,7 +304,7 @@ create-null-ids.json FAIL inserting _id with type null via insertOne countDocume
create-null-ids.json FAIL inserting _id with type null via insertMany countDocuments: expected 1, got 0
create-null-ids.json FAIL inserting _id with type null via updateOne countDocuments: expected 1, got 0
create-null-ids.json FAIL inserting _id with type null via updateMany countDocuments: expected 1, got 0
create-null-ids.json FAIL inserting _id with type null via replaceOne MongoServerError: internal error
create-null-ids.json FAIL inserting _id with type null via replaceOne countDocuments: expected 1, got 0
create-null-ids.json FAIL inserting _id with type null via bulkWrite countDocuments: expected 1, got 0
create-null-ids.json SKIP inserting _id with type null via clientBulkWrite needs server >= 8.0
db-aggregate-rawdata.json SKIP Aggregate with rawData option needs server >= 8.2.0, needs topology replicaset
@@ -380,31 +371,21 @@ findOneAndDelete-let.json SKIP findOneAndDelete with let option needs server >=
findOneAndDelete-let.json FAIL findOneAndDelete with let option unsupported (server-side error) findOneAndDelete: expected an error, the operation succeeded
findOneAndDelete-rawdata.json SKIP findOneAndDelete with rawData option needs server >= 8.2.0
findOneAndReplace-collation.json FAIL FindOneAndReplace when one document matches with collation returning the document after modification findOneAndReplace: expected a document, got null
findOneAndReplace-comment.json FAIL findOneAndReplace with string comment MongoServerError: bad update
findOneAndReplace-comment.json FAIL findOneAndReplace with document comment MongoServerError: bad update
findOneAndReplace-comment.json SKIP findOneAndReplace with comment - pre 4.4 needs server <= 4.2.99
findOneAndReplace-dots_and_dollars.json FAIL Replacing document with top-level dotted key on 3.6+ server MongoServerError: bad update
findOneAndReplace-dots_and_dollars.json SKIP Replacing document with dollar-prefixed key in embedded doc on 5.0+ server needs server >= 5.0
findOneAndReplace-dots_and_dollars.json FAIL Replacing document with dotted key in embedded doc on 3.6+ server MongoServerError: bad update
findOneAndReplace-dots_and_dollars.json FAIL Replacing document with dollar-prefixed key in embedded doc on pre-5.0 server yields server-side error findOneAndReplace: expected an error, the operation succeeded
findOneAndReplace-hint-serverError.json SKIP * needs server <= 4.3.0
findOneAndReplace-hint-unacknowledged.json SKIP Unacknowledged findOneAndReplace with hint string fails with client-side error on pre-4.4 server needs server <= 4.2.99
findOneAndReplace-hint-unacknowledged.json SKIP Unacknowledged findOneAndReplace with hint document fails with client-side error on pre-4.4 server needs server <= 4.2.99
findOneAndReplace-hint-unacknowledged.json FAIL Unacknowledged findOneAndReplace with hint string on 4.4+ server MongoServerError: bad update
findOneAndReplace-hint-unacknowledged.json FAIL Unacknowledged findOneAndReplace with hint document on 4.4+ server MongoServerError: bad update
findOneAndReplace-hint.json FAIL FindOneAndReplace with hint string MongoServerError: bad update
findOneAndReplace-hint.json FAIL FindOneAndReplace with hint document MongoServerError: bad update
findOneAndReplace-hint-unacknowledged.json FAIL Unacknowledged findOneAndReplace with hint string on 4.4+ server findOneAndReplace: expected null, got {"_id":2,"x":22}
findOneAndReplace-hint-unacknowledged.json FAIL Unacknowledged findOneAndReplace with hint document on 4.4+ server findOneAndReplace: expected null, got {"_id":2,"x":22}
findOneAndReplace-let.json SKIP findOneAndReplace with let option needs server >= 5.0
findOneAndReplace-let.json FAIL findOneAndReplace with let option unsupported (server-side error) findOneAndReplace: expected an error, the operation succeeded
findOneAndReplace-rawdata.json SKIP findOneAndReplace with rawData option needs server >= 8.2.0
findOneAndReplace-rawdata.json FAIL findOneAndReplace with rawData option on less than 8.2.0 - ignore argument MongoServerError: bad update
findOneAndReplace-upsert.json FAIL FindOneAndReplace when no documents match without id specified with upsert returning the document before modification MongoServerError: internal error
findOneAndReplace-upsert.json FAIL FindOneAndReplace when no documents match without id specified with upsert returning the document after modification MongoServerError: internal error
findOneAndReplace-upsert.json FAIL FindOneAndReplace when no documents match with id specified with upsert returning the document before modification MongoServerError: internal error
findOneAndReplace-upsert.json FAIL FindOneAndReplace when no documents match with id specified with upsert returning the document after modification MongoServerError: internal error
findOneAndReplace.json FAIL FindOneAndReplace when many documents match returning the document before modification MongoServerError: bad update
findOneAndReplace.json FAIL FindOneAndReplace when many documents match returning the document after modification MongoServerError: bad update
findOneAndReplace.json FAIL FindOneAndReplace when one document matches returning the document before modification MongoServerError: bad update
findOneAndReplace.json FAIL FindOneAndReplace when one document matches returning the document after modification MongoServerError: bad update
findOneAndReplace-upsert.json FAIL FindOneAndReplace when no documents match without id specified with upsert returning the document after modification findOneAndReplace: expected a document, got null
findOneAndReplace-upsert.json FAIL FindOneAndReplace when no documents match with id specified with upsert returning the document after modification findOneAndReplace: expected a document, got null
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
findOneAndUpdate-arrayFilters.json FAIL FindOneAndUpdate when no document matches arrayFilters outcome crud-v1.coll[0].y: expected an array, got {"$[i]":{"b":2}}
findOneAndUpdate-arrayFilters.json FAIL FindOneAndUpdate when one document matches arrayFilters outcome crud-v1.coll[0].y: expected an array, got {"$[i]":{"b":2}}
findOneAndUpdate-arrayFilters.json FAIL FindOneAndUpdate when multiple documents match arrayFilters outcome crud-v1.coll[0].y: expected an array, got {"$[i]":{"b":2}}
@@ -447,25 +428,15 @@ insertOne-dots_and_dollars.json FAIL Unacknowledged write using dollar-prefixed
insertOne-errorResponse.json SKIP insert operations support errorResponse assertions runner: failPoint
insertOne-rawdata.json SKIP insertOne with rawData option needs server >= 8.2.0
replaceOne-collation.json FAIL ReplaceOne when one document matches with collation replaceOne.matchedCount: expected 1, got 0
replaceOne-comment.json FAIL ReplaceOne with string comment MongoServerError: bad update
replaceOne-comment.json FAIL ReplaceOne with document comment MongoServerError: bad update
replaceOne-comment.json SKIP ReplaceOne with comment - pre 4.4 needs server <= 4.2.99
replaceOne-dots_and_dollars.json FAIL Replacing document with top-level dotted key on 3.6+ server MongoServerError: bad update
replaceOne-dots_and_dollars.json SKIP Replacing document with dollar-prefixed key in embedded doc on 5.0+ server needs server >= 5.0
replaceOne-dots_and_dollars.json FAIL Replacing document with dotted key in embedded doc on 3.6+ server MongoServerError: bad update
replaceOne-hint-unacknowledged.json FAIL Unacknowledged replaceOne with hint string on 4.2+ server MongoServerError: bad update
replaceOne-hint-unacknowledged.json FAIL Unacknowledged replaceOne with hint document on 4.2+ server MongoServerError: bad update
replaceOne-hint.json FAIL ReplaceOne with hint string MongoServerError: bad update
replaceOne-hint.json FAIL ReplaceOne with hint document MongoServerError: bad update
replaceOne-dots_and_dollars.json FAIL Replacing document with dollar-prefixed key in embedded doc on pre-5.0 server yields server-side error replaceOne: expected an error, the operation succeeded
replaceOne-hint-unacknowledged.json FAIL Unacknowledged replaceOne with hint string on 4.2+ server replaceOne: unexpected extra keys ["modifiedCount","upsertedId","upsertedCount","matchedCount"]
replaceOne-hint-unacknowledged.json FAIL Unacknowledged replaceOne with hint document on 4.2+ server replaceOne: unexpected extra keys ["modifiedCount","upsertedId","upsertedCount","matchedCount"]
replaceOne-let.json SKIP ReplaceOne with let option needs server >= 5.0
replaceOne-let.json FAIL ReplaceOne with let option unsupported (server-side error) replaceOne: expected an error, the operation succeeded
replaceOne-rawdata.json SKIP ReplaceOne with rawData option needs server >= 8.2.0
replaceOne-rawdata.json FAIL ReplaceOne with rawData option on less than 8.2.0 - ignore argument MongoServerError: bad update
replaceOne-sort.json SKIP ReplaceOne with sort option needs server >= 8.0
replaceOne.json FAIL ReplaceOne when many documents match MongoServerError: bad update
replaceOne.json FAIL ReplaceOne when one document matches MongoServerError: bad update
replaceOne.json FAIL ReplaceOne with upsert when no documents match without an id specified MongoServerError: internal error
replaceOne.json FAIL ReplaceOne with upsert when no documents match with an id specified MongoServerError: internal error
updateMany-arrayFilters.json FAIL UpdateMany when no documents match arrayFilters updateMany.modifiedCount: expected 0, got 2
updateMany-arrayFilters.json FAIL UpdateMany when one document matches arrayFilters updateMany.modifiedCount: expected 1, got 2
updateMany-arrayFilters.json FAIL UpdateMany when multiple documents match arrayFilters outcome crud-v1.coll[0].y: expected an array, got {"$[i]":{"b":2}}
@@ -500,4 +471,3 @@ updateOne-let.json FAIL UpdateOne with let option unsupported (server-side error
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-sort.json SKIP UpdateOne with sort option needs server >= 8.0
updateOne-sort.json FAIL updateOne with sort option unsupported (server-side error) updateOne: expected an error, the operation succeeded