mirror of https://github.com/mongodb/mongo
SERVER-107211 Allow v1 oplog entries in applyOps (#39402)
GitOrigin-RevId: 6c6161fa727047c944aee79e0b06886654421eb4
This commit is contained in:
parent
463fb36531
commit
5bac5853b5
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
@ -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()),
|
||||
|
|
|
|||
|
|
@ -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{});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue