SERVER-107211 Allow v1 oplog entries in applyOps (#39402)

GitOrigin-RevId: 6c6161fa727047c944aee79e0b06886654421eb4
This commit is contained in:
Parker Felix 2025-08-14 17:32:07 -04:00 committed by MongoDB Bot
parent 463fb36531
commit 5bac5853b5
11 changed files with 354 additions and 58 deletions

View File

@ -482,6 +482,10 @@ last-continuous:
ticket: SERVER-102364
- test_file: jstests/sharding/validate_collection_shard_version.js
ticket: SERVER-87119
- test_file: jstests/core/query/internal_apply_oplog_update.js
ticket: SERVER-107211
- test_file: jstests/replsets/v1_apply_ops_oplog_format.js
ticket: SERVER-107211
suites: null
last-lts:
all:
@ -1019,4 +1023,8 @@ last-lts:
ticket: SERVER-102364
- test_file: jstests/sharding/validate_collection_shard_version.js
ticket: SERVER-87119
- test_file: jstests/core/query/internal_apply_oplog_update.js
ticket: SERVER-107211
- test_file: jstests/replsets/v1_apply_ops_oplog_format.js
ticket: SERVER-107211
suites: null

View File

@ -17,7 +17,7 @@
* ]
*/
var t = db.apply_ops1;
const t = db.apply_ops1;
t.drop();
//
@ -417,53 +417,96 @@ res = assert.commandFailed(db.adminCommand({
}));
assert.eq(res.code, 4772600);
// When we explicitly specify {$v: 1} it should fail because this version is no longer supported.
assert.commandFailedWithCode(db.adminCommand({
// When $v is missing, it should be treated it as a {$v:1} update. This means that we should get
// 'UpdateNode' update semantics, and $set operations get performed in lexicographic order.
assert.commandWorked(db.adminCommand({
applyOps: [
{"op": "i", "ns": t.getFullName(), "o": {_id: 10}},
{"op": "u", "ns": t.getFullName(), "o2": {_id: 10}, "o": {$set: {z: 1, a: 2}}}
]
}));
assert.eq(t.findOne({_id: 10}), {_id: 10, a: 2, z: 1}); // Note: 'a' and 'z' have been sorted.
// When we explicitly specify {$v: 1}, we should get 'UpdateNode' update semantics, and $set
// operations get performed in lexicographic order.
assert.commandWorked(db.adminCommand({
applyOps: [
{"op": "i", "ns": t.getFullName(), "o": {_id: 11}},
{
"op": "u",
"ns": t.getFullName(),
"o2": {_id: 10},
"o2": {_id: 11},
"o": {$v: NumberInt(1), $set: {z: 1, a: 2}}
}
]
}),
4772600);
}));
assert.eq(t.findOne({_id: 11}), {_id: 11, a: 2, z: 1}); // Note: 'a' and 'z' have been sorted.
// {$v: 2} entries encode diffs differently, and operations are applied in the order specified
// rather than in lexicographic order.
res = assert.commandWorked(db.adminCommand({
applyOps: [
{"op": "i", "ns": t.getFullName(), "o": {_id: 11, deleteField: 1}},
{"op": "i", "ns": t.getFullName(), "o": {_id: 12, deleteField: 1}},
{
"op": "u",
"ns": t.getFullName(),
"o2": {_id: 11},
"o2": {_id: 12},
// The diff indicates that 'deleteField' will be removed and 'newField' will be added
// with value "foo".
"o": {$v: NumberInt(2), diff: {d: {deleteField: false}, i: {newField: "foo"}}}
}
]
}));
assert.eq(t.findOne({_id: 11}), {_id: 11, newField: "foo"});
assert.eq(t.findOne({_id: 12}), {_id: 12, newField: "foo"});
// {$v: 3} does not exist yet, and we check that trying to use it throws an error.
res = assert.commandFailed(db.adminCommand({
applyOps: [
{"op": "i", "ns": t.getFullName(), "o": {_id: 12}},
{
// Test interesting $v field values
{
assert.commandWorked(
db.adminCommand({applyOps: [{"op": "i", "ns": t.getFullName(), "o": {_id: 13}}]}));
// {$v: 3} does not exist yet, and we check that trying to use it throws an error.
assert.commandFailedWithCode(db.adminCommand({
applyOps: [{
"op": "u",
"ns": t.getFullName(),
"o2": {_id: 12},
"o2": {_id: 13},
"o": {$v: NumberInt(3), diff: {d: {deleteField: false}}}
}
]
}));
assert.eq(res.code, 4772600);
}]
}),
4772600);
var insert_op1 = {_id: 13, x: 'inserted apply ops1'};
var insert_op2 = {_id: 14, x: 'inserted apply ops2'};
// Non-numeric values are interpreted as 0, which is no longer a valid oplog version.
assert.commandFailedWithCode(db.adminCommand({
applyOps: [{
"op": "u",
"ns": t.getFullName(),
"o2": {_id: 13},
"o": {$v: "2", diff: {d: {deleteField: false}}}
}]
}),
4772600);
// This is not behavior we rely on, it's just how it currently works.
assert.commandWorked(db.adminCommand({
applyOps: [{
"op": "u",
"ns": t.getFullName(),
"o2": {_id: 13},
"o": {$v: 2.34, diff: {d: {deleteField: false}}}
}]
}));
assert.commandWorked(db.adminCommand({
applyOps: [{
"op": "u",
"ns": t.getFullName(),
"o2": {_id: 13},
"o": {$v: NumberDecimal("2.357"), diff: {d: {deleteField: false}}}
}]
}));
}
var insert_op1 = {_id: 14, x: 'inserted apply ops1'};
var insert_op2 = {_id: 15, x: 'inserted apply ops2'};
assert.commandWorked(db.adminCommand({
"applyOps": [{
op: 'c',
@ -483,8 +526,8 @@ assert.commandWorked(db.adminCommand({
}]
}),
"Nested apply ops was NOT successful");
assert.eq(t.findOne({_id: 13}), insert_op1);
assert.eq(t.findOne({_id: 14}), insert_op2);
assert.eq(t.findOne({_id: 14}), insert_op1);
assert.eq(t.findOne({_id: 15}), insert_op2);
assert.commandWorked(db.adminCommand({
"applyOps": [{
@ -499,13 +542,13 @@ assert.commandWorked(db.adminCommand({
{
op: 'u',
ns: t.getFullName(),
o2: {_id: 13},
o2: {_id: 14},
o: {$v: 2, diff: {u: {x: 'nested apply op update1'}}},
},
{
op: 'u',
ns: t.getFullName(),
o2: {_id: 14},
o2: {_id: 15},
o: {$v: 2, diff: {u: {x: 'nested apply op update2'}}},
}
]
@ -515,8 +558,44 @@ assert.commandWorked(db.adminCommand({
}]
}),
"Nested apply ops was NOT successful");
assert.eq(t.findOne({_id: 13}), {_id: 13, x: 'nested apply op update1'});
assert.eq(t.findOne({_id: 14}), {_id: 14, x: 'nested apply op update2'});
assert.eq(t.findOne({_id: 14}), {_id: 14, x: 'nested apply op update1'});
assert.eq(t.findOne({_id: 15}), {_id: 15, x: 'nested apply op update2'});
// Ensure that nested applyOps works with implicit and explicit {$v:1} oplog entries.
assert.commandWorked(db.adminCommand({
"applyOps": [{
op: 'c',
ns: 'admin.$cmd',
o: {
"applyOps": [{
op: 'c',
ns: 'test.$cmd',
o: {
"applyOps": [
{
op: 'u',
ns: t.getFullName(),
o2: {_id: 14},
o: {"$set": {x: 'nested apply op update1 (implicit v1)'}},
},
{
op: 'u',
ns: t.getFullName(),
o2: {_id: 15},
o: {
$v: NumberInt(1),
"$set": {x: 'nested apply op update2 (explicit v1)'}
},
}
]
}
}],
}
}]
}),
"Nested apply ops was NOT successful");
assert.eq(t.findOne({_id: 14}), {_id: 14, x: 'nested apply op update1 (implicit v1)'});
assert.eq(t.findOne({_id: 15}), {_id: 15, x: 'nested apply op update2 (explicit v1)'});
// Operations without collection UUIDs should not crash (SERVER-82349)

View File

@ -1,7 +1,8 @@
/**
* Tests pipeline-style updates that use the $_internalApplyOplogUpdate aggregate stage.
* @tags: [
* requires_fcv_60,
* # This now tests applyOps with v1 oplog entries, which is only supported in FCV 8.0 onwards.
* requires_fcv_80,
* requires_multi_updates,
* requires_non_retryable_writes,
* requires_getmore,
@ -40,8 +41,8 @@ let documents2 = [{
assert.commandWorked(db.t2.insert(documents2));
//
// Test $_internalApplyOplogUpdate with v2 oplog update descriptions. For each update description,
// we execute $_internalApplyOplogUpdate twice to verify idempotency.
// Test $_internalApplyOplogUpdate with v1 oplog and v2 oplog update descriptions. For each
// update description, we execute $_internalApplyOplogUpdate twice to verify idempotency.
//
function testUpdate(expected, coll, filter, oplogUpdate, opts = {}) {
@ -59,14 +60,37 @@ let oplogUpdate = {"$v": NumberInt(2), diff: {u: {b: 3}}};
documents1[1].b = 3;
testUpdate(documents1, db.t1, {_id: 3}, oplogUpdate);
oplogUpdate = {
"$v": NumberInt(1),
"$set": {b: 2}
};
documents1[2].b = 2;
documents1[3].b = 2;
testUpdate(documents1, db.t1, {a: 0}, oplogUpdate, {multi: true});
oplogUpdate = {
"$v": NumberInt(1),
"$unset": {b: true}
};
delete documents1[1].b;
testUpdate(documents1, db.t1, {_id: 3}, oplogUpdate);
// Add field back so we can test deleting it with $v:2 oplog.
documents1[1].b = 3;
oplogUpdate = {
"$v": NumberInt(2),
diff: {d: {b: false}}
};
delete documents1[1].b;
testUpdate(documents1, db.t1, {_id: 3}, oplogUpdate);
oplogUpdate = {
"$v": NumberInt(1),
"$set": {"b.d": 2}
};
documents1[4].b.d = 2;
testUpdate(documents1, db.t1, {_id: 8}, oplogUpdate);
// Test an update with upsert=true where no documents match the filter prior to the update.
oplogUpdate = {
"$v": NumberInt(2),

View File

@ -1,8 +1,7 @@
/**
* These tests verify that the oplog entries are created correctly for updates
*
* Do not add more tests here but instead add C++ unit tests in db/query/write_ops/modifier*_test
* files
* Do not add more tests here but instead add C++ unit tests in db/update/[modifier]_node_test files
*
* @tags: [
* ]
@ -172,4 +171,4 @@ assert.eq(res.nModified, 1, "update failed for '" + msg + "': " + res.toString()
assert.docEq({_id: 1, a: {b: [{c: 1}, {c: -1}]}}, coll.findOne({}), msg);
assertLastOplog({"$v": 2, "diff": {"sa": {"u": {"b": [{"c": 1}, {"c": -1}]}}}}, {_id: 1}, msg);
replTest.stopSet();
replTest.stopSet();

View File

@ -0,0 +1,86 @@
/**
* These tests verify that we don't create v1 oplog entries when applying v1 updates supplied to
* applyOps.
*
* @tags: [
* # Applying v1 oplog entries with applyOps is only supported in 8.0 onwards.
* requires_fcv_80,
* ]
*/
import {ReplSetTest} from "jstests/libs/replsettest.js";
const rst = new ReplSetTest({nodes: 1, oplogSize: 2});
const nodes = rst.startSet();
rst.initiate();
const primary = rst.getPrimary();
const db = primary.getDB(jsTestName() + "_db");
const coll = db[jsTestName() + "_coll"];
function getLastOplogEntry() {
return primary.getDB("local").oplog.rs.find().limit(1).sort({$natural: -1}).next();
}
const assertLastOplog = function(o, o2, msg) {
const last = getLastOplogEntry();
assert.eq(last.ns, coll.getFullName(), "ns bad : " + msg);
assert.docEq(last.o, o, "o bad : " + msg);
if (o2)
assert.docEq(last.o2, o2, "o2 bad : " + msg);
return last.ts;
};
// Initialize the document we want to modify.
assert.commandWorked(coll.insert({_id: 1}));
assertLastOplog({_id: 1}, null, "insert -- setup");
{
const msg = "Explicitly versioned v1 entry: $set";
const res = assert.commandWorked(primary.adminCommand({
"applyOps": [{
"op": "u",
"ns": coll.getFullName(),
"o2": {"_id": 1},
"o": {"$v": 1, "$set": {"a": 1}}
}]
}));
assert.eq(res.results, [true], msg);
assertLastOplog({"$v": 2, "diff": {"i": {"a": 1}}}, {"_id": 1}, msg);
}
{
const msg = "Explicitly versioned v1 entry: $unset";
const res = assert.commandWorked(primary.adminCommand({
"applyOps": [{
"op": "u",
"ns": coll.getFullName(),
"o2": {"_id": 1},
"o": {"$v": 1, "$unset": {"a": ""}}
}]
}));
assert.eq(res.results, [true], msg);
assertLastOplog({"$v": 2, "diff": {"d": {"a": false}}}, {"_id": 1}, msg);
}
{
const msg = "Implicitly versioned v1 entry: $set";
const res = assert.commandWorked(primary.adminCommand({
"applyOps":
[{"op": "u", "ns": coll.getFullName(), "o2": {"_id": 1}, "o": {"$set": {"b": 1}}}]
}));
assert.eq(res.results, [true], msg);
assertLastOplog({"$v": 2, "diff": {"i": {"b": 1}}}, {"_id": 1}, msg);
}
{
const msg = "Implicitly versioned v1 entry: $unset";
const res = assert.commandWorked(primary.adminCommand({
"applyOps":
[{"op": "u", "ns": coll.getFullName(), "o2": {"_id": 1}, "o": {"$unset": {"b": ""}}}]
}));
assert.eq(res.results, [true], msg);
assertLastOplog({"$v": 2, "diff": {"d": {"b": false}}}, {"_id": 1}, msg);
}
rst.stopSet();

View File

@ -105,6 +105,15 @@ TEST_F(DocumentSourceInternalApplyOplogUpdateTest, ShouldRejectMalformedSpecs) {
DBException,
4772600);
spec = fromjson(
R"({$_internalApplyOplogUpdate: {
oplogUpdate: {"$v": "2", diff: {u: {b: 3}}}
}})");
ASSERT_THROWS_CODE(
DocumentSourceInternalApplyOplogUpdate::createFromBson(spec.firstElement(), getExpCtx()),
DBException,
4772600);
spec = fromjson(R"({$_internalApplyOplogUpdate: {oplogUpdate: {"$v": NumberInt(2)}}})");
ASSERT_THROWS_CODE(
DocumentSourceInternalApplyOplogUpdate::createFromBson(spec.firstElement(), getExpCtx()),

View File

@ -684,16 +684,18 @@ UpdateModification UpdateModification::parseFromOplogEntry(const BSONObj& oField
BSONElement idField = oField["_id"];
// If _id field is present, we're getting a replacement style update in which $v can be a user
// field. Otherwise, $v field has to be $v:2.
uassert(4772600,
str::stream() << "Expected _id field or $v:2, but got: " << vField,
idField.ok() ||
(vField.ok() &&
vField.safeNumberInt() == static_cast<int>(UpdateOplogEntryVersion::kDeltaV2)));
// field. Otherwise, the $v field has to be either missing or be one of $v:1 / $v:2.
uassert(
4772600,
str::stream() << "Expected _id field or $v field missing or $v:1/$v:2, but got: " << vField,
idField.ok() || !vField.ok() ||
vField.safeNumberInt() == static_cast<int>(UpdateOplogEntryVersion::kUpdateNodeV1) ||
vField.safeNumberInt() == static_cast<int>(UpdateOplogEntryVersion::kDeltaV2));
// It is important to check for '_id' field first, because a replacement style update can still
// have a '$v' field in the object.
if (!idField.ok()) {
if (!idField.ok() && vField.ok() &&
vField.safeNumberInt() == static_cast<int>(UpdateOplogEntryVersion::kDeltaV2)) {
// Make sure there's a diff field.
BSONElement diff = oField[update_oplog_entry::kDiffObjectFieldName];
uassert(4772601,
@ -703,8 +705,10 @@ UpdateModification UpdateModification::parseFromOplogEntry(const BSONObj& oField
return UpdateModification(doc_diff::Diff{diff.embeddedObject()}, DeltaTag{}, options);
} else {
// Treat it as a a full replacement update.
return UpdateModification(oField, ReplacementTag{});
// Treat it as a full replacement update or modifier update depending on the presence of
// the "_id" field.
return idField.ok() ? UpdateModification(oField, ReplacementTag{})
: UpdateModification(oField, ModifierUpdateTag{});
}
}

View File

@ -723,7 +723,7 @@ TEST_F(ReshardingOplogApplierTest, ErrorDuringFirstBatchApply) {
auto cancelToken = operationContext()->getCancellationToken();
CancelableOperationContextFactory factory(cancelToken, getCancelableOpCtxExecutor());
auto future = applier->run(getExecutor(), getExecutor(), cancelToken, factory);
ASSERT_EQ(future.getNoThrow(), ErrorCodes::duplicateCodeForTest(4772600));
ASSERT_EQ(future.getNoThrow(), ErrorCodes::FailedToParse);
DBDirectClient client(operationContext());
auto doc = client.findOne(appliedToNs(), BSON("_id" << 1));
@ -768,7 +768,7 @@ TEST_F(ReshardingOplogApplierTest, ErrorDuringSecondBatchApply) {
auto cancelToken = operationContext()->getCancellationToken();
CancelableOperationContextFactory factory(cancelToken, getCancelableOpCtxExecutor());
auto future = applier->run(getExecutor(), getExecutor(), cancelToken, factory);
ASSERT_EQ(future.getNoThrow(), ErrorCodes::duplicateCodeForTest(4772600));
ASSERT_EQ(future.getNoThrow(), ErrorCodes::FailedToParse);
DBDirectClient client(operationContext());
auto doc = client.findOne(appliedToNs(), BSON("_id" << 1));

View File

@ -96,7 +96,22 @@ bool parseUpdateExpression(
const std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>>& arrayFilters) {
bool positional = false;
std::set<std::string> foundIdentifiers;
bool foundVersionField = false;
for (auto&& mod : updateExpr) {
// If there is a "$v" field among the modifiers, it should have already been used by the
// caller to determine that this is the correct parsing function.
if (mod.fieldNameStringData() == kUpdateOplogEntryVersionFieldName) {
uassert(
ErrorCodes::BadValue, "Duplicate $v in oplog update document", !foundVersionField);
foundVersionField = true;
tassert(10721100,
"If we are in parseUpdateExpression for a modifier update and have a $v field, "
"it must be {$v:1}.",
mod.safeNumberInt() ==
static_cast<int>(UpdateOplogEntryVersion::kUpdateNodeV1));
continue;
}
auto modType = validateMod(mod);
for (auto&& field : mod.Obj()) {
auto statusWithPositional = UpdateObjectNode::parseAndMerge(
@ -188,15 +203,26 @@ void UpdateDriver::parse(
invariant(_updateType == UpdateType::kOperator);
// By this point we are expecting a "kModifier" update. This version of mongod only supports
// $v: 2 (delta) (older versions support $v: 0 and $v: 1). We've already checked whether
// this is a delta update, so we verify that we're not on the oplog application path.
tassert(5030100,
"An oplog update can only be of type 'kReplacement' or 'kDelta'",
!_fromOplogApplication);
// By this point we are expecting a "modifier" update. This version of mongod supports $v:1
// (modifier language) and $v:2 (delta) (older versions support $v:0). We've already checked
// whether this is a delta update so we check that the $v field isn't present, or has a value
// of 1.
auto updateExpr = updateMod.getUpdateModifier();
BSONElement versionElement = updateExpr[kUpdateOplogEntryVersionFieldName];
if (versionElement) {
uassert(ErrorCodes::FailedToParse,
"The $v update field is only recognized internally",
_fromOplogApplication);
// The UpdateModification should have verified that the value of $v is valid.
tassert(10721101,
"Modifier updates must be {$v:1} if $v is present",
versionElement.safeNumberInt() ==
static_cast<int>(UpdateOplogEntryVersion::kUpdateNodeV1));
}
auto root = std::make_unique<UpdateObjectNode>();
_positional =
parseUpdateExpression(updateMod.getUpdateModifier(), root.get(), _expCtx, arrayFilters);
_positional = parseUpdateExpression(updateExpr, root.get(), _expCtx, arrayFilters);
_updateExecutor = std::make_unique<UpdateTreeExecutor>(std::move(root));
}

View File

@ -178,6 +178,66 @@ TEST(Parse, SetOnInsert) {
ASSERT_FALSE(driver.type() == UpdateDriver::UpdateType::kReplacement);
}
TEST(Parse, V1OplogUpdatesOnNonOplogApplicationPath) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
UpdateDriver driver(expCtx);
// Oplog updates cannot be applied on the fromOplogApplication path
driver.setFromOplogApplication(false);
std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters;
ASSERT_THROWS_CODE_AND_WHAT(
driver.parse(makeUpdateMod(fromjson("{$v: 1, $set: {a:1}}")), arrayFilters),
AssertionException,
ErrorCodes::FailedToParse,
"The $v update field is only recognized internally");
}
TEST(Parse, V2OplogUpdatesOnNonOplogApplicationPath) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
UpdateDriver driver(expCtx);
// Oplog updates cannot be applied on the fromOplogApplication path
driver.setFromOplogApplication(false);
std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters;
ASSERT_THROWS_CODE_AND_WHAT(
driver.parse(makeUpdateMod(fromjson("{$v: 2, diff: {i: {a:1}}}")), arrayFilters),
AssertionException,
ErrorCodes::FailedToParse,
"The $v update field is only recognized internally");
}
TEST(Parse, ExplicitV1OplogEntry) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
UpdateDriver driver(expCtx);
// Oplog updates can only be applied on the fromOplogApplication path
driver.setFromOplogApplication(true);
std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters;
ASSERT_DOES_NOT_THROW(
driver.parse(makeUpdateMod(fromjson("{$v: 1, $set: {a:1}}")), arrayFilters));
ASSERT_TRUE(driver.type() == UpdateDriver::UpdateType::kOperator);
}
TEST(Parse, ImplicitV1OplogEntry) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
UpdateDriver driver(expCtx);
// Oplog updates can only be applied on the fromOplogApplication path
driver.setFromOplogApplication(true);
std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters;
ASSERT_DOES_NOT_THROW(driver.parse(makeUpdateMod(fromjson("{$set: {a:1}}")), arrayFilters));
ASSERT_TRUE(driver.type() == UpdateDriver::UpdateType::kOperator);
}
TEST(Parse, V1WithDuplicateVersionField) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
UpdateDriver driver(expCtx);
// Oplog updates can only be applied on the fromOplogApplication path
driver.setFromOplogApplication(true);
std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters;
ASSERT_THROWS_CODE_AND_WHAT(
driver.parse(makeUpdateMod(fromjson("{$v: 1, $set: {a:1}, $v: 1}")), arrayFilters),
AssertionException,
ErrorCodes::BadValue,
"Duplicate $v in oplog update document");
}
TEST(Collator, SetCollationUpdatesModifierInterfaces) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
CollatorInterfaceMock reverseStringCollator(CollatorInterfaceMock::MockType::kReverseString);

View File

@ -46,11 +46,12 @@ enum class UpdateOplogEntryVersion {
kRemovedV0 = 0,
// The update system introduced in v3.6, and, until 5.1, also served the function of how updates
// were record in oplog entries. Oplog entries of this form are no longer supported, but the
// user facing modifier-style update system remains. When a single update adds
// multiple fields, those fields are added in lexicographic order by field name. This system
// introduces support for arrayFilters and $[] syntax.
kUpdateNodeV1_NotSupprted = 1,
// were record in oplog entries. Oplog entries of this form are no longer generated, but the
// user facing modifier-style update system remains. However, the server is still capable of
// processing these oplog entries when they come from an applyOps command. When a single update
// adds multiple fields, those fields are added in lexicographic order by field name. This
// system introduces support for arrayFilters and $[] syntax.
kUpdateNodeV1 = 1,
// Delta style update, introduced in 4.7. When a pipeline based update is executed, the pre and
// post images are diffed, producing a delta. The delta is recorded in the oplog. On