/**
* Copyright (C) 2013 10gen Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the GNU Affero General Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include "mongo/db/ops/modifier_set.h"
#include
#include "mongo/base/status.h"
#include "mongo/base/string_data.h"
#include "mongo/bson/mutable/algorithm.h"
#include "mongo/bson/mutable/document.h"
#include "mongo/bson/mutable/mutable_bson_test_utils.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/json.h"
#include "mongo/db/ops/log_builder.h"
#include "mongo/unittest/unittest.h"
namespace {
using mongo::BSONObj;
using mongo::fromjson;
using mongo::LogBuilder;
using mongo::ModifierInterface;
using mongo::NumberInt;
using mongo::ModifierSet;
using mongo::Status;
using mongo::StringData;
using mongo::mutablebson::ConstElement;
using mongo::mutablebson::countChildren;
using mongo::mutablebson::Document;
using mongo::mutablebson::Element;
/** Helper to build and manipulate a $set mod. */
class Mod {
public:
Mod() : _mod() {}
explicit Mod(BSONObj modObj, bool fromRepl = false)
: _mod(mongoutils::str::equals(modObj.firstElement().fieldName(), "$setOnInsert")
? ModifierSet::SET_ON_INSERT
: ModifierSet::SET_NORMAL) {
_modObj = modObj;
StringData modName = modObj.firstElement().fieldName();
ASSERT_OK(_mod.init(_modObj[modName].embeddedObject().firstElement(),
!fromRepl ? ModifierInterface::Options::normal()
: ModifierInterface::Options::fromRepl()));
}
Status prepare(Element root, StringData matchedField, ModifierInterface::ExecInfo* execInfo) {
return _mod.prepare(root, matchedField, execInfo);
}
Status apply() const {
return _mod.apply();
}
Status log(LogBuilder* logBuilder) const {
return _mod.log(logBuilder);
}
ModifierSet& mod() {
return _mod;
}
private:
ModifierSet _mod;
BSONObj _modObj;
};
//
// Init tests
//
TEST(Init, EmptyOperation) {
BSONObj modObj = fromjson("{$set: {}}");
ModifierSet mod;
ASSERT_NOT_OK(mod.init(modObj["$set"].embeddedObject().firstElement(),
ModifierInterface::Options::normal()));
}
//
// Simple Mods
//
TEST(SimpleMod, PrepareNoOp) {
Document doc(fromjson("{a: 2}"));
Mod setMod(fromjson("{$set: {a: 2}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
ASSERT_TRUE(execInfo.noOp);
}
TEST(SimpleMod, PrepareNotNoOp) {
Document doc(fromjson("{a: NumberInt(2)}"));
Mod setMod(fromjson("{$set: {a: NumberLong(2)}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
ASSERT_FALSE(execInfo.noOp);
}
TEST(SimpleMod, PrepareIdentityOpOnDeserializedIsNotANoOp) {
Document doc(fromjson("{a: { b: NumberInt(0)}}"));
// Apply a mutation to the document that will make it non-serialized.
doc.root()["a"]["b"].setValueInt(2);
// Apply an op that would be a no-op.
Mod setMod(fromjson("{$set: {a: {b : NumberInt(2)}}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
ASSERT_FALSE(execInfo.noOp);
}
TEST(SimpleMod, PrepareSetOnInsert) {
Document doc(fromjson("{a: 1}"));
Mod setMod(fromjson("{$setOnInsert: {a: 2}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
ASSERT_FALSE(execInfo.noOp);
ASSERT_EQUALS(execInfo.context, ModifierInterface::ExecInfo::INSERT_CONTEXT);
}
TEST(SimpleMod, PrepareApplyEmptyDocument) {
Document doc(fromjson("{}"));
Mod setMod(fromjson("{$set: {a: 2}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
ASSERT_FALSE(execInfo.noOp);
ASSERT_OK(setMod.apply());
ASSERT_FALSE(doc.isInPlaceModeEnabled());
ASSERT_EQUALS(fromjson("{a: 2}"), doc);
}
TEST(SimpleMod, PrepareApplyInPlace) {
Document doc(fromjson("{a: 1}"));
Mod setMod(fromjson("{$set: {a: 2}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
ASSERT_FALSE(execInfo.noOp);
ASSERT_OK(setMod.apply());
ASSERT_TRUE(doc.isInPlaceModeEnabled());
ASSERT_EQUALS(fromjson("{a: 2}"), doc);
}
TEST(SimpleMod, PrepareApplyOverridePath) {
Document doc(fromjson("{a: {b: 1}}"));
Mod setMod(fromjson("{$set: {a: 2}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
ASSERT_FALSE(execInfo.noOp);
ASSERT_OK(setMod.apply());
ASSERT_FALSE(doc.isInPlaceModeEnabled());
ASSERT_EQUALS(fromjson("{a: 2}"), doc);
}
TEST(SimpleMod, PrepareApplyChangeType) {
Document doc(fromjson("{a: 'str'}"));
Mod setMod(fromjson("{$set: {a: 2}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
ASSERT_FALSE(execInfo.noOp);
ASSERT_OK(setMod.apply());
ASSERT_FALSE(doc.isInPlaceModeEnabled());
ASSERT_EQUALS(fromjson("{a: 2}"), doc);
}
TEST(SimpleMod, PrepareApplyNewPath) {
Document doc(fromjson("{b: 1}"));
Mod setMod(fromjson("{$set: {a: 2}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
ASSERT_FALSE(execInfo.noOp);
ASSERT_OK(setMod.apply());
ASSERT_FALSE(doc.isInPlaceModeEnabled());
ASSERT_EQUALS(fromjson("{b: 1, a: 2}"), doc);
}
TEST(SimpleMod, LogNormal) {
BSONObj obj = fromjson("{a: 1}");
Mod setMod(fromjson("{$set: {a: 2}}"));
Document doc(obj);
ModifierInterface::ExecInfo dummy;
ASSERT_OK(setMod.prepare(doc.root(), "", &dummy));
Document logDoc;
LogBuilder logBuilder(logDoc.root());
ASSERT_OK(setMod.log(&logBuilder));
ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
ASSERT_EQUALS(fromjson("{$set: {a: 2}}"), logDoc);
}
//
// Simple dotted mod
//
TEST(DottedMod, PrepareNoOp) {
Document doc(fromjson("{a: {b: 2}}"));
Mod setMod(fromjson("{$set: {'a.b': 2}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
ASSERT_TRUE(execInfo.noOp);
}
TEST(DottedMod, PrepareNotNoOp) {
Document doc(fromjson("{a: {b: NumberLong(2)}}"));
Mod setMod(fromjson("{$set: {'a.b': NumberInt(2)}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
ASSERT_FALSE(execInfo.noOp);
}
TEST(DottedMod, PreparePathNotViable) {
Document doc(fromjson("{a:1}"));
Mod setMod(fromjson("{$set: {'a.b': 2}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_NOT_OK(setMod.prepare(doc.root(), "", &execInfo));
}
TEST(DottedMod, PreparePathNotViableArrray) {
Document doc(fromjson("{a:[{b:1}]}"));
Mod setMod(fromjson("{$set: {'a.b': 2}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_NOT_OK(setMod.prepare(doc.root(), "", &execInfo));
}
TEST(DottedMod, PrepareApplyInPlace) {
Document doc(fromjson("{a: {b: 1}}"));
Mod setMod(fromjson("{$set: {'a.b': 2}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
ASSERT_FALSE(execInfo.noOp);
ASSERT_OK(setMod.apply());
ASSERT_TRUE(doc.isInPlaceModeEnabled());
ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc);
}
TEST(DottedMod, PrepareApplyChangeType) {
Document doc(fromjson("{a: {b: 'str'}}"));
Mod setMod(fromjson("{$set: {'a.b': 2}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
ASSERT_FALSE(execInfo.noOp);
ASSERT_OK(setMod.apply());
ASSERT_FALSE(doc.isInPlaceModeEnabled());
ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc);
}
TEST(DottedMod, PrepareApplyChangePath) {
Document doc(fromjson("{a: {b: {c: 1}}}"));
Mod setMod(fromjson("{$set: {'a.b': 2}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
ASSERT_FALSE(execInfo.noOp);
ASSERT_OK(setMod.apply());
ASSERT_FALSE(doc.isInPlaceModeEnabled());
ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc);
}
TEST(DottedMod, PrepareApplyExtendPath) {
Document doc(fromjson("{a: {c: 1}}"));
Mod setMod(fromjson("{$set: {'a.b': 2}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
ASSERT_FALSE(execInfo.noOp);
ASSERT_OK(setMod.apply());
ASSERT_FALSE(doc.isInPlaceModeEnabled());
ASSERT_EQUALS(fromjson("{a: {c: 1, b: 2}}"), doc);
}
TEST(DottedMod, PrepareApplyNewPath) {
Document doc(fromjson("{c: 1}"));
Mod setMod(fromjson("{$set: {'a.b': 2}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
ASSERT_FALSE(execInfo.noOp);
ASSERT_OK(setMod.apply());
ASSERT_FALSE(doc.isInPlaceModeEnabled());
ASSERT_EQUALS(fromjson("{c: 1, a: {b: 2}}"), doc);
}
TEST(DottedMod, PrepareApplyEmptyDoc) {
Document doc(fromjson("{}"));
Mod setMod(fromjson("{$set: {'a.b': 2}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
ASSERT_FALSE(execInfo.noOp);
ASSERT_OK(setMod.apply());
ASSERT_FALSE(doc.isInPlaceModeEnabled());
ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc);
}
TEST(DottedMod, PrepareApplyFieldWithDot) {
Document doc(fromjson("{'a.b':4}"));
Mod setMod(fromjson("{$set: {'a.b': 2}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
ASSERT_FALSE(execInfo.noOp);
ASSERT_OK(setMod.apply());
ASSERT_FALSE(doc.isInPlaceModeEnabled());
ASSERT_EQUALS(fromjson("{'a.b':4, a: {b: 2}}"), doc);
}
//
// Indexed mod
//
TEST(IndexedMod, PrepareNoOp) {
Document doc(fromjson("{a: [{b: 0},{b: 1},{b: 2}]}"));
Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
ASSERT_TRUE(execInfo.noOp);
}
TEST(IndexedMod, PrepareNotNoOp) {
Document doc(fromjson("{a: [{b: 0},{b: 1},{b: 2.0}]}"));
Mod setMod(fromjson("{$set: {'a.2.b': NumberInt(2)}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
ASSERT_FALSE(execInfo.noOp);
}
TEST(IndexedMod, PrepareNonViablePath) {
Document doc(fromjson("{a: 0}"));
Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_NOT_OK(setMod.prepare(doc.root(), "", &execInfo));
}
TEST(IndexedMod, PrepareApplyInPlace) {
Document doc(fromjson("{a: [{b: 0},{b: 1},{b: 1}]}"));
Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
ASSERT_FALSE(execInfo.noOp);
ASSERT_OK(setMod.apply());
ASSERT_TRUE(doc.isInPlaceModeEnabled());
ASSERT_EQUALS(fromjson("{a: [{b: 0},{b: 1},{b: 2}]}"), doc);
}
TEST(IndexedMod, PrepareApplyNormalArray) {
Document doc(fromjson("{a: [{b: 0},{b: 1}]}"));
Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
ASSERT_FALSE(execInfo.noOp);
ASSERT_OK(setMod.apply());
ASSERT_FALSE(doc.isInPlaceModeEnabled());
ASSERT_EQUALS(fromjson("{a: [{b: 0},{b: 1},{b: 2}]}"), doc);
}
TEST(IndexedMod, PrepareApplyPaddingArray) {
Document doc(fromjson("{a: [{b: 0}]}"));
Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
ASSERT_FALSE(execInfo.noOp);
ASSERT_OK(setMod.apply());
ASSERT_FALSE(doc.isInPlaceModeEnabled());
ASSERT_EQUALS(fromjson("{a: [{b: 0},null,{b: 2}]}"), doc);
}
TEST(IndexedMod, PrepareApplyNumericObject) {
Document doc(fromjson("{a: {b: 0}}"));
Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
ASSERT_FALSE(execInfo.noOp);
ASSERT_OK(setMod.apply());
ASSERT_FALSE(doc.isInPlaceModeEnabled());
ASSERT_EQUALS(fromjson("{a: {b: 0, '2': {b: 2}}}"), doc);
}
TEST(IndexedMod, PrepareApplyNumericField) {
Document doc(fromjson("{a: {'2': {b: 1}}}"));
Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
ASSERT_FALSE(execInfo.noOp);
ASSERT_OK(setMod.apply());
ASSERT_TRUE(doc.isInPlaceModeEnabled());
ASSERT_EQUALS(fromjson("{a: {'2': {b: 2}}}"), doc);
}
TEST(IndexedMod, PrepareApplyExtendNumericField) {
Document doc(fromjson("{a: {'2': {c: 1}}}"));
Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
ASSERT_FALSE(execInfo.noOp);
ASSERT_OK(setMod.apply());
ASSERT_FALSE(doc.isInPlaceModeEnabled());
ASSERT_EQUALS(fromjson("{a: {'2': {c: 1, b: 2}}}"), doc);
}
TEST(IndexedMod, PrepareApplyEmptyObject) {
Document doc(fromjson("{a: {}}"));
Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
ASSERT_FALSE(execInfo.noOp);
ASSERT_OK(setMod.apply());
ASSERT_FALSE(doc.isInPlaceModeEnabled());
ASSERT_EQUALS(fromjson("{a: {'2': {b: 2}}}"), doc);
}
TEST(IndexedMod, PrepareApplyEmptyArray) {
Document doc(fromjson("{a: []}"));
Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
ASSERT_FALSE(execInfo.noOp);
ASSERT_OK(setMod.apply());
ASSERT_FALSE(doc.isInPlaceModeEnabled());
ASSERT_EQUALS(fromjson("{a: [null, null, {b: 2}]}"), doc);
}
TEST(IndexedMod, PrepareApplyInexistent) {
Document doc(fromjson("{}"));
Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
ASSERT_FALSE(execInfo.noOp);
ASSERT_OK(setMod.apply());
ASSERT_FALSE(doc.isInPlaceModeEnabled());
ASSERT_EQUALS(fromjson("{a: {'2': {b: 2}}}"), doc);
}
TEST(IndexedMod, LogNormal) {
BSONObj obj = fromjson("{a: [{b:0}, {b:1}]}");
Document doc(obj);
Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
Document logDoc;
LogBuilder logBuilder(logDoc.root());
ASSERT_OK(setMod.log(&logBuilder));
ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
ASSERT_EQUALS(fromjson("{$set: {'a.2.b': 2}}"), logDoc);
}
TEST(IndexedMod, LogEmptyArray) {
BSONObj obj = fromjson("{a: []}");
Document doc(obj);
Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
Document logDoc;
LogBuilder logBuilder(logDoc.root());
ASSERT_OK(setMod.log(&logBuilder));
ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
ASSERT_EQUALS(fromjson("{$set: {'a.2.b': 2}}"), logDoc);
}
TEST(IndexedMod, LogEmptyObject) {
BSONObj obj = fromjson("{a: []}");
Document doc(obj);
Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
Document logDoc;
LogBuilder logBuilder(logDoc.root());
ASSERT_OK(setMod.log(&logBuilder));
ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
ASSERT_EQUALS(fromjson("{$set: {'a.2.b': 2}}"), logDoc);
}
//
// Indexed complex mod
//
TEST(IndexedComplexMod, PrepareNoOp) {
Document doc(fromjson("{a: [{b: {c: 0, d: 0}}, {b: {c: 1, d: 1}}]}}"));
Mod setMod(fromjson("{$set: {'a.1.b': {c: 1, d: 1}}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.b");
ASSERT_TRUE(execInfo.noOp);
}
TEST(IndexedComplexMod, PrepareSameStructure) {
Document doc(fromjson("{a: [{b: {c: 0, d: 0}}, {b: {c: 1, xxx: 1}}]}}"));
Mod setMod(fromjson("{$set: {'a.1.b': {c: 1, d: 1}}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.b");
ASSERT_FALSE(execInfo.noOp);
}
//
// Replication version where viable paths don't block modification
//
TEST(NonViablePathWithoutRepl, ControlRun) {
Document doc(fromjson("{a: 1}"));
Mod setMod(fromjson("{$set: {'a.1.b': 1}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_NOT_OK(setMod.prepare(doc.root(), "", &execInfo));
}
TEST(NonViablePathWithRepl, SingleField) {
Document doc(fromjson("{_id:1, a: 1}"));
Mod setMod(fromjson("{$set: {'a.1.b': 1}}"), true);
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.b");
ASSERT_FALSE(execInfo.noOp);
ASSERT_OK(setMod.apply());
ASSERT_FALSE(doc.isInPlaceModeEnabled());
ASSERT_EQUALS(fromjson("{_id:1, a: {'1': {b: 1}}}"), doc);
}
TEST(NonViablePathWithRepl, SingleFieldNoId) {
Document doc(fromjson("{a: 1}"));
Mod setMod(fromjson("{$set: {'a.1.b': 1}}"), true);
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.b");
ASSERT_FALSE(execInfo.noOp);
ASSERT_OK(setMod.apply());
ASSERT_FALSE(doc.isInPlaceModeEnabled());
ASSERT_EQUALS(fromjson("{a: {'1': {b: 1}}}"), doc);
}
TEST(NonViablePathWithRepl, NestedField) {
Document doc(fromjson("{_id:1, a: {a: 1}}"));
Mod setMod(fromjson("{$set: {'a.a.1.b': 1}}"), true);
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.a.1.b");
ASSERT_FALSE(execInfo.noOp);
ASSERT_OK(setMod.apply());
ASSERT_FALSE(doc.isInPlaceModeEnabled());
ASSERT_EQUALS(fromjson("{_id:1, a: {a: {'1': {b: 1}}}}"), doc);
}
TEST(NonViablePathWithRepl, DoubleNestedField) {
Document doc(fromjson("{_id:1, a: {b: {c: 1}}}"));
Mod setMod(fromjson("{$set: {'a.b.c.d': 2}}"), true);
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b.c.d");
ASSERT_FALSE(execInfo.noOp);
ASSERT_OK(setMod.apply());
ASSERT_FALSE(doc.isInPlaceModeEnabled());
ASSERT_EQUALS(fromjson("{_id:1, a: {b: {c: {d: 2}}}}"), doc);
}
TEST(NonViablePathWithRepl, NestedFieldNoId) {
Document doc(fromjson("{a: {a: 1}}"));
Mod setMod(fromjson("{$set: {'a.a.1.b': 1}}"), true);
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.a.1.b");
ASSERT_FALSE(execInfo.noOp);
ASSERT_OK(setMod.apply());
ASSERT_FALSE(doc.isInPlaceModeEnabled());
ASSERT_EQUALS(fromjson("{a: {a: {'1': {b: 1}}}}"), doc);
}
TEST(NonViablePathWithRepl, ReplayArrayFieldNotAppendedItermediate) {
Document doc(fromjson("{_id: 0, a: [1, {b: [1]}]}"));
Mod setMod(fromjson("{$set: {'a.0.b': [0,2]}}}"), true);
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0.b");
ASSERT_FALSE(execInfo.noOp);
ASSERT_OK(setMod.apply());
ASSERT_FALSE(doc.isInPlaceModeEnabled());
ASSERT_EQUALS(fromjson("{_id: 0, a: [{b: [0,2]}, {b: [1]}]}"), doc);
}
// Cases from users/issues/jstests
TEST(JsTestIssues, Set6) {
Document doc(fromjson("{_id: 1, r: {a:1, b:2}}"));
Mod setMod(fromjson("{$set: {'r.a': 2}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "r.a");
ASSERT_FALSE(execInfo.noOp);
ASSERT_OK(setMod.apply());
ASSERT_TRUE(doc.isInPlaceModeEnabled());
ASSERT_EQUALS(fromjson("{_id: 1, r: {a:2, b:2}}"), doc);
Document logDoc;
LogBuilder logBuilder(logDoc.root());
ASSERT_OK(setMod.log(&logBuilder));
ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
ASSERT_EQUALS(fromjson("{$set: {'r.a': 2}}"), logDoc);
}
// Test which failed before and is here to verify correct execution.
TEST(JsTestIssues, Set6FromRepl) {
Document doc(fromjson("{_id: 1, r: {a:1, b:2}}"));
Mod setMod(fromjson("{$set: { 'r.a': 2}}"), true);
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "r.a");
ASSERT_FALSE(execInfo.noOp);
ASSERT_OK(setMod.apply());
ASSERT_TRUE(doc.isInPlaceModeEnabled());
ASSERT_EQUALS(fromjson("{_id: 1, r: {a:2, b:2} }"), doc);
Document logDoc;
LogBuilder logBuilder(logDoc.root());
ASSERT_OK(setMod.log(&logBuilder));
ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
ASSERT_EQUALS(fromjson("{$set: {'r.a': 2}}"), logDoc);
}
TEST(Ephemeral, ApplySetModToEphemeralDocument) {
// The following mod when applied to a document constructed node by node exposed a
// latent debug only defect in mutable BSON, so this is more a test of mutable than
// $set.
Document doc;
Element x = doc.makeElementObject("x");
doc.root().pushBack(x);
Element a = doc.makeElementInt("a", 100);
x.pushBack(a);
Mod setMod(fromjson("{ $set: { x: { a: 100, b: 2 }}}"), true);
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "x");
ASSERT_FALSE(execInfo.noOp);
ASSERT_OK(setMod.apply());
ASSERT_FALSE(doc.isInPlaceModeEnabled());
ASSERT_EQUALS(fromjson("{ x : { a : 100, b : 2 } }"), doc);
}
} // unnamed namespace