summaryrefslogtreecommitdiff
path: root/src/mongo/db/ops
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/ops')
-rw-r--r--src/mongo/db/ops/delete.cpp64
-rw-r--r--src/mongo/db/ops/delete.h23
-rw-r--r--src/mongo/db/ops/delete_request.h125
-rw-r--r--src/mongo/db/ops/field_checker.cpp65
-rw-r--r--src/mongo/db/ops/field_checker.h50
-rw-r--r--src/mongo/db/ops/field_checker_test.cpp132
-rw-r--r--src/mongo/db/ops/insert.cpp292
-rw-r--r--src/mongo/db/ops/insert.h35
-rw-r--r--src/mongo/db/ops/log_builder.cpp235
-rw-r--r--src/mongo/db/ops/log_builder.h149
-rw-r--r--src/mongo/db/ops/log_builder_test.cpp453
-rw-r--r--src/mongo/db/ops/modifier_add_to_set.cpp572
-rw-r--r--src/mongo/db/ops/modifier_add_to_set.h68
-rw-r--r--src/mongo/db/ops/modifier_add_to_set_test.cpp678
-rw-r--r--src/mongo/db/ops/modifier_bit.cpp394
-rw-r--r--src/mongo/db/ops/modifier_bit.h82
-rw-r--r--src/mongo/db/ops/modifier_bit_test.cpp1288
-rw-r--r--src/mongo/db/ops/modifier_compare.cpp233
-rw-r--r--src/mongo/db/ops/modifier_compare.h135
-rw-r--r--src/mongo/db/ops/modifier_compare_test.cpp487
-rw-r--r--src/mongo/db/ops/modifier_current_date.cpp358
-rw-r--r--src/mongo/db/ops/modifier_current_date.h73
-rw-r--r--src/mongo/db/ops/modifier_current_date_test.cpp600
-rw-r--r--src/mongo/db/ops/modifier_inc.cpp399
-rw-r--r--src/mongo/db/ops/modifier_inc.h115
-rw-r--r--src/mongo/db/ops/modifier_inc_test.cpp933
-rw-r--r--src/mongo/db/ops/modifier_interface.h275
-rw-r--r--src/mongo/db/ops/modifier_object_replace.cpp240
-rw-r--r--src/mongo/db/ops/modifier_object_replace.h113
-rw-r--r--src/mongo/db/ops/modifier_object_replace_test.cpp506
-rw-r--r--src/mongo/db/ops/modifier_pop.cpp269
-rw-r--r--src/mongo/db/ops/modifier_pop.h67
-rw-r--r--src/mongo/db/ops/modifier_pop_test.cpp462
-rw-r--r--src/mongo/db/ops/modifier_pull.cpp399
-rw-r--r--src/mongo/db/ops/modifier_pull.h67
-rw-r--r--src/mongo/db/ops/modifier_pull_all.cpp325
-rw-r--r--src/mongo/db/ops/modifier_pull_all.h64
-rw-r--r--src/mongo/db/ops/modifier_pull_all_test.cpp389
-rw-r--r--src/mongo/db/ops/modifier_pull_test.cpp901
-rw-r--r--src/mongo/db/ops/modifier_push.cpp1000
-rw-r--r--src/mongo/db/ops/modifier_push.h182
-rw-r--r--src/mongo/db/ops/modifier_push_sorter.h57
-rw-r--r--src/mongo/db/ops/modifier_push_sorter_test.cpp232
-rw-r--r--src/mongo/db/ops/modifier_push_test.cpp2445
-rw-r--r--src/mongo/db/ops/modifier_rename.cpp429
-rw-r--r--src/mongo/db/ops/modifier_rename.h101
-rw-r--r--src/mongo/db/ops/modifier_rename_test.cpp676
-rw-r--r--src/mongo/db/ops/modifier_set.cpp389
-rw-r--r--src/mongo/db/ops/modifier_set.h142
-rw-r--r--src/mongo/db/ops/modifier_set_test.cpp1126
-rw-r--r--src/mongo/db/ops/modifier_table.cpp125
-rw-r--r--src/mongo/db/ops/modifier_table.h64
-rw-r--r--src/mongo/db/ops/modifier_table_test.cpp32
-rw-r--r--src/mongo/db/ops/modifier_unset.cpp214
-rw-r--r--src/mongo/db/ops/modifier_unset.h120
-rw-r--r--src/mongo/db/ops/modifier_unset_test.cpp628
-rw-r--r--src/mongo/db/ops/parsed_delete.cpp142
-rw-r--r--src/mongo/db/ops/parsed_delete.h152
-rw-r--r--src/mongo/db/ops/parsed_update.cpp206
-rw-r--r--src/mongo/db/ops/parsed_update.h187
-rw-r--r--src/mongo/db/ops/path_support.cpp609
-rw-r--r--src/mongo/db/ops/path_support.h295
-rw-r--r--src/mongo/db/ops/path_support_test.cpp1606
-rw-r--r--src/mongo/db/ops/update.cpp124
-rw-r--r--src/mongo/db/ops/update.h42
-rw-r--r--src/mongo/db/ops/update_driver.cpp576
-rw-r--r--src/mongo/db/ops/update_driver.h283
-rw-r--r--src/mongo/db/ops/update_driver_test.cpp695
-rw-r--r--src/mongo/db/ops/update_lifecycle.h63
-rw-r--r--src/mongo/db/ops/update_lifecycle_impl.cpp69
-rw-r--r--src/mongo/db/ops/update_lifecycle_impl.h39
-rw-r--r--src/mongo/db/ops/update_request.h392
-rw-r--r--src/mongo/db/ops/update_result.cpp29
-rw-r--r--src/mongo/db/ops/update_result.h52
74 files changed, 12563 insertions, 13075 deletions
diff --git a/src/mongo/db/ops/delete.cpp b/src/mongo/db/ops/delete.cpp
index ecf4f752873..86ed88395b5 100644
--- a/src/mongo/db/ops/delete.cpp
+++ b/src/mongo/db/ops/delete.cpp
@@ -38,41 +38,41 @@
namespace mongo {
- /* ns: namespace, e.g. <database>.<collection>
- pattern: the "where" clause / criteria
- justOne: stop after 1 match
- god: allow access to system namespaces, and don't yield
- */
- long long deleteObjects(OperationContext* txn,
- Database* db,
- StringData ns,
- BSONObj pattern,
- PlanExecutor::YieldPolicy policy,
- bool justOne,
- bool god,
- bool fromMigrate) {
- NamespaceString nsString(ns);
- DeleteRequest request(nsString);
- request.setQuery(pattern);
- request.setMulti(!justOne);
- request.setGod(god);
- request.setFromMigrate(fromMigrate);
- request.setYieldPolicy(policy);
+/* ns: namespace, e.g. <database>.<collection>
+ pattern: the "where" clause / criteria
+ justOne: stop after 1 match
+ god: allow access to system namespaces, and don't yield
+*/
+long long deleteObjects(OperationContext* txn,
+ Database* db,
+ StringData ns,
+ BSONObj pattern,
+ PlanExecutor::YieldPolicy policy,
+ bool justOne,
+ bool god,
+ bool fromMigrate) {
+ NamespaceString nsString(ns);
+ DeleteRequest request(nsString);
+ request.setQuery(pattern);
+ request.setMulti(!justOne);
+ request.setGod(god);
+ request.setFromMigrate(fromMigrate);
+ request.setYieldPolicy(policy);
- Collection* collection = NULL;
- if (db) {
- collection = db->getCollection(nsString.ns());
- }
+ Collection* collection = NULL;
+ if (db) {
+ collection = db->getCollection(nsString.ns());
+ }
- ParsedDelete parsedDelete(txn, &request);
- uassertStatusOK(parsedDelete.parseRequest());
+ ParsedDelete parsedDelete(txn, &request);
+ uassertStatusOK(parsedDelete.parseRequest());
- PlanExecutor* rawExec;
- uassertStatusOK(getExecutorDelete(txn, collection, &parsedDelete, &rawExec));
- std::unique_ptr<PlanExecutor> exec(rawExec);
+ PlanExecutor* rawExec;
+ uassertStatusOK(getExecutorDelete(txn, collection, &parsedDelete, &rawExec));
+ std::unique_ptr<PlanExecutor> exec(rawExec);
- uassertStatusOK(exec->executePlan());
- return DeleteStage::getNumDeleted(exec.get());
- }
+ uassertStatusOK(exec->executePlan());
+ return DeleteStage::getNumDeleted(exec.get());
+}
} // namespace mongo
diff --git a/src/mongo/db/ops/delete.h b/src/mongo/db/ops/delete.h
index 563734428ab..b4b7345d70b 100644
--- a/src/mongo/db/ops/delete.h
+++ b/src/mongo/db/ops/delete.h
@@ -36,16 +36,15 @@
namespace mongo {
- class Database;
- class OperationContext;
-
- long long deleteObjects(OperationContext* txn,
- Database* db,
- StringData ns,
- BSONObj pattern,
- PlanExecutor::YieldPolicy policy,
- bool justOne,
- bool god = false,
- bool fromMigrate = false);
-
+class Database;
+class OperationContext;
+
+long long deleteObjects(OperationContext* txn,
+ Database* db,
+ StringData ns,
+ BSONObj pattern,
+ PlanExecutor::YieldPolicy policy,
+ bool justOne,
+ bool god = false,
+ bool fromMigrate = false);
}
diff --git a/src/mongo/db/ops/delete_request.h b/src/mongo/db/ops/delete_request.h
index 27ea4f2bbb9..8ab27dab596 100644
--- a/src/mongo/db/ops/delete_request.h
+++ b/src/mongo/db/ops/delete_request.h
@@ -36,52 +36,91 @@
namespace mongo {
- class DeleteRequest {
- MONGO_DISALLOW_COPYING(DeleteRequest);
- public:
- explicit DeleteRequest(const NamespaceString& nsString) :
- _nsString(nsString),
- _multi(false),
- _god(false),
- _fromMigrate(false),
- _isExplain(false),
- _returnDeleted(false),
- _yieldPolicy(PlanExecutor::YIELD_MANUAL) {}
+class DeleteRequest {
+ MONGO_DISALLOW_COPYING(DeleteRequest);
- void setQuery(const BSONObj& query) { _query = query; }
- void setProj(const BSONObj& proj) { _proj = proj; }
- void setSort(const BSONObj& sort) { _sort = sort; }
- void setMulti(bool multi = true) { _multi = multi; }
- void setGod(bool god = true) { _god = god; }
- void setFromMigrate(bool fromMigrate = true) { _fromMigrate = fromMigrate; }
- void setExplain(bool isExplain = true) { _isExplain = isExplain; }
- void setReturnDeleted(bool returnDeleted = true) { _returnDeleted = returnDeleted; }
- void setYieldPolicy(PlanExecutor::YieldPolicy yieldPolicy) { _yieldPolicy = yieldPolicy; }
+public:
+ explicit DeleteRequest(const NamespaceString& nsString)
+ : _nsString(nsString),
+ _multi(false),
+ _god(false),
+ _fromMigrate(false),
+ _isExplain(false),
+ _returnDeleted(false),
+ _yieldPolicy(PlanExecutor::YIELD_MANUAL) {}
- const NamespaceString& getNamespaceString() const { return _nsString; }
- const BSONObj& getQuery() const { return _query; }
- const BSONObj& getProj() const { return _proj; }
- const BSONObj& getSort() const { return _sort; }
- bool isMulti() const { return _multi; }
- bool isGod() const { return _god; }
- bool isFromMigrate() const { return _fromMigrate; }
- bool isExplain() const { return _isExplain; }
- bool shouldReturnDeleted() const { return _returnDeleted; }
- PlanExecutor::YieldPolicy getYieldPolicy() const { return _yieldPolicy; }
+ void setQuery(const BSONObj& query) {
+ _query = query;
+ }
+ void setProj(const BSONObj& proj) {
+ _proj = proj;
+ }
+ void setSort(const BSONObj& sort) {
+ _sort = sort;
+ }
+ void setMulti(bool multi = true) {
+ _multi = multi;
+ }
+ void setGod(bool god = true) {
+ _god = god;
+ }
+ void setFromMigrate(bool fromMigrate = true) {
+ _fromMigrate = fromMigrate;
+ }
+ void setExplain(bool isExplain = true) {
+ _isExplain = isExplain;
+ }
+ void setReturnDeleted(bool returnDeleted = true) {
+ _returnDeleted = returnDeleted;
+ }
+ void setYieldPolicy(PlanExecutor::YieldPolicy yieldPolicy) {
+ _yieldPolicy = yieldPolicy;
+ }
- std::string toString() const;
+ const NamespaceString& getNamespaceString() const {
+ return _nsString;
+ }
+ const BSONObj& getQuery() const {
+ return _query;
+ }
+ const BSONObj& getProj() const {
+ return _proj;
+ }
+ const BSONObj& getSort() const {
+ return _sort;
+ }
+ bool isMulti() const {
+ return _multi;
+ }
+ bool isGod() const {
+ return _god;
+ }
+ bool isFromMigrate() const {
+ return _fromMigrate;
+ }
+ bool isExplain() const {
+ return _isExplain;
+ }
+ bool shouldReturnDeleted() const {
+ return _returnDeleted;
+ }
+ PlanExecutor::YieldPolicy getYieldPolicy() const {
+ return _yieldPolicy;
+ }
- private:
- const NamespaceString& _nsString;
- BSONObj _query;
- BSONObj _proj;
- BSONObj _sort;
- bool _multi;
- bool _god;
- bool _fromMigrate;
- bool _isExplain;
- bool _returnDeleted;
- PlanExecutor::YieldPolicy _yieldPolicy;
- };
+ std::string toString() const;
+
+private:
+ const NamespaceString& _nsString;
+ BSONObj _query;
+ BSONObj _proj;
+ BSONObj _sort;
+ bool _multi;
+ bool _god;
+ bool _fromMigrate;
+ bool _isExplain;
+ bool _returnDeleted;
+ PlanExecutor::YieldPolicy _yieldPolicy;
+};
} // namespace mongo
diff --git a/src/mongo/db/ops/field_checker.cpp b/src/mongo/db/ops/field_checker.cpp
index da6607ac229..0c71c7e5d07 100644
--- a/src/mongo/db/ops/field_checker.cpp
+++ b/src/mongo/db/ops/field_checker.cpp
@@ -34,51 +34,50 @@
namespace mongo {
- using mongoutils::str::stream;
+using mongoutils::str::stream;
namespace fieldchecker {
- Status isUpdatable(const FieldRef& field) {
- const size_t numParts = field.numParts();
+Status isUpdatable(const FieldRef& field) {
+ const size_t numParts = field.numParts();
- if (numParts == 0) {
- return Status(ErrorCodes::EmptyFieldName,
- "An empty update path is not valid.");
- }
+ if (numParts == 0) {
+ return Status(ErrorCodes::EmptyFieldName, "An empty update path is not valid.");
+ }
- for (size_t i = 0; i != numParts; ++i) {
- const StringData part = field.getPart(i);
+ for (size_t i = 0; i != numParts; ++i) {
+ const StringData part = field.getPart(i);
- if (part.empty()) {
- return Status(ErrorCodes::EmptyFieldName,
- mongoutils::str::stream() << "The update path '"
- << field.dottedField()
+ if (part.empty()) {
+ return Status(ErrorCodes::EmptyFieldName,
+ mongoutils::str::stream()
+ << "The update path '" << field.dottedField()
<< "' contains an empty field name, which is not allowed.");
- }
}
-
- return Status::OK();
}
- bool isPositional(const FieldRef& fieldRef, size_t* pos, size_t* count) {
+ return Status::OK();
+}
- // 'count' is optional.
- size_t dummy;
- if (count == NULL) {
- count = &dummy;
- }
+bool isPositional(const FieldRef& fieldRef, size_t* pos, size_t* count) {
+ // 'count' is optional.
+ size_t dummy;
+ if (count == NULL) {
+ count = &dummy;
+ }
- *count = 0;
- size_t size = fieldRef.numParts();
- for (size_t i=0; i<size; i++) {
- StringData fieldPart = fieldRef.getPart(i);
- if ((fieldPart.size() == 1) && (fieldPart[0] == '$')) {
- if (*count == 0) *pos = i;
- (*count)++;
- }
+ *count = 0;
+ size_t size = fieldRef.numParts();
+ for (size_t i = 0; i < size; i++) {
+ StringData fieldPart = fieldRef.getPart(i);
+ if ((fieldPart.size() == 1) && (fieldPart[0] == '$')) {
+ if (*count == 0)
+ *pos = i;
+ (*count)++;
}
- return *count > 0;
}
+ return *count > 0;
+}
-} // namespace fieldchecker
-} // namespace mongo
+} // namespace fieldchecker
+} // namespace mongo
diff --git a/src/mongo/db/ops/field_checker.h b/src/mongo/db/ops/field_checker.h
index 544ea131074..772034d4773 100644
--- a/src/mongo/db/ops/field_checker.h
+++ b/src/mongo/db/ops/field_checker.h
@@ -32,28 +32,28 @@
namespace mongo {
- class FieldRef;
-
- namespace fieldchecker {
-
- /**
- * Returns OK if all the below conditions on 'field' are valid:
- * + Non-empty
- * + Does not start or end with a '.'
- * Otherwise returns a code indicating cause of failure.
- */
- Status isUpdatable(const FieldRef& field);
-
- /**
- * Returns true, the position 'pos' of the first $-sign if present in 'fieldRef', and
- * how many other $-signs were found in 'count'. Otherwise return false.
- *
- * Note:
- * isPositional assumes that the field is updatable. Call isUpdatable() above to
- * verify.
- */
- bool isPositional(const FieldRef& fieldRef, size_t* pos, size_t* count = NULL);
-
- } // namespace fieldchecker
-
-} // namespace mongo
+class FieldRef;
+
+namespace fieldchecker {
+
+/**
+ * Returns OK if all the below conditions on 'field' are valid:
+ * + Non-empty
+ * + Does not start or end with a '.'
+ * Otherwise returns a code indicating cause of failure.
+ */
+Status isUpdatable(const FieldRef& field);
+
+/**
+ * Returns true, the position 'pos' of the first $-sign if present in 'fieldRef', and
+ * how many other $-signs were found in 'count'. Otherwise return false.
+ *
+ * Note:
+ * isPositional assumes that the field is updatable. Call isUpdatable() above to
+ * verify.
+ */
+bool isPositional(const FieldRef& fieldRef, size_t* pos, size_t* count = NULL);
+
+} // namespace fieldchecker
+
+} // namespace mongo
diff --git a/src/mongo/db/ops/field_checker_test.cpp b/src/mongo/db/ops/field_checker_test.cpp
index 470197e5e39..5ee608688e9 100644
--- a/src/mongo/db/ops/field_checker_test.cpp
+++ b/src/mongo/db/ops/field_checker_test.cpp
@@ -35,72 +35,72 @@
namespace {
- using mongo::ErrorCodes;
- using mongo::FieldRef;
- using mongo::fieldchecker::isUpdatable;
- using mongo::fieldchecker::isPositional;
- using mongo::Status;
-
- TEST(IsUpdatable, Basics) {
- FieldRef fieldRef("x");
- ASSERT_OK(isUpdatable(fieldRef));
- }
-
- TEST(IsUpdatable, DottedFields) {
- FieldRef fieldRef("x.y.z");
- ASSERT_OK(isUpdatable(fieldRef));
- }
-
- TEST(IsUpdatable, EmptyFields) {
- FieldRef fieldRef("");
- ASSERT_NOT_OK(isUpdatable(fieldRef));
-
- FieldRef fieldRefDot(".");
- ASSERT_NOT_OK(isUpdatable(fieldRefDot));
-
- /* TODO: Re-enable after review
- FieldRef fieldRefDollar;
- fieldRefDollar.parse("$");
- ASSERT_NOT_OK(isUpdatable(fieldRefDollar));
+using mongo::ErrorCodes;
+using mongo::FieldRef;
+using mongo::fieldchecker::isUpdatable;
+using mongo::fieldchecker::isPositional;
+using mongo::Status;
+
+TEST(IsUpdatable, Basics) {
+ FieldRef fieldRef("x");
+ ASSERT_OK(isUpdatable(fieldRef));
+}
+
+TEST(IsUpdatable, DottedFields) {
+ FieldRef fieldRef("x.y.z");
+ ASSERT_OK(isUpdatable(fieldRef));
+}
+
+TEST(IsUpdatable, EmptyFields) {
+ FieldRef fieldRef("");
+ ASSERT_NOT_OK(isUpdatable(fieldRef));
+
+ FieldRef fieldRefDot(".");
+ ASSERT_NOT_OK(isUpdatable(fieldRefDot));
+
+ /* TODO: Re-enable after review
+ FieldRef fieldRefDollar;
+ fieldRefDollar.parse("$");
+ ASSERT_NOT_OK(isUpdatable(fieldRefDollar));
*/
- FieldRef fieldRefADot("a.");
- ASSERT_NOT_OK(isUpdatable(fieldRefADot));
-
- FieldRef fieldRefDotB(".b");
- ASSERT_NOT_OK(isUpdatable(fieldRefDotB));
-
- FieldRef fieldRefEmptyMiddle;
- fieldRefEmptyMiddle.parse("a..b");
- ASSERT_NOT_OK(isUpdatable(fieldRefEmptyMiddle));
- }
-
- // Positional checks
- TEST(isPositional, EntireArrayItem) {
- FieldRef fieldRefPositional("a.$");
- size_t pos;
- size_t count;
- ASSERT_TRUE(isPositional(fieldRefPositional, &pos, &count));
- ASSERT_EQUALS(pos, 1u);
- ASSERT_EQUALS(count, 1u);
- }
-
- TEST(isPositional, ArraySubObject) {
- FieldRef fieldRefPositional("a.$.b");
- size_t pos;
- size_t count;
- ASSERT_TRUE(isPositional(fieldRefPositional, &pos, &count));
- ASSERT_EQUALS(pos, 1u);
- ASSERT_EQUALS(count, 1u);
- }
-
- TEST(isPositional, MultiplePositional) {
- FieldRef fieldRefPositional("a.$.b.$.c");
- size_t pos;
- size_t count;
- ASSERT_TRUE(isPositional(fieldRefPositional, &pos, &count));
- ASSERT_EQUALS(pos, 1u);
- ASSERT_EQUALS(count, 2u);
- }
-} // unnamed namespace
+ FieldRef fieldRefADot("a.");
+ ASSERT_NOT_OK(isUpdatable(fieldRefADot));
+
+ FieldRef fieldRefDotB(".b");
+ ASSERT_NOT_OK(isUpdatable(fieldRefDotB));
+
+ FieldRef fieldRefEmptyMiddle;
+ fieldRefEmptyMiddle.parse("a..b");
+ ASSERT_NOT_OK(isUpdatable(fieldRefEmptyMiddle));
+}
+
+// Positional checks
+TEST(isPositional, EntireArrayItem) {
+ FieldRef fieldRefPositional("a.$");
+ size_t pos;
+ size_t count;
+ ASSERT_TRUE(isPositional(fieldRefPositional, &pos, &count));
+ ASSERT_EQUALS(pos, 1u);
+ ASSERT_EQUALS(count, 1u);
+}
+
+TEST(isPositional, ArraySubObject) {
+ FieldRef fieldRefPositional("a.$.b");
+ size_t pos;
+ size_t count;
+ ASSERT_TRUE(isPositional(fieldRefPositional, &pos, &count));
+ ASSERT_EQUALS(pos, 1u);
+ ASSERT_EQUALS(count, 1u);
+}
+
+TEST(isPositional, MultiplePositional) {
+ FieldRef fieldRefPositional("a.$.b.$.c");
+ size_t pos;
+ size_t count;
+ ASSERT_TRUE(isPositional(fieldRefPositional, &pos, &count));
+ ASSERT_EQUALS(pos, 1u);
+ ASSERT_EQUALS(count, 2u);
+}
+} // unnamed namespace
diff --git a/src/mongo/db/ops/insert.cpp b/src/mongo/db/ops/insert.cpp
index 38a2a478043..827bdc69c39 100644
--- a/src/mongo/db/ops/insert.cpp
+++ b/src/mongo/db/ops/insert.cpp
@@ -34,174 +34,174 @@
namespace mongo {
- using std::string;
-
- using namespace mongoutils;
-
- StatusWith<BSONObj> fixDocumentForInsert( const BSONObj& doc ) {
- if ( doc.objsize() > BSONObjMaxUserSize )
- return StatusWith<BSONObj>( ErrorCodes::BadValue,
- str::stream()
- << "object to insert too large"
- << ". size in bytes: " << doc.objsize()
- << ", max size: " << BSONObjMaxUserSize );
-
- bool firstElementIsId = doc.firstElement().fieldNameStringData() == "_id";
- bool hasTimestampToFix = false;
- {
- BSONObjIterator i( doc );
- while ( i.more() ) {
- BSONElement e = i.next();
-
- if ( e.type() == bsonTimestamp && e.timestampValue() == 0 ) {
- // we replace Timestamp(0,0) at the top level with a correct value
- // in the fast pass, we just mark that we want to swap
- hasTimestampToFix = true;
- }
-
- const char* fieldName = e.fieldName();
-
- if ( fieldName[0] == '$' ) {
- return StatusWith<BSONObj>( ErrorCodes::BadValue,
- str::stream()
- << "Document can't have $ prefixed field names: "
- << e.fieldName() );
- }
-
- // check no regexp for _id (SERVER-9502)
- // also, disallow undefined and arrays
- if ( str::equals( fieldName, "_id") ) {
- if ( e.type() == RegEx ) {
- return StatusWith<BSONObj>( ErrorCodes::BadValue,
- "can't use a regex for _id" );
- }
- if ( e.type() == Undefined ) {
- return StatusWith<BSONObj>( ErrorCodes::BadValue,
- "can't use a undefined for _id" );
- }
- if ( e.type() == Array ) {
- return StatusWith<BSONObj>( ErrorCodes::BadValue,
- "can't use an array for _id" );
- }
- if ( e.type() == Object ) {
- BSONObj o = e.Obj();
- Status s = o.storageValidEmbedded();
- if ( !s.isOK() )
- return StatusWith<BSONObj>( s );
- }
- }
+using std::string;
+
+using namespace mongoutils;
+
+StatusWith<BSONObj> fixDocumentForInsert(const BSONObj& doc) {
+ if (doc.objsize() > BSONObjMaxUserSize)
+ return StatusWith<BSONObj>(ErrorCodes::BadValue,
+ str::stream() << "object to insert too large"
+ << ". size in bytes: " << doc.objsize()
+ << ", max size: " << BSONObjMaxUserSize);
+
+ bool firstElementIsId = doc.firstElement().fieldNameStringData() == "_id";
+ bool hasTimestampToFix = false;
+ {
+ BSONObjIterator i(doc);
+ while (i.more()) {
+ BSONElement e = i.next();
+ if (e.type() == bsonTimestamp && e.timestampValue() == 0) {
+ // we replace Timestamp(0,0) at the top level with a correct value
+ // in the fast pass, we just mark that we want to swap
+ hasTimestampToFix = true;
}
- }
-
- if ( firstElementIsId && !hasTimestampToFix )
- return StatusWith<BSONObj>( BSONObj() );
- bool hadId = firstElementIsId;
+ const char* fieldName = e.fieldName();
- BSONObjIterator i( doc );
-
- BSONObjBuilder b( doc.objsize() + 16 );
- if ( firstElementIsId ) {
- b.append( doc.firstElement() );
- i.next();
- }
- else {
- BSONElement e = doc["_id"];
- if ( e.type() ) {
- b.append( e );
- hadId = true;
+ if (fieldName[0] == '$') {
+ return StatusWith<BSONObj>(ErrorCodes::BadValue,
+ str::stream()
+ << "Document can't have $ prefixed field names: "
+ << e.fieldName());
}
- else {
- b.appendOID( "_id", NULL, true );
- }
- }
- while ( i.more() ) {
- BSONElement e = i.next();
- if ( hadId && e.fieldNameStringData() == "_id" ) {
- // no-op
- }
- else if ( e.type() == bsonTimestamp && e.timestampValue() == 0 ) {
- b.append( e.fieldName(), getNextGlobalTimestamp() );
- }
- else {
- b.append( e );
+ // check no regexp for _id (SERVER-9502)
+ // also, disallow undefined and arrays
+ if (str::equals(fieldName, "_id")) {
+ if (e.type() == RegEx) {
+ return StatusWith<BSONObj>(ErrorCodes::BadValue, "can't use a regex for _id");
+ }
+ if (e.type() == Undefined) {
+ return StatusWith<BSONObj>(ErrorCodes::BadValue,
+ "can't use a undefined for _id");
+ }
+ if (e.type() == Array) {
+ return StatusWith<BSONObj>(ErrorCodes::BadValue, "can't use an array for _id");
+ }
+ if (e.type() == Object) {
+ BSONObj o = e.Obj();
+ Status s = o.storageValidEmbedded();
+ if (!s.isOK())
+ return StatusWith<BSONObj>(s);
+ }
}
}
- return StatusWith<BSONObj>( b.obj() );
}
- Status userAllowedWriteNS( StringData ns ) {
- return userAllowedWriteNS( nsToDatabaseSubstring( ns ), nsToCollectionSubstring( ns ) );
- }
+ if (firstElementIsId && !hasTimestampToFix)
+ return StatusWith<BSONObj>(BSONObj());
- Status userAllowedWriteNS( const NamespaceString& ns ) {
- return userAllowedWriteNS( ns.db(), ns.coll() );
- }
+ bool hadId = firstElementIsId;
+
+ BSONObjIterator i(doc);
- Status userAllowedWriteNS( StringData db, StringData coll ) {
- if ( coll == "system.profile" ) {
- return Status( ErrorCodes::BadValue,
- str::stream() << "cannot write to '" << db << ".system.profile'" );
+ BSONObjBuilder b(doc.objsize() + 16);
+ if (firstElementIsId) {
+ b.append(doc.firstElement());
+ i.next();
+ } else {
+ BSONElement e = doc["_id"];
+ if (e.type()) {
+ b.append(e);
+ hadId = true;
+ } else {
+ b.appendOID("_id", NULL, true);
}
- return userAllowedCreateNS( db, coll );
}
- Status userAllowedCreateNS( StringData db, StringData coll ) {
- // validity checking
-
- if ( db.size() == 0 )
- return Status( ErrorCodes::BadValue, "db cannot be blank" );
-
- if ( !NamespaceString::validDBName( db ) )
- return Status( ErrorCodes::BadValue, "invalid db name" );
-
- if ( coll.size() == 0 )
- return Status( ErrorCodes::BadValue, "collection cannot be blank" );
-
- if ( !NamespaceString::validCollectionName( coll ) )
- return Status( ErrorCodes::BadValue, "invalid collection name" );
-
- if ( db.size() + 1 /* dot */ + coll.size() > NamespaceString::MaxNsCollectionLen )
- return Status( ErrorCodes::BadValue,
- str::stream()
- << "fully qualified namespace " << db << '.' << coll << " is too long "
- << "(max is " << NamespaceString::MaxNsCollectionLen << " bytes)" );
+ while (i.more()) {
+ BSONElement e = i.next();
+ if (hadId && e.fieldNameStringData() == "_id") {
+ // no-op
+ } else if (e.type() == bsonTimestamp && e.timestampValue() == 0) {
+ b.append(e.fieldName(), getNextGlobalTimestamp());
+ } else {
+ b.append(e);
+ }
+ }
+ return StatusWith<BSONObj>(b.obj());
+}
- // check spceial areas
+Status userAllowedWriteNS(StringData ns) {
+ return userAllowedWriteNS(nsToDatabaseSubstring(ns), nsToCollectionSubstring(ns));
+}
- if ( db == "system" )
- return Status( ErrorCodes::BadValue, "cannot use 'system' database" );
+Status userAllowedWriteNS(const NamespaceString& ns) {
+ return userAllowedWriteNS(ns.db(), ns.coll());
+}
+Status userAllowedWriteNS(StringData db, StringData coll) {
+ if (coll == "system.profile") {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "cannot write to '" << db << ".system.profile'");
+ }
+ return userAllowedCreateNS(db, coll);
+}
- if ( coll.startsWith( "system." ) ) {
- if ( coll == "system.indexes" ) return Status::OK();
- if ( coll == "system.js" ) return Status::OK();
- if ( coll == "system.profile" ) return Status::OK();
- if ( coll == "system.users" ) return Status::OK();
- if ( db == "admin" ) {
- if ( coll == "system.version" ) return Status::OK();
- if ( coll == "system.roles" ) return Status::OK();
- if ( coll == "system.new_users" ) return Status::OK();
- if ( coll == "system.backup_users" ) return Status::OK();
- }
- if ( db == "local" ) {
- if ( coll == "system.replset" ) return Status::OK();
- }
- return Status( ErrorCodes::BadValue,
- str::stream() << "cannot write to '" << db << "." << coll << "'" );
+Status userAllowedCreateNS(StringData db, StringData coll) {
+ // validity checking
+
+ if (db.size() == 0)
+ return Status(ErrorCodes::BadValue, "db cannot be blank");
+
+ if (!NamespaceString::validDBName(db))
+ return Status(ErrorCodes::BadValue, "invalid db name");
+
+ if (coll.size() == 0)
+ return Status(ErrorCodes::BadValue, "collection cannot be blank");
+
+ if (!NamespaceString::validCollectionName(coll))
+ return Status(ErrorCodes::BadValue, "invalid collection name");
+
+ if (db.size() + 1 /* dot */ + coll.size() > NamespaceString::MaxNsCollectionLen)
+ return Status(ErrorCodes::BadValue,
+ str::stream()
+ << "fully qualified namespace " << db << '.' << coll << " is too long "
+ << "(max is " << NamespaceString::MaxNsCollectionLen << " bytes)");
+
+ // check spceial areas
+
+ if (db == "system")
+ return Status(ErrorCodes::BadValue, "cannot use 'system' database");
+
+
+ if (coll.startsWith("system.")) {
+ if (coll == "system.indexes")
+ return Status::OK();
+ if (coll == "system.js")
+ return Status::OK();
+ if (coll == "system.profile")
+ return Status::OK();
+ if (coll == "system.users")
+ return Status::OK();
+ if (db == "admin") {
+ if (coll == "system.version")
+ return Status::OK();
+ if (coll == "system.roles")
+ return Status::OK();
+ if (coll == "system.new_users")
+ return Status::OK();
+ if (coll == "system.backup_users")
+ return Status::OK();
}
-
- // some special rules
-
- if ( coll.find( ".system." ) != string::npos ) {
- // this matches old (2.4 and older) behavior, but I'm not sure its a good idea
- return Status( ErrorCodes::BadValue,
- str::stream() << "cannot write to '" << db << "." << coll << "'" );
+ if (db == "local") {
+ if (coll == "system.replset")
+ return Status::OK();
}
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "cannot write to '" << db << "." << coll << "'");
+ }
- return Status::OK();
+ // some special rules
+
+ if (coll.find(".system.") != string::npos) {
+ // this matches old (2.4 and older) behavior, but I'm not sure its a good idea
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "cannot write to '" << db << "." << coll << "'");
}
+ return Status::OK();
+}
}
diff --git a/src/mongo/db/ops/insert.h b/src/mongo/db/ops/insert.h
index f55f8e80ea6..e0204c290c7 100644
--- a/src/mongo/db/ops/insert.h
+++ b/src/mongo/db/ops/insert.h
@@ -33,25 +33,24 @@
namespace mongo {
- /**
- * if doc is ok, then return is BSONObj()
- * otherwise, BSONObj is what should be inserted instead
- */
- StatusWith<BSONObj> fixDocumentForInsert( const BSONObj& doc );
-
+/**
+ * if doc is ok, then return is BSONObj()
+ * otherwise, BSONObj is what should be inserted instead
+ */
+StatusWith<BSONObj> fixDocumentForInsert(const BSONObj& doc);
- /**
- * Returns Status::OK() if this namespace is valid for user write operations. If not, returns
- * an error Status.
- */
- Status userAllowedWriteNS( StringData db, StringData coll );
- Status userAllowedWriteNS( StringData ns );
- Status userAllowedWriteNS( const NamespaceString& ns );
- /**
- * Returns Status::OK() if the namespace described by (db, coll) is valid for user create
- * operations. If not, returns an error Status.
- */
- Status userAllowedCreateNS( StringData db, StringData coll );
+/**
+ * Returns Status::OK() if this namespace is valid for user write operations. If not, returns
+ * an error Status.
+ */
+Status userAllowedWriteNS(StringData db, StringData coll);
+Status userAllowedWriteNS(StringData ns);
+Status userAllowedWriteNS(const NamespaceString& ns);
+/**
+ * Returns Status::OK() if the namespace described by (db, coll) is valid for user create
+ * operations. If not, returns an error Status.
+ */
+Status userAllowedCreateNS(StringData db, StringData coll);
}
diff --git a/src/mongo/db/ops/log_builder.cpp b/src/mongo/db/ops/log_builder.cpp
index 437c9056e5e..21baffe246c 100644
--- a/src/mongo/db/ops/log_builder.cpp
+++ b/src/mongo/db/ops/log_builder.cpp
@@ -31,143 +31,128 @@
namespace mongo {
- using mutablebson::Document;
- using mutablebson::Element;
- namespace str = mongoutils::str;
-
- namespace {
- const char kSet[] = "$set";
- const char kUnset[] = "$unset";
- } // namespace
-
- inline Status LogBuilder::addToSection(Element newElt,
- Element* section,
- const char* sectionName) {
-
- // If we don't already have this section, try to create it now.
- if (!section->ok()) {
-
- // If we already have object replacement data, we can't also have section entries.
- if (hasObjectReplacement())
- return Status(
- ErrorCodes::IllegalOperation,
- "LogBuilder: Invalid attempt to add a $set/$unset entry"
- "to a log with an existing object replacement");
-
- Document& doc = _logRoot.getDocument();
-
- // We should not already have an element with the section name under the root.
- dassert(_logRoot[sectionName] == doc.end());
-
- // Construct a new object element to represent this section in the log.
- const Element newElement = doc.makeElementObject(sectionName);
- if (!newElement.ok())
- return Status(ErrorCodes::InternalError,
- "LogBuilder: failed to construct Object Element for $set/$unset");
-
- // Enqueue the new section under the root, and record it as our out parameter.
- Status result = _logRoot.pushBack(newElement);
- if (!result.isOK())
- return result;
- *section = newElement;
-
- // Invalidate attempts to add an object replacement, now that we have a named
- // section under the root.
- _objectReplacementAccumulator = doc.end();
- }
-
- // Whatever transpired, we should now have an ok accumulator for the section, and not
- // have a replacement accumulator.
- dassert(section->ok());
- dassert(!_objectReplacementAccumulator.ok());
-
- // Enqueue the provided element to the section and propagate the result.
- return section->pushBack(newElt);
- }
-
- Status LogBuilder::addToSets(Element elt) {
- return addToSection(elt, &_setAccumulator, kSet);
- }
+using mutablebson::Document;
+using mutablebson::Element;
+namespace str = mongoutils::str;
+
+namespace {
+const char kSet[] = "$set";
+const char kUnset[] = "$unset";
+} // namespace
+
+inline Status LogBuilder::addToSection(Element newElt, Element* section, const char* sectionName) {
+ // If we don't already have this section, try to create it now.
+ if (!section->ok()) {
+ // If we already have object replacement data, we can't also have section entries.
+ if (hasObjectReplacement())
+ return Status(ErrorCodes::IllegalOperation,
+ "LogBuilder: Invalid attempt to add a $set/$unset entry"
+ "to a log with an existing object replacement");
- Status LogBuilder::addToSetsWithNewFieldName(StringData name,
- const mutablebson::Element val) {
- mutablebson::Element elemToSet =
- _logRoot.getDocument().makeElementWithNewFieldName(name, val);
- if (!elemToSet.ok())
- return Status(ErrorCodes::InternalError,
- str::stream() << "Could not create new '"
- << name << "' element from existing element '"
- << val.getFieldName() << "' of type "
- << typeName(val.getType()));
+ Document& doc = _logRoot.getDocument();
- return addToSets(elemToSet);
- }
+ // We should not already have an element with the section name under the root.
+ dassert(_logRoot[sectionName] == doc.end());
- Status LogBuilder::addToSetsWithNewFieldName(StringData name,
- const BSONElement& val){
- mutablebson::Element elemToSet =
- _logRoot.getDocument().makeElementWithNewFieldName(name, val);
- if (!elemToSet.ok())
+ // Construct a new object element to represent this section in the log.
+ const Element newElement = doc.makeElementObject(sectionName);
+ if (!newElement.ok())
return Status(ErrorCodes::InternalError,
- str::stream() << "Could not create new '"
- << name << "' element from existing element '"
- << val.fieldName() << "' of type "
- << typeName(val.type()));
+ "LogBuilder: failed to construct Object Element for $set/$unset");
- return addToSets(elemToSet);
- }
+ // Enqueue the new section under the root, and record it as our out parameter.
+ Status result = _logRoot.pushBack(newElement);
+ if (!result.isOK())
+ return result;
+ *section = newElement;
- Status LogBuilder::addToSets(StringData name, const SafeNum& val){
- mutablebson::Element elemToSet = _logRoot.getDocument().makeElementSafeNum(name, val);
- if (!elemToSet.ok())
- return Status(ErrorCodes::InternalError,
- str::stream() << "Could not create new '"
- << name << "' SafeNum from "
- << val.debugString());
-
- return addToSets(elemToSet);
+ // Invalidate attempts to add an object replacement, now that we have a named
+ // section under the root.
+ _objectReplacementAccumulator = doc.end();
}
- Status LogBuilder::addToUnsets(StringData path) {
- mutablebson::Element logElement = _logRoot.getDocument().makeElementBool(path, true);
- if (!logElement.ok())
- return Status(ErrorCodes::InternalError,
- str::stream() << "Cannot create $unset oplog entry for path" << path);
-
- return addToSection(logElement, &_unsetAccumulator, kUnset);
+ // Whatever transpired, we should now have an ok accumulator for the section, and not
+ // have a replacement accumulator.
+ dassert(section->ok());
+ dassert(!_objectReplacementAccumulator.ok());
+
+ // Enqueue the provided element to the section and propagate the result.
+ return section->pushBack(newElt);
+}
+
+Status LogBuilder::addToSets(Element elt) {
+ return addToSection(elt, &_setAccumulator, kSet);
+}
+
+Status LogBuilder::addToSetsWithNewFieldName(StringData name, const mutablebson::Element val) {
+ mutablebson::Element elemToSet = _logRoot.getDocument().makeElementWithNewFieldName(name, val);
+ if (!elemToSet.ok())
+ return Status(ErrorCodes::InternalError,
+ str::stream() << "Could not create new '" << name
+ << "' element from existing element '" << val.getFieldName()
+ << "' of type " << typeName(val.getType()));
+
+ return addToSets(elemToSet);
+}
+
+Status LogBuilder::addToSetsWithNewFieldName(StringData name, const BSONElement& val) {
+ mutablebson::Element elemToSet = _logRoot.getDocument().makeElementWithNewFieldName(name, val);
+ if (!elemToSet.ok())
+ return Status(ErrorCodes::InternalError,
+ str::stream() << "Could not create new '" << name
+ << "' element from existing element '" << val.fieldName()
+ << "' of type " << typeName(val.type()));
+
+ return addToSets(elemToSet);
+}
+
+Status LogBuilder::addToSets(StringData name, const SafeNum& val) {
+ mutablebson::Element elemToSet = _logRoot.getDocument().makeElementSafeNum(name, val);
+ if (!elemToSet.ok())
+ return Status(ErrorCodes::InternalError,
+ str::stream() << "Could not create new '" << name << "' SafeNum from "
+ << val.debugString());
+
+ return addToSets(elemToSet);
+}
+
+Status LogBuilder::addToUnsets(StringData path) {
+ mutablebson::Element logElement = _logRoot.getDocument().makeElementBool(path, true);
+ if (!logElement.ok())
+ return Status(ErrorCodes::InternalError,
+ str::stream() << "Cannot create $unset oplog entry for path" << path);
+
+ return addToSection(logElement, &_unsetAccumulator, kUnset);
+}
+
+Status LogBuilder::getReplacementObject(Element* outElt) {
+ // If the replacement accumulator is not ok, we must have started a $set or $unset
+ // already, so an object replacement is not permitted.
+ if (!_objectReplacementAccumulator.ok()) {
+ dassert(_setAccumulator.ok() || _unsetAccumulator.ok());
+ return Status(ErrorCodes::IllegalOperation,
+ "LogBuilder: Invalid attempt to obtain the object replacement slot "
+ "for a log containing $set or $unset entries");
}
- Status LogBuilder::getReplacementObject(Element* outElt) {
+ if (hasObjectReplacement())
+ return Status(ErrorCodes::IllegalOperation,
+ "LogBuilder: Invalid attempt to acquire the replacement object "
+ "in a log with existing object replacement data");
- // If the replacement accumulator is not ok, we must have started a $set or $unset
- // already, so an object replacement is not permitted.
- if (!_objectReplacementAccumulator.ok()) {
- dassert(_setAccumulator.ok() || _unsetAccumulator.ok());
- return Status(
- ErrorCodes::IllegalOperation,
- "LogBuilder: Invalid attempt to obtain the object replacement slot "
- "for a log containing $set or $unset entries");
- }
+ // OK to enqueue object replacement items.
+ *outElt = _objectReplacementAccumulator;
+ return Status::OK();
+}
- if (hasObjectReplacement())
- return Status(
- ErrorCodes::IllegalOperation,
- "LogBuilder: Invalid attempt to acquire the replacement object "
- "in a log with existing object replacement data");
-
- // OK to enqueue object replacement items.
- *outElt = _objectReplacementAccumulator;
- return Status::OK();
- }
-
- inline bool LogBuilder::hasObjectReplacement() const {
- if (!_objectReplacementAccumulator.ok())
- return false;
+inline bool LogBuilder::hasObjectReplacement() const {
+ if (!_objectReplacementAccumulator.ok())
+ return false;
- dassert(!_setAccumulator.ok());
- dassert(!_unsetAccumulator.ok());
+ dassert(!_setAccumulator.ok());
+ dassert(!_unsetAccumulator.ok());
- return _objectReplacementAccumulator.hasChildren();
- }
+ return _objectReplacementAccumulator.hasChildren();
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/db/ops/log_builder.h b/src/mongo/db/ops/log_builder.h
index d35f48846f4..945ba18cf8f 100644
--- a/src/mongo/db/ops/log_builder.h
+++ b/src/mongo/db/ops/log_builder.h
@@ -33,90 +33,89 @@
namespace mongo {
- /** LogBuilder abstracts away some of the details of producing a properly constructed oplog
- * update entry. It manages separate regions into which it accumulates $set and $unset
- * operations, and distinguishes object replacement style oplog generation from
- * $set/$unset style generation and prevents admixture.
+/** LogBuilder abstracts away some of the details of producing a properly constructed oplog
+ * update entry. It manages separate regions into which it accumulates $set and $unset
+ * operations, and distinguishes object replacement style oplog generation from
+ * $set/$unset style generation and prevents admixture.
+ */
+class LogBuilder {
+public:
+ /** Construct a new LogBuilder. Log entries will be recorded as new children under the
+ * 'logRoot' Element, which must be of type mongo::Object and have no children.
*/
- class LogBuilder {
- public:
- /** Construct a new LogBuilder. Log entries will be recorded as new children under the
- * 'logRoot' Element, which must be of type mongo::Object and have no children.
- */
- inline LogBuilder(mutablebson::Element logRoot)
- : _logRoot(logRoot)
- , _objectReplacementAccumulator(_logRoot)
- , _setAccumulator(_logRoot.getDocument().end())
- , _unsetAccumulator(_setAccumulator) {
- dassert(logRoot.isType(mongo::Object));
- dassert(!logRoot.hasChildren());
- }
+ inline LogBuilder(mutablebson::Element logRoot)
+ : _logRoot(logRoot),
+ _objectReplacementAccumulator(_logRoot),
+ _setAccumulator(_logRoot.getDocument().end()),
+ _unsetAccumulator(_setAccumulator) {
+ dassert(logRoot.isType(mongo::Object));
+ dassert(!logRoot.hasChildren());
+ }
- /** Return the Document to which the logging root belongs. */
- inline mutablebson::Document& getDocument() {
- return _logRoot.getDocument();
- }
+ /** Return the Document to which the logging root belongs. */
+ inline mutablebson::Document& getDocument() {
+ return _logRoot.getDocument();
+ }
- /** Add the given Element as a new entry in the '$set' section of the log. If a $set
- * section does not yet exist, it will be created. If this LogBuilder is currently
- * configured to contain an object replacement, the request to add to the $set section
- * will return an Error.
- */
- Status addToSets(mutablebson::Element elt);
+ /** Add the given Element as a new entry in the '$set' section of the log. If a $set
+ * section does not yet exist, it will be created. If this LogBuilder is currently
+ * configured to contain an object replacement, the request to add to the $set section
+ * will return an Error.
+ */
+ Status addToSets(mutablebson::Element elt);
- /**
- * Convenience method which calls addToSets after
- * creating a new Element to wrap the SafeNum value.
- *
- * If any problem occurs then the operation will stop and return that error Status.
- */
- Status addToSets(StringData name, const SafeNum& val);
+ /**
+ * Convenience method which calls addToSets after
+ * creating a new Element to wrap the SafeNum value.
+ *
+ * If any problem occurs then the operation will stop and return that error Status.
+ */
+ Status addToSets(StringData name, const SafeNum& val);
- /**
- * Convenience method which calls addToSets after
- * creating a new Element to wrap the old one.
- *
- * If any problem occurs then the operation will stop and return that error Status.
- */
- Status addToSetsWithNewFieldName(StringData name, const mutablebson::Element val);
+ /**
+ * Convenience method which calls addToSets after
+ * creating a new Element to wrap the old one.
+ *
+ * If any problem occurs then the operation will stop and return that error Status.
+ */
+ Status addToSetsWithNewFieldName(StringData name, const mutablebson::Element val);
- /**
- * Convenience method which calls addToSets after
- * creating a new Element to wrap the old one.
- *
- * If any problem occurs then the operation will stop and return that error Status.
- */
- Status addToSetsWithNewFieldName(StringData name, const BSONElement& val);
+ /**
+ * Convenience method which calls addToSets after
+ * creating a new Element to wrap the old one.
+ *
+ * If any problem occurs then the operation will stop and return that error Status.
+ */
+ Status addToSetsWithNewFieldName(StringData name, const BSONElement& val);
- /** Add the given path as a new entry in the '$unset' section of the log. If an
- * '$unset' section does not yet exist, it will be created. If this LogBuilder is
- * currently configured to contain an object replacement, the request to add to the
- * $unset section will return an Error.
- */
- Status addToUnsets(StringData path);
+ /** Add the given path as a new entry in the '$unset' section of the log. If an
+ * '$unset' section does not yet exist, it will be created. If this LogBuilder is
+ * currently configured to contain an object replacement, the request to add to the
+ * $unset section will return an Error.
+ */
+ Status addToUnsets(StringData path);
- /** Obtain, via the out parameter 'outElt', a pointer to the mongo::Object type Element
- * to which the components of an object replacement should be recorded. It is an error
- * to call this if any Elements have been added by calling either addToSets or
- * addToUnsets, and attempts to do so will return a non-OK Status. Similarly, if there
- * is already object replacement data recorded for this log, the call will fail.
- */
- Status getReplacementObject(mutablebson::Element* outElt);
+ /** Obtain, via the out parameter 'outElt', a pointer to the mongo::Object type Element
+ * to which the components of an object replacement should be recorded. It is an error
+ * to call this if any Elements have been added by calling either addToSets or
+ * addToUnsets, and attempts to do so will return a non-OK Status. Similarly, if there
+ * is already object replacement data recorded for this log, the call will fail.
+ */
+ Status getReplacementObject(mutablebson::Element* outElt);
- private:
- // Returns true if the object replacement accumulator is valid and has children, false
- // otherwise.
- inline bool hasObjectReplacement() const;
+private:
+ // Returns true if the object replacement accumulator is valid and has children, false
+ // otherwise.
+ inline bool hasObjectReplacement() const;
- inline Status addToSection(
- mutablebson::Element newElt,
- mutablebson::Element* section,
- const char* sectionName);
+ inline Status addToSection(mutablebson::Element newElt,
+ mutablebson::Element* section,
+ const char* sectionName);
- mutablebson::Element _logRoot;
- mutablebson::Element _objectReplacementAccumulator;
- mutablebson::Element _setAccumulator;
- mutablebson::Element _unsetAccumulator;
- };
+ mutablebson::Element _logRoot;
+ mutablebson::Element _objectReplacementAccumulator;
+ mutablebson::Element _setAccumulator;
+ mutablebson::Element _unsetAccumulator;
+};
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/db/ops/log_builder_test.cpp b/src/mongo/db/ops/log_builder_test.cpp
index 253dd70e5a1..f2a3d20aa78 100644
--- a/src/mongo/db/ops/log_builder_test.cpp
+++ b/src/mongo/db/ops/log_builder_test.cpp
@@ -36,243 +36,240 @@
namespace {
- namespace mmb = mongo::mutablebson;
- using mongo::LogBuilder;
+namespace mmb = mongo::mutablebson;
+using mongo::LogBuilder;
- TEST(LogBuilder, Initialization) {
- mmb::Document doc;
- LogBuilder lb(doc.root());
- ASSERT_EQUALS(&doc, &lb.getDocument());
- }
+TEST(LogBuilder, Initialization) {
+ mmb::Document doc;
+ LogBuilder lb(doc.root());
+ ASSERT_EQUALS(&doc, &lb.getDocument());
+}
- TEST(LogBuilder, AddOneToSet) {
- mmb::Document doc;
- LogBuilder lb(doc.root());
+TEST(LogBuilder, AddOneToSet) {
+ mmb::Document doc;
+ LogBuilder lb(doc.root());
- const mmb::Element elt_ab = doc.makeElementInt("a.b", 1);
- ASSERT_TRUE(elt_ab.ok());
- ASSERT_OK(lb.addToSets(elt_ab));
+ const mmb::Element elt_ab = doc.makeElementInt("a.b", 1);
+ ASSERT_TRUE(elt_ab.ok());
+ ASSERT_OK(lb.addToSets(elt_ab));
- ASSERT_EQUALS(mongo::fromjson("{ $set : { 'a.b' : 1 } }"), doc);
- }
+ ASSERT_EQUALS(mongo::fromjson("{ $set : { 'a.b' : 1 } }"), doc);
+}
- TEST(LogBuilder, AddElementToSet) {
- mmb::Document doc;
- LogBuilder lb(doc.root());
+TEST(LogBuilder, AddElementToSet) {
+ mmb::Document doc;
+ LogBuilder lb(doc.root());
- const mmb::Element elt_ab = doc.makeElementInt("", 1);
- ASSERT_TRUE(elt_ab.ok());
- ASSERT_OK(lb.addToSetsWithNewFieldName("a.b", elt_ab));
+ const mmb::Element elt_ab = doc.makeElementInt("", 1);
+ ASSERT_TRUE(elt_ab.ok());
+ ASSERT_OK(lb.addToSetsWithNewFieldName("a.b", elt_ab));
- ASSERT_EQUALS(mongo::fromjson("{ $set : { 'a.b' : 1 } }"), doc);
- }
+ ASSERT_EQUALS(mongo::fromjson("{ $set : { 'a.b' : 1 } }"), doc);
+}
- TEST(LogBuilder, AddBSONElementToSet) {
- mmb::Document doc;
- LogBuilder lb(doc.root());
+TEST(LogBuilder, AddBSONElementToSet) {
+ mmb::Document doc;
+ LogBuilder lb(doc.root());
- mongo::BSONObj obj = mongo::fromjson("{'':1}");
-
- ASSERT_OK(lb.addToSetsWithNewFieldName("a.b", obj.firstElement()));
+ mongo::BSONObj obj = mongo::fromjson("{'':1}");
- ASSERT_EQUALS(mongo::fromjson("{ $set : { 'a.b' : 1 } }"), doc);
- }
+ ASSERT_OK(lb.addToSetsWithNewFieldName("a.b", obj.firstElement()));
- TEST(LogBuilder, AddSafeNumToSet) {
- mmb::Document doc;
- LogBuilder lb(doc.root());
+ ASSERT_EQUALS(mongo::fromjson("{ $set : { 'a.b' : 1 } }"), doc);
+}
- mongo::BSONObj obj = mongo::fromjson("{'':1}");
-
- ASSERT_OK(lb.addToSets("a.b", mongo::SafeNum(1)));
-
- ASSERT_EQUALS(mongo::fromjson("{ $set : { 'a.b' : 1 } }"), doc);
- }
-
- TEST(LogBuilder, AddOneToUnset) {
- mmb::Document doc;
- LogBuilder lb(doc.root());
- ASSERT_OK(lb.addToUnsets("x.y"));
- ASSERT_EQUALS(mongo::fromjson("{ $unset : { 'x.y' : true } }"), doc);
- }
-
- TEST(LogBuilder, AddOneToEach) {
- mmb::Document doc;
- LogBuilder lb(doc.root());
-
- const mmb::Element elt_ab = doc.makeElementInt("a.b", 1);
- ASSERT_TRUE(elt_ab.ok());
- ASSERT_OK(lb.addToSets(elt_ab));
-
- ASSERT_OK(lb.addToUnsets("x.y"));
-
- ASSERT_EQUALS(
- mongo::fromjson(
- "{ "
- " $set : { 'a.b' : 1 }, "
- " $unset : { 'x.y' : true } "
- "}"
- ), doc);
- }
-
- TEST(LogBuilder, AddOneObjectReplacementEntry) {
- mmb::Document doc;
- LogBuilder lb(doc.root());
-
- mmb::Element replacement = doc.end();
- ASSERT_FALSE(replacement.ok());
- ASSERT_OK(lb.getReplacementObject(&replacement));
- ASSERT_TRUE(replacement.ok());
- ASSERT_TRUE(replacement.isType(mongo::Object));
-
- const mmb::Element elt_a = doc.makeElementInt("a", 1);
- ASSERT_TRUE(elt_a.ok());
- ASSERT_OK(replacement.pushBack(elt_a));
-
- ASSERT_EQUALS(mongo::fromjson("{ a : 1 }"), doc);
- }
-
- TEST(LogBuilder, AddTwoObjectReplacementEntry) {
- mmb::Document doc;
- LogBuilder lb(doc.root());
-
- mmb::Element replacement = doc.end();
- ASSERT_FALSE(replacement.ok());
- ASSERT_OK(lb.getReplacementObject(&replacement));
- ASSERT_TRUE(replacement.ok());
- ASSERT_TRUE(replacement.isType(mongo::Object));
-
- const mmb::Element elt_a = doc.makeElementInt("a", 1);
- ASSERT_TRUE(elt_a.ok());
- ASSERT_OK(replacement.pushBack(elt_a));
-
- const mmb::Element elt_b = doc.makeElementInt("b", 2);
- ASSERT_TRUE(elt_b.ok());
- ASSERT_OK(replacement.pushBack(elt_b));
-
- ASSERT_EQUALS(mongo::fromjson("{ a : 1, b: 2 }"), doc);
- }
-
- TEST(LogBuilder, VerifySetsAreGrouped) {
- mmb::Document doc;
- LogBuilder lb(doc.root());
-
- const mmb::Element elt_ab = doc.makeElementInt("a.b", 1);
- ASSERT_TRUE(elt_ab.ok());
- ASSERT_OK(lb.addToSets(elt_ab));
-
- const mmb::Element elt_xy = doc.makeElementInt("x.y", 1);
- ASSERT_TRUE(elt_xy.ok());
- ASSERT_OK(lb.addToSets(elt_xy));
-
- ASSERT_EQUALS(
- mongo::fromjson(
- "{ $set : {"
- " 'a.b' : 1, "
- " 'x.y' : 1 "
- "} }"
- ), doc);
- }
-
- TEST(LogBuilder, VerifyUnsetsAreGrouped) {
- mmb::Document doc;
- LogBuilder lb(doc.root());
-
- ASSERT_OK(lb.addToUnsets("a.b"));
- ASSERT_OK(lb.addToUnsets("x.y"));
-
- ASSERT_EQUALS(
- mongo::fromjson(
- "{ $unset : {"
- " 'a.b' : true, "
- " 'x.y' : true "
- "} }"
- ), doc);
- }
-
- TEST(LogBuilder, PresenceOfSetPreventsObjectReplacement) {
- mmb::Document doc;
- LogBuilder lb(doc.root());
-
- mmb::Element replacement = doc.end();
- ASSERT_FALSE(replacement.ok());
- ASSERT_OK(lb.getReplacementObject(&replacement));
- ASSERT_TRUE(replacement.ok());
-
- const mmb::Element elt_ab = doc.makeElementInt("a.b", 1);
- ASSERT_TRUE(elt_ab.ok());
- ASSERT_OK(lb.addToSets(elt_ab));
-
- replacement = doc.end();
- ASSERT_FALSE(replacement.ok());
- ASSERT_NOT_OK(lb.getReplacementObject(&replacement));
- ASSERT_FALSE(replacement.ok());
- }
-
- TEST(LogBuilder, PresenceOfUnsetPreventsObjectReplacement) {
- mmb::Document doc;
- LogBuilder lb(doc.root());
-
- mmb::Element replacement = doc.end();
- ASSERT_FALSE(replacement.ok());
- ASSERT_OK(lb.getReplacementObject(&replacement));
- ASSERT_TRUE(replacement.ok());
-
- const mmb::Element elt_ab = doc.makeElementInt("a.b", 1);
- ASSERT_TRUE(elt_ab.ok());
- ASSERT_OK(lb.addToSets(elt_ab));
-
- replacement = doc.end();
- ASSERT_FALSE(replacement.ok());
- ASSERT_NOT_OK(lb.getReplacementObject(&replacement));
- ASSERT_FALSE(replacement.ok());
- }
-
- TEST(LogBuilder, CantAddSetWithObjectReplacementDataPresent) {
- mmb::Document doc;
- LogBuilder lb(doc.root());
-
- mmb::Element replacement = doc.end();
- ASSERT_FALSE(replacement.ok());
- ASSERT_OK(lb.getReplacementObject(&replacement));
- ASSERT_TRUE(replacement.ok());
- ASSERT_OK(replacement.appendInt("a", 1));
-
- mmb::Element setCandidate = doc.makeElementInt("x", 0);
- ASSERT_NOT_OK(lb.addToSets(setCandidate));
- }
-
- TEST(LogBuilder, CantAddUnsetWithObjectReplacementDataPresent) {
- mmb::Document doc;
- LogBuilder lb(doc.root());
-
- mmb::Element replacement = doc.end();
- ASSERT_FALSE(replacement.ok());
- ASSERT_OK(lb.getReplacementObject(&replacement));
- ASSERT_TRUE(replacement.ok());
- ASSERT_OK(replacement.appendInt("a", 1));
-
- ASSERT_NOT_OK(lb.addToUnsets("x"));
- }
-
- // Ensure that once you have obtained the object replacement slot and mutated it, that the
- // object replacement slot becomes in accessible. This is a bit paranoid, since in practice
- // the modifier conflict detection logic should prevent that outcome at a higher level, but
- // preventing it here costs us nothing and add an extra safety check.
- TEST(LogBuilder, CantReacquireObjectReplacementData) {
- mmb::Document doc;
- LogBuilder lb(doc.root());
-
- mmb::Element replacement = doc.end();
- ASSERT_FALSE(replacement.ok());
- ASSERT_OK(lb.getReplacementObject(&replacement));
- ASSERT_TRUE(replacement.ok());
- ASSERT_OK(replacement.appendInt("a", 1));
-
- mmb::Element again = doc.end();
- ASSERT_FALSE(again.ok());
- ASSERT_NOT_OK(lb.getReplacementObject(&again));
- ASSERT_FALSE(again.ok());
- }
-
-} // namespace
+TEST(LogBuilder, AddSafeNumToSet) {
+ mmb::Document doc;
+ LogBuilder lb(doc.root());
+
+ mongo::BSONObj obj = mongo::fromjson("{'':1}");
+
+ ASSERT_OK(lb.addToSets("a.b", mongo::SafeNum(1)));
+
+ ASSERT_EQUALS(mongo::fromjson("{ $set : { 'a.b' : 1 } }"), doc);
+}
+
+TEST(LogBuilder, AddOneToUnset) {
+ mmb::Document doc;
+ LogBuilder lb(doc.root());
+ ASSERT_OK(lb.addToUnsets("x.y"));
+ ASSERT_EQUALS(mongo::fromjson("{ $unset : { 'x.y' : true } }"), doc);
+}
+
+TEST(LogBuilder, AddOneToEach) {
+ mmb::Document doc;
+ LogBuilder lb(doc.root());
+
+ const mmb::Element elt_ab = doc.makeElementInt("a.b", 1);
+ ASSERT_TRUE(elt_ab.ok());
+ ASSERT_OK(lb.addToSets(elt_ab));
+
+ ASSERT_OK(lb.addToUnsets("x.y"));
+
+ ASSERT_EQUALS(mongo::fromjson(
+ "{ "
+ " $set : { 'a.b' : 1 }, "
+ " $unset : { 'x.y' : true } "
+ "}"),
+ doc);
+}
+
+TEST(LogBuilder, AddOneObjectReplacementEntry) {
+ mmb::Document doc;
+ LogBuilder lb(doc.root());
+
+ mmb::Element replacement = doc.end();
+ ASSERT_FALSE(replacement.ok());
+ ASSERT_OK(lb.getReplacementObject(&replacement));
+ ASSERT_TRUE(replacement.ok());
+ ASSERT_TRUE(replacement.isType(mongo::Object));
+
+ const mmb::Element elt_a = doc.makeElementInt("a", 1);
+ ASSERT_TRUE(elt_a.ok());
+ ASSERT_OK(replacement.pushBack(elt_a));
+
+ ASSERT_EQUALS(mongo::fromjson("{ a : 1 }"), doc);
+}
+
+TEST(LogBuilder, AddTwoObjectReplacementEntry) {
+ mmb::Document doc;
+ LogBuilder lb(doc.root());
+
+ mmb::Element replacement = doc.end();
+ ASSERT_FALSE(replacement.ok());
+ ASSERT_OK(lb.getReplacementObject(&replacement));
+ ASSERT_TRUE(replacement.ok());
+ ASSERT_TRUE(replacement.isType(mongo::Object));
+
+ const mmb::Element elt_a = doc.makeElementInt("a", 1);
+ ASSERT_TRUE(elt_a.ok());
+ ASSERT_OK(replacement.pushBack(elt_a));
+
+ const mmb::Element elt_b = doc.makeElementInt("b", 2);
+ ASSERT_TRUE(elt_b.ok());
+ ASSERT_OK(replacement.pushBack(elt_b));
+
+ ASSERT_EQUALS(mongo::fromjson("{ a : 1, b: 2 }"), doc);
+}
+
+TEST(LogBuilder, VerifySetsAreGrouped) {
+ mmb::Document doc;
+ LogBuilder lb(doc.root());
+
+ const mmb::Element elt_ab = doc.makeElementInt("a.b", 1);
+ ASSERT_TRUE(elt_ab.ok());
+ ASSERT_OK(lb.addToSets(elt_ab));
+
+ const mmb::Element elt_xy = doc.makeElementInt("x.y", 1);
+ ASSERT_TRUE(elt_xy.ok());
+ ASSERT_OK(lb.addToSets(elt_xy));
+
+ ASSERT_EQUALS(mongo::fromjson(
+ "{ $set : {"
+ " 'a.b' : 1, "
+ " 'x.y' : 1 "
+ "} }"),
+ doc);
+}
+
+TEST(LogBuilder, VerifyUnsetsAreGrouped) {
+ mmb::Document doc;
+ LogBuilder lb(doc.root());
+
+ ASSERT_OK(lb.addToUnsets("a.b"));
+ ASSERT_OK(lb.addToUnsets("x.y"));
+
+ ASSERT_EQUALS(mongo::fromjson(
+ "{ $unset : {"
+ " 'a.b' : true, "
+ " 'x.y' : true "
+ "} }"),
+ doc);
+}
+
+TEST(LogBuilder, PresenceOfSetPreventsObjectReplacement) {
+ mmb::Document doc;
+ LogBuilder lb(doc.root());
+
+ mmb::Element replacement = doc.end();
+ ASSERT_FALSE(replacement.ok());
+ ASSERT_OK(lb.getReplacementObject(&replacement));
+ ASSERT_TRUE(replacement.ok());
+
+ const mmb::Element elt_ab = doc.makeElementInt("a.b", 1);
+ ASSERT_TRUE(elt_ab.ok());
+ ASSERT_OK(lb.addToSets(elt_ab));
+
+ replacement = doc.end();
+ ASSERT_FALSE(replacement.ok());
+ ASSERT_NOT_OK(lb.getReplacementObject(&replacement));
+ ASSERT_FALSE(replacement.ok());
+}
+
+TEST(LogBuilder, PresenceOfUnsetPreventsObjectReplacement) {
+ mmb::Document doc;
+ LogBuilder lb(doc.root());
+
+ mmb::Element replacement = doc.end();
+ ASSERT_FALSE(replacement.ok());
+ ASSERT_OK(lb.getReplacementObject(&replacement));
+ ASSERT_TRUE(replacement.ok());
+
+ const mmb::Element elt_ab = doc.makeElementInt("a.b", 1);
+ ASSERT_TRUE(elt_ab.ok());
+ ASSERT_OK(lb.addToSets(elt_ab));
+
+ replacement = doc.end();
+ ASSERT_FALSE(replacement.ok());
+ ASSERT_NOT_OK(lb.getReplacementObject(&replacement));
+ ASSERT_FALSE(replacement.ok());
+}
+
+TEST(LogBuilder, CantAddSetWithObjectReplacementDataPresent) {
+ mmb::Document doc;
+ LogBuilder lb(doc.root());
+
+ mmb::Element replacement = doc.end();
+ ASSERT_FALSE(replacement.ok());
+ ASSERT_OK(lb.getReplacementObject(&replacement));
+ ASSERT_TRUE(replacement.ok());
+ ASSERT_OK(replacement.appendInt("a", 1));
+
+ mmb::Element setCandidate = doc.makeElementInt("x", 0);
+ ASSERT_NOT_OK(lb.addToSets(setCandidate));
+}
+
+TEST(LogBuilder, CantAddUnsetWithObjectReplacementDataPresent) {
+ mmb::Document doc;
+ LogBuilder lb(doc.root());
+
+ mmb::Element replacement = doc.end();
+ ASSERT_FALSE(replacement.ok());
+ ASSERT_OK(lb.getReplacementObject(&replacement));
+ ASSERT_TRUE(replacement.ok());
+ ASSERT_OK(replacement.appendInt("a", 1));
+
+ ASSERT_NOT_OK(lb.addToUnsets("x"));
+}
+
+// Ensure that once you have obtained the object replacement slot and mutated it, that the
+// object replacement slot becomes in accessible. This is a bit paranoid, since in practice
+// the modifier conflict detection logic should prevent that outcome at a higher level, but
+// preventing it here costs us nothing and add an extra safety check.
+TEST(LogBuilder, CantReacquireObjectReplacementData) {
+ mmb::Document doc;
+ LogBuilder lb(doc.root());
+
+ mmb::Element replacement = doc.end();
+ ASSERT_FALSE(replacement.ok());
+ ASSERT_OK(lb.getReplacementObject(&replacement));
+ ASSERT_TRUE(replacement.ok());
+ ASSERT_OK(replacement.appendInt("a", 1));
+
+ mmb::Element again = doc.end();
+ ASSERT_FALSE(again.ok());
+ ASSERT_NOT_OK(lb.getReplacementObject(&again));
+ ASSERT_FALSE(again.ok());
+}
+
+} // namespace
diff --git a/src/mongo/db/ops/modifier_add_to_set.cpp b/src/mongo/db/ops/modifier_add_to_set.cpp
index 381d06632e3..383991c2b1f 100644
--- a/src/mongo/db/ops/modifier_add_to_set.cpp
+++ b/src/mongo/db/ops/modifier_add_to_set.cpp
@@ -37,155 +37,145 @@
namespace mongo {
- namespace mb = mutablebson;
- namespace str = mongoutils::str;
-
- namespace {
-
- template<typename Ordering, typename Equality>
- void deduplicate(mb::Element parent, Ordering comp, Equality equal) {
-
- // First, build a vector of the children.
- std::vector<mb::Element> children;
- mb::Element current = parent.leftChild();
- while (current.ok()) {
- children.push_back(current);
- current = current.rightSibling();
- }
+namespace mb = mutablebson;
+namespace str = mongoutils::str;
+
+namespace {
+
+template <typename Ordering, typename Equality>
+void deduplicate(mb::Element parent, Ordering comp, Equality equal) {
+ // First, build a vector of the children.
+ std::vector<mb::Element> children;
+ mb::Element current = parent.leftChild();
+ while (current.ok()) {
+ children.push_back(current);
+ current = current.rightSibling();
+ }
- // Then, sort the child vector with our comparator.
- std::sort(children.begin(), children.end(), comp);
+ // Then, sort the child vector with our comparator.
+ std::sort(children.begin(), children.end(), comp);
- // Next, remove duplicates by walking the vector.
- std::vector<mb::Element>::iterator where = children.begin();
- const std::vector<mb::Element>::iterator end = children.end();
+ // Next, remove duplicates by walking the vector.
+ std::vector<mb::Element>::iterator where = children.begin();
+ const std::vector<mb::Element>::iterator end = children.end();
- while( where != end ) {
- std::vector<mb::Element>::iterator next = where; ++next;
- while (next != end && equal(*where, *next)) {
- next->remove();
- ++next;
- }
- where = next;
- }
+ while (where != end) {
+ std::vector<mb::Element>::iterator next = where;
+ ++next;
+ while (next != end && equal(*where, *next)) {
+ next->remove();
+ ++next;
}
+ where = next;
+ }
+}
- } // namespace
+} // namespace
- struct ModifierAddToSet::PreparedState {
+struct ModifierAddToSet::PreparedState {
+ PreparedState(mb::Document& doc)
+ : doc(doc),
+ idxFound(0),
+ elemFound(doc.end()),
+ addAll(false),
+ elementsToAdd(),
+ noOp(false) {}
- PreparedState(mb::Document& doc)
- : doc(doc)
- , idxFound(0)
- , elemFound(doc.end())
- , addAll(false)
- , elementsToAdd()
- , noOp(false) {
- }
+ // Document that is going to be changed.
+ mb::Document& doc;
- // Document that is going to be changed.
- mb::Document& doc;
+ // Index in _fieldRef for which an Element exist in the document.
+ size_t idxFound;
- // Index in _fieldRef for which an Element exist in the document.
- size_t idxFound;
+ // Element corresponding to _fieldRef[0.._idxFound].
+ mb::Element elemFound;
- // Element corresponding to _fieldRef[0.._idxFound].
- mb::Element elemFound;
+ // Are we adding all of the $each elements, or just a subset?
+ bool addAll;
- // Are we adding all of the $each elements, or just a subset?
- bool addAll;
+ // Values to be applied.
+ std::vector<mb::Element> elementsToAdd;
- // Values to be applied.
- std::vector<mb::Element> elementsToAdd;
+ // True if this update is a no-op
+ bool noOp;
+};
- // True if this update is a no-op
- bool noOp;
- };
+ModifierAddToSet::ModifierAddToSet()
+ : ModifierInterface(), _fieldRef(), _posDollar(0), _valDoc(), _val(_valDoc.end()) {}
- ModifierAddToSet::ModifierAddToSet()
- : ModifierInterface ()
- , _fieldRef()
- , _posDollar(0)
- , _valDoc()
- , _val(_valDoc.end()) {
- }
+ModifierAddToSet::~ModifierAddToSet() {}
- ModifierAddToSet::~ModifierAddToSet() {
+Status ModifierAddToSet::init(const BSONElement& modExpr, const Options& opts, bool* positional) {
+ // Perform standard field name and updateable checks.
+ _fieldRef.parse(modExpr.fieldName());
+ Status status = fieldchecker::isUpdatable(_fieldRef);
+ if (!status.isOK()) {
+ return status;
}
- Status ModifierAddToSet::init(const BSONElement& modExpr, const Options& opts,
- bool* positional) {
+ // If a $-positional operator was used, get the index in which it occurred
+ // and ensure only one occurrence.
+ size_t foundCount;
+ bool foundDollar = fieldchecker::isPositional(_fieldRef, &_posDollar, &foundCount);
- // Perform standard field name and updateable checks.
- _fieldRef.parse(modExpr.fieldName());
- Status status = fieldchecker::isUpdatable(_fieldRef);
- if (! status.isOK()) {
- return status;
- }
+ if (positional)
+ *positional = foundDollar;
- // If a $-positional operator was used, get the index in which it occurred
- // and ensure only one occurrence.
- size_t foundCount;
- bool foundDollar = fieldchecker::isPositional(_fieldRef, &_posDollar, &foundCount);
+ if (foundDollar && foundCount > 1) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "Too many positional (i.e. '$') elements found in path '"
+ << _fieldRef.dottedField() << "'");
+ }
- if (positional)
- *positional = foundDollar;
+ // TODO: The driver could potentially do this re-writing.
- if (foundDollar && foundCount > 1) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "Too many positional (i.e. '$') elements found in path '"
- << _fieldRef.dottedField() << "'");
- }
-
- // TODO: The driver could potentially do this re-writing.
-
- // If the type of the value is 'Object', we might be dealing with a $each. See if that
- // is the case.
- if (modExpr.type() == mongo::Object) {
- BSONElement modExprObjPayload = modExpr.embeddedObject().firstElement();
- if (!modExprObjPayload.eoo() && StringData(modExprObjPayload.fieldName()) == "$each") {
- // It is a $each. Verify that the payload is an array as is required for $each,
- // set our flag, and store the array as our value.
- if (modExprObjPayload.type() != mongo::Array) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "The argument to $each in $addToSet must "
- "be an array but it was of type "
- << typeName(modExprObjPayload.type()));
- }
-
- status = _valDoc.root().appendElement(modExprObjPayload);
- if (!status.isOK())
- return status;
-
- _val = _valDoc.root().leftChild();
-
- deduplicate(_val, mb::woLess(false), mb::woEqual(false));
+ // If the type of the value is 'Object', we might be dealing with a $each. See if that
+ // is the case.
+ if (modExpr.type() == mongo::Object) {
+ BSONElement modExprObjPayload = modExpr.embeddedObject().firstElement();
+ if (!modExprObjPayload.eoo() && StringData(modExprObjPayload.fieldName()) == "$each") {
+ // It is a $each. Verify that the payload is an array as is required for $each,
+ // set our flag, and store the array as our value.
+ if (modExprObjPayload.type() != mongo::Array) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "The argument to $each in $addToSet must "
+ "be an array but it was of type "
+ << typeName(modExprObjPayload.type()));
}
- }
-
- // If this wasn't an 'each', turn it into one. No need to sort or de-dup since we only
- // have one element.
- if (_val == _valDoc.end()) {
- mb::Element each = _valDoc.makeElementArray("$each");
- status = each.appendElement(modExpr);
+ status = _valDoc.root().appendElement(modExprObjPayload);
if (!status.isOK())
return status;
- status = _valDoc.root().pushBack(each);
- if (!status.isOK())
- return status;
+ _val = _valDoc.root().leftChild();
- _val = each;
+ deduplicate(_val, mb::woLess(false), mb::woEqual(false));
}
+ }
- // Check if no invalid data (such as fields with '$'s) are being used in the $each
- // clause.
- mb::ConstElement valCursor = _val.leftChild();
- while (valCursor.ok()) {
- const BSONType type = valCursor.getType();
- dassert(valCursor.hasValue());
- switch(type) {
+ // If this wasn't an 'each', turn it into one. No need to sort or de-dup since we only
+ // have one element.
+ if (_val == _valDoc.end()) {
+ mb::Element each = _valDoc.makeElementArray("$each");
+
+ status = each.appendElement(modExpr);
+ if (!status.isOK())
+ return status;
+
+ status = _valDoc.root().pushBack(each);
+ if (!status.isOK())
+ return status;
+
+ _val = each;
+ }
+
+ // Check if no invalid data (such as fields with '$'s) are being used in the $each
+ // clause.
+ mb::ConstElement valCursor = _val.leftChild();
+ while (valCursor.ok()) {
+ const BSONType type = valCursor.getType();
+ dassert(valCursor.hasValue());
+ switch (type) {
case mongo::Object: {
Status s = valCursor.getValueObject().storageValidEmbedded();
if (!s.isOK())
@@ -202,231 +192,207 @@ namespace mongo {
}
default:
break;
- }
-
- valCursor = valCursor.rightSibling();
}
- return Status::OK();
+ valCursor = valCursor.rightSibling();
}
- Status ModifierAddToSet::prepare(mb::Element root,
- StringData matchedField,
- ExecInfo* execInfo) {
+ return Status::OK();
+}
- _preparedState.reset(new PreparedState(root.getDocument()));
+Status ModifierAddToSet::prepare(mb::Element root, StringData matchedField, ExecInfo* execInfo) {
+ _preparedState.reset(new PreparedState(root.getDocument()));
- // If we have a $-positional field, it is time to bind it to an actual field part.
- if (_posDollar) {
- if (matchedField.empty()) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "The positional operator did not find the match "
- "needed from the query. Unexpanded update: "
- << _fieldRef.dottedField());
- }
- _fieldRef.setPart(_posDollar, matchedField);
- }
-
- // Locate the field name in 'root'.
- Status status = pathsupport::findLongestPrefix(_fieldRef,
- root,
- &_preparedState->idxFound,
- &_preparedState->elemFound);
-
- // FindLongestPrefix may say the path does not exist at all, which is fine here, or
- // that the path was not viable or otherwise wrong, in which case, the mod cannot
- // proceed.
- if (status.code() == ErrorCodes::NonExistentPath) {
- _preparedState->elemFound = root.getDocument().end();
- } else if (!status.isOK()) {
- return status;
+ // If we have a $-positional field, it is time to bind it to an actual field part.
+ if (_posDollar) {
+ if (matchedField.empty()) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "The positional operator did not find the match "
+ "needed from the query. Unexpanded update: "
+ << _fieldRef.dottedField());
}
+ _fieldRef.setPart(_posDollar, matchedField);
+ }
- // We register interest in the field name. The driver needs this info to sort out if
- // there is any conflict among mods.
- execInfo->fieldRef[0] = &_fieldRef;
-
- //
- // in-place and no-op logic
- //
-
- // If the field path is not fully present, then this mod cannot be in place, nor is a
- // noOp.
- if (!_preparedState->elemFound.ok() ||
- _preparedState->idxFound < (_fieldRef.numParts() - 1)) {
- // If no target element exists, we will simply be creating a new array.
- _preparedState->addAll = true;
- return Status::OK();
- }
+ // Locate the field name in 'root'.
+ Status status = pathsupport::findLongestPrefix(
+ _fieldRef, root, &_preparedState->idxFound, &_preparedState->elemFound);
+
+ // FindLongestPrefix may say the path does not exist at all, which is fine here, or
+ // that the path was not viable or otherwise wrong, in which case, the mod cannot
+ // proceed.
+ if (status.code() == ErrorCodes::NonExistentPath) {
+ _preparedState->elemFound = root.getDocument().end();
+ } else if (!status.isOK()) {
+ return status;
+ }
- // This operation only applies to arrays
- if (_preparedState->elemFound.getType() != mongo::Array) {
- mb::Element idElem = mb::findElementNamed(root.leftChild(), "_id");
- return Status(
- ErrorCodes::BadValue,
- str::stream() << "Cannot apply $addToSet to a non-array field. Field named '"
- << _preparedState->elemFound.getFieldName()
- << "' has a non-array type "
- << typeName(_preparedState->elemFound.getType())
- << " in the document "
- << idElem.toString());
- }
+ // We register interest in the field name. The driver needs this info to sort out if
+ // there is any conflict among mods.
+ execInfo->fieldRef[0] = &_fieldRef;
- // If the array is empty, then we don't need to check anything: all of the values are
- // going to be added.
- if (!_preparedState->elemFound.hasChildren()) {
- _preparedState->addAll = true;
- return Status::OK();
- }
+ //
+ // in-place and no-op logic
+ //
- // For each value in the $each clause, compare it against the values in the array. If
- // the element is not present, record it as one to add.
- mb::Element eachIter = _val.leftChild();
- while (eachIter.ok()) {
- mb::Element where = mb::findElement(
- _preparedState->elemFound.leftChild(),
- mb::woEqualTo(eachIter, false));
- if (!where.ok()) {
- // The element was not found. Record the element from $each as one to be added.
- _preparedState->elementsToAdd.push_back(eachIter);
- }
- eachIter = eachIter.rightSibling();
- }
+ // If the field path is not fully present, then this mod cannot be in place, nor is a
+ // noOp.
+ if (!_preparedState->elemFound.ok() || _preparedState->idxFound < (_fieldRef.numParts() - 1)) {
+ // If no target element exists, we will simply be creating a new array.
+ _preparedState->addAll = true;
+ return Status::OK();
+ }
- // If we didn't find any elements to add, then this is a no-op.
- if (_preparedState->elementsToAdd.empty()) {
- _preparedState->noOp = execInfo->noOp = true;
- }
+ // This operation only applies to arrays
+ if (_preparedState->elemFound.getType() != mongo::Array) {
+ mb::Element idElem = mb::findElementNamed(root.leftChild(), "_id");
+ return Status(ErrorCodes::BadValue,
+ str::stream()
+ << "Cannot apply $addToSet to a non-array field. Field named '"
+ << _preparedState->elemFound.getFieldName() << "' has a non-array type "
+ << typeName(_preparedState->elemFound.getType()) << " in the document "
+ << idElem.toString());
+ }
+ // If the array is empty, then we don't need to check anything: all of the values are
+ // going to be added.
+ if (!_preparedState->elemFound.hasChildren()) {
+ _preparedState->addAll = true;
return Status::OK();
}
- Status ModifierAddToSet::apply() const {
- dassert(_preparedState->noOp == false);
+ // For each value in the $each clause, compare it against the values in the array. If
+ // the element is not present, record it as one to add.
+ mb::Element eachIter = _val.leftChild();
+ while (eachIter.ok()) {
+ mb::Element where =
+ mb::findElement(_preparedState->elemFound.leftChild(), mb::woEqualTo(eachIter, false));
+ if (!where.ok()) {
+ // The element was not found. Record the element from $each as one to be added.
+ _preparedState->elementsToAdd.push_back(eachIter);
+ }
+ eachIter = eachIter.rightSibling();
+ }
- // TODO: The contents of this block are lifted directly from $push.
+ // If we didn't find any elements to add, then this is a no-op.
+ if (_preparedState->elementsToAdd.empty()) {
+ _preparedState->noOp = execInfo->noOp = true;
+ }
- // If the array field is not there, create it as an array and attach it to the
- // document.
- if (!_preparedState->elemFound.ok() ||
- _preparedState->idxFound < (_fieldRef.numParts() - 1)) {
-
- // Creates the array element
- mb::Document& doc = _preparedState->doc;
- StringData lastPart = _fieldRef.getPart(_fieldRef.numParts() - 1);
- mb::Element baseArray = doc.makeElementArray(lastPart);
- if (!baseArray.ok()) {
- return Status(ErrorCodes::InternalError, "can't create new base array");
- }
+ return Status::OK();
+}
- // Now, we can be in two cases here, as far as attaching the element being set
- // goes: (a) none of the parts in the element's path exist, or (b) some parts of
- // the path exist but not all.
- if (!_preparedState->elemFound.ok()) {
- _preparedState->elemFound = doc.root();
- _preparedState->idxFound = 0;
- }
- else {
- _preparedState->idxFound++;
- }
+Status ModifierAddToSet::apply() const {
+ dassert(_preparedState->noOp == false);
- // createPathAt() will complete the path and attach 'elemToSet' at the end of it.
- Status status = pathsupport::createPathAt(_fieldRef,
- _preparedState->idxFound,
- _preparedState->elemFound,
- baseArray);
- if (!status.isOK()) {
- return status;
- }
+ // TODO: The contents of this block are lifted directly from $push.
- // Point to the base array just created. The subsequent code expects it to exist
- // already.
- _preparedState->elemFound = baseArray;
+ // If the array field is not there, create it as an array and attach it to the
+ // document.
+ if (!_preparedState->elemFound.ok() || _preparedState->idxFound < (_fieldRef.numParts() - 1)) {
+ // Creates the array element
+ mb::Document& doc = _preparedState->doc;
+ StringData lastPart = _fieldRef.getPart(_fieldRef.numParts() - 1);
+ mb::Element baseArray = doc.makeElementArray(lastPart);
+ if (!baseArray.ok()) {
+ return Status(ErrorCodes::InternalError, "can't create new base array");
}
- if (_preparedState->addAll) {
-
- // If we are adding all the values, we can just walk over _val;
+ // Now, we can be in two cases here, as far as attaching the element being set
+ // goes: (a) none of the parts in the element's path exist, or (b) some parts of
+ // the path exist but not all.
+ if (!_preparedState->elemFound.ok()) {
+ _preparedState->elemFound = doc.root();
+ _preparedState->idxFound = 0;
+ } else {
+ _preparedState->idxFound++;
+ }
- mb::Element where = _val.leftChild();
- while (where.ok()) {
+ // createPathAt() will complete the path and attach 'elemToSet' at the end of it.
+ Status status = pathsupport::createPathAt(
+ _fieldRef, _preparedState->idxFound, _preparedState->elemFound, baseArray);
+ if (!status.isOK()) {
+ return status;
+ }
- dassert(where.hasValue());
+ // Point to the base array just created. The subsequent code expects it to exist
+ // already.
+ _preparedState->elemFound = baseArray;
+ }
- mb::Element toAdd = _preparedState->doc.makeElement(where.getValue());
- Status status = _preparedState->elemFound.pushBack(toAdd);
- if (!status.isOK())
- return status;
+ if (_preparedState->addAll) {
+ // If we are adding all the values, we can just walk over _val;
- where = where.rightSibling();
- }
+ mb::Element where = _val.leftChild();
+ while (where.ok()) {
+ dassert(where.hasValue());
- } else {
+ mb::Element toAdd = _preparedState->doc.makeElement(where.getValue());
+ Status status = _preparedState->elemFound.pushBack(toAdd);
+ if (!status.isOK())
+ return status;
- // Otherwise, we aren't adding all the values, and we need to add exactly those
- // elements that were found to be missing during our scan in prepare.
- std::vector<mb::Element>::const_iterator where =
- _preparedState->elementsToAdd.begin();
+ where = where.rightSibling();
+ }
- const std::vector<mb::Element>::const_iterator end =
- _preparedState->elementsToAdd.end();
+ } else {
+ // Otherwise, we aren't adding all the values, and we need to add exactly those
+ // elements that were found to be missing during our scan in prepare.
+ std::vector<mb::Element>::const_iterator where = _preparedState->elementsToAdd.begin();
- for ( ; where != end; ++where) {
+ const std::vector<mb::Element>::const_iterator end = _preparedState->elementsToAdd.end();
- dassert(where->hasValue());
+ for (; where != end; ++where) {
+ dassert(where->hasValue());
- mb::Element toAdd = _preparedState->doc.makeElement(where->getValue());
- Status status = _preparedState->elemFound.pushBack(toAdd);
- if (!status.isOK())
- return status;
- }
+ mb::Element toAdd = _preparedState->doc.makeElement(where->getValue());
+ Status status = _preparedState->elemFound.pushBack(toAdd);
+ if (!status.isOK())
+ return status;
}
-
- return Status::OK();
}
- Status ModifierAddToSet::log(LogBuilder* logBuilder) const {
+ return Status::OK();
+}
- // TODO: This is copied more or less identically from $push. As a result, it copies the
- // behavior in $push that relies on 'apply' having been called unless this is a no-op.
+Status ModifierAddToSet::log(LogBuilder* logBuilder) const {
+ // TODO: This is copied more or less identically from $push. As a result, it copies the
+ // behavior in $push that relies on 'apply' having been called unless this is a no-op.
- // TODO We can log just a positional set in several cases. For now, let's just log the
- // full resulting array.
+ // TODO We can log just a positional set in several cases. For now, let's just log the
+ // full resulting array.
- // We'd like to create an entry such as {$set: {<fieldname>: [<resulting aray>]}} under
- // 'logRoot'. We start by creating the {$set: ...} Element.
- mb::Document& doc = logBuilder->getDocument();
+ // We'd like to create an entry such as {$set: {<fieldname>: [<resulting aray>]}} under
+ // 'logRoot'. We start by creating the {$set: ...} Element.
+ mb::Document& doc = logBuilder->getDocument();
- // Then we create the {<fieldname>:[]} Element, that is, an empty array.
- mb::Element logElement = doc.makeElementArray(_fieldRef.dottedField());
- if (!logElement.ok()) {
- return Status(ErrorCodes::InternalError, "cannot create details for $addToSet mod");
- }
-
- // Fill up the empty array.
- mb::Element curr = _preparedState->elemFound.leftChild();
- while (curr.ok()) {
+ // Then we create the {<fieldname>:[]} Element, that is, an empty array.
+ mb::Element logElement = doc.makeElementArray(_fieldRef.dottedField());
+ if (!logElement.ok()) {
+ return Status(ErrorCodes::InternalError, "cannot create details for $addToSet mod");
+ }
- dassert(curr.hasValue());
+ // Fill up the empty array.
+ mb::Element curr = _preparedState->elemFound.leftChild();
+ while (curr.ok()) {
+ dassert(curr.hasValue());
- // We need to copy each array entry from the resulting document to the log
- // document.
- mb::Element currCopy = doc.makeElementWithNewFieldName(
- StringData(),
- curr.getValue());
- if (!currCopy.ok()) {
- return Status(ErrorCodes::InternalError, "could create copy element");
- }
- Status status = logElement.pushBack(currCopy);
- if (!status.isOK()) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "Could not append entry for $addToSet oplog entry."
- << "Underlying cause: " << status.toString());
- }
- curr = curr.rightSibling();
+ // We need to copy each array entry from the resulting document to the log
+ // document.
+ mb::Element currCopy = doc.makeElementWithNewFieldName(StringData(), curr.getValue());
+ if (!currCopy.ok()) {
+ return Status(ErrorCodes::InternalError, "could create copy element");
}
-
- return logBuilder->addToSets(logElement);
+ Status status = logElement.pushBack(currCopy);
+ if (!status.isOK()) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "Could not append entry for $addToSet oplog entry."
+ << "Underlying cause: " << status.toString());
+ }
+ curr = curr.rightSibling();
}
-} // namespace mongo
+ return logBuilder->addToSets(logElement);
+}
+
+} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_add_to_set.h b/src/mongo/db/ops/modifier_add_to_set.h
index bbbc37ece1e..66bd372834c 100644
--- a/src/mongo/db/ops/modifier_add_to_set.h
+++ b/src/mongo/db/ops/modifier_add_to_set.h
@@ -36,50 +36,46 @@
namespace mongo {
- class LogBuilder;
+class LogBuilder;
- class ModifierAddToSet : public ModifierInterface {
- MONGO_DISALLOW_COPYING(ModifierAddToSet);
+class ModifierAddToSet : public ModifierInterface {
+ MONGO_DISALLOW_COPYING(ModifierAddToSet);
- public:
+public:
+ ModifierAddToSet();
+ virtual ~ModifierAddToSet();
- ModifierAddToSet();
- virtual ~ModifierAddToSet();
+ /** Goes over the array item(s) that are going to be set- unioned and converts them
+ * internally to a mutable bson. Both single and $each forms are supported. Returns OK
+ * if the item(s) are valid otherwise returns a status describing the error.
+ */
+ virtual Status init(const BSONElement& modExpr, const Options& opts, bool* positional = NULL);
- /** Goes over the array item(s) that are going to be set- unioned and converts them
- * internally to a mutable bson. Both single and $each forms are supported. Returns OK
- * if the item(s) are valid otherwise returns a status describing the error.
- */
- virtual Status init(const BSONElement& modExpr, const Options& opts,
- bool* positional = NULL);
+ /** Decides which portion of the array items that are going to be set-unioned to root's
+ * document and fills in 'execInfo' accordingly. Returns OK if the document has a
+ * valid array to set-union to, othwise returns a status describing the error.
+ */
+ virtual Status prepare(mutablebson::Element root, StringData matchedField, ExecInfo* execInfo);
- /** Decides which portion of the array items that are going to be set-unioned to root's
- * document and fills in 'execInfo' accordingly. Returns OK if the document has a
- * valid array to set-union to, othwise returns a status describing the error.
- */
- virtual Status prepare(mutablebson::Element root,
- StringData matchedField,
- ExecInfo* execInfo);
+ /** Updates the Element used in prepare with the effects of the $addToSet operation. */
+ virtual Status apply() const;
- /** Updates the Element used in prepare with the effects of the $addToSet operation. */
- virtual Status apply() const;
+ /** Converts the effects of this $addToSet into one or more equivalent $set operations. */
+ virtual Status log(LogBuilder* logBuilder) const;
- /** Converts the effects of this $addToSet into one or more equivalent $set operations. */
- virtual Status log(LogBuilder* logBuilder) const;
+private:
+ // Access to each component of fieldName that's the target of this mod.
+ FieldRef _fieldRef;
- private:
- // Access to each component of fieldName that's the target of this mod.
- FieldRef _fieldRef;
+ // 0 or index for $-positional in _fieldRef.
+ size_t _posDollar;
- // 0 or index for $-positional in _fieldRef.
- size_t _posDollar;
+ // Array of values to be set-union'ed onto target.
+ mutablebson::Document _valDoc;
+ mutablebson::Element _val;
- // Array of values to be set-union'ed onto target.
- mutablebson::Document _valDoc;
- mutablebson::Element _val;
+ struct PreparedState;
+ std::unique_ptr<PreparedState> _preparedState;
+};
- struct PreparedState;
- std::unique_ptr<PreparedState> _preparedState;
- };
-
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_add_to_set_test.cpp b/src/mongo/db/ops/modifier_add_to_set_test.cpp
index 45741b4c000..43335cb4847 100644
--- a/src/mongo/db/ops/modifier_add_to_set_test.cpp
+++ b/src/mongo/db/ops/modifier_add_to_set_test.cpp
@@ -40,354 +40,350 @@
namespace {
- using mongo::BSONObj;
- using mongo::LogBuilder;
- using mongo::ModifierAddToSet;
- using mongo::ModifierInterface;
- using mongo::Status;
- using mongo::StringData;
- using mongo::fromjson;
- using mongo::mutablebson::Document;
- using mongo::mutablebson::Element;
-
- /** Helper to build and manipulate a $addToSet mod. */
- class Mod {
- public:
- Mod() : _mod() {}
-
- explicit Mod(BSONObj modObj)
- : _modObj(modObj)
- , _mod() {
- ASSERT_OK(_mod.init(_modObj["$addToSet"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
-
- 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);
- }
-
- ModifierAddToSet& mod() {
- return _mod;
- }
-
- private:
- BSONObj _modObj;
- ModifierAddToSet _mod;
- };
-
- TEST(Init, FailToInitWithInvalidValue) {
- BSONObj modObj;
- ModifierAddToSet mod;
-
- modObj = fromjson("{ $addToSet : { a : { 'x.$.y' : 'bad' } } }");
- ASSERT_NOT_OK(mod.init(modObj["$addToSet"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- modObj = fromjson("{ $addToSet : { a : { $each : [ { 'x.$.y' : 'bad' } ] } } }");
- ASSERT_NOT_OK(mod.init(modObj["$addToSet"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
-
- // An int is not valid after $each
- modObj = fromjson("{ $addToSet : { a : { $each : 0 } } }");
- ASSERT_NOT_OK(mod.init(modObj["$addToSet"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
-
- // An object is not valid after $each
- modObj = fromjson("{ $addToSet : { a : { $each : { a : 1 } } } }");
- ASSERT_NOT_OK(mod.init(modObj["$addToSet"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
+using mongo::BSONObj;
+using mongo::LogBuilder;
+using mongo::ModifierAddToSet;
+using mongo::ModifierInterface;
+using mongo::Status;
+using mongo::StringData;
+using mongo::fromjson;
+using mongo::mutablebson::Document;
+using mongo::mutablebson::Element;
+
+/** Helper to build and manipulate a $addToSet mod. */
+class Mod {
+public:
+ Mod() : _mod() {}
+
+ explicit Mod(BSONObj modObj) : _modObj(modObj), _mod() {
+ ASSERT_OK(_mod.init(_modObj["$addToSet"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
}
- TEST(Init, ParsesSimple) {
- Mod(fromjson("{ $addToSet : { a : 1 } }"));
- Mod(fromjson("{ $addToSet : { a : 'foo' } }"));
- Mod(fromjson("{ $addToSet : { a : {} } }"));
- Mod(fromjson("{ $addToSet : { a : { x : 1 } } }"));
- Mod(fromjson("{ $addToSet : { a : [] } }"));
- Mod(fromjson("{ $addToSet : { a : [1, 2] } } }"));
- Mod(fromjson("{ $addToSet : { 'a.b' : 1 } }"));
- Mod(fromjson("{ $addToSet : { 'a.b' : 'foo' } }"));
- Mod(fromjson("{ $addToSet : { 'a.b' : {} } }"));
- Mod(fromjson("{ $addToSet : { 'a.b' : { x : 1} } }"));
- Mod(fromjson("{ $addToSet : { 'a.b' : [] } }"));
- Mod(fromjson("{ $addToSet : { 'a.b' : [1, 2] } } }"));
+ Status prepare(Element root, StringData matchedField, ModifierInterface::ExecInfo* execInfo) {
+ return _mod.prepare(root, matchedField, execInfo);
}
- TEST(Init, ParsesEach) {
- Mod(fromjson("{ $addToSet : { a : { $each : [] } } }"));
- Mod(fromjson("{ $addToSet : { a : { $each : [ 1 ] } } }"));
- Mod(fromjson("{ $addToSet : { a : { $each : [ 1, 2 ] } } }"));
- Mod(fromjson("{ $addToSet : { a : { $each : [ 1, 2, 1 ] } } }"));
- Mod(fromjson("{ $addToSet : { a : { $each : [ {} ] } } }"));
- Mod(fromjson("{ $addToSet : { a : { $each : [ { x : 1 } ] } } }"));
- Mod(fromjson("{ $addToSet : { a : { $each : [ { x : 1 }, { y : 2 } ] } } }"));
- Mod(fromjson("{ $addToSet : { a : { $each : [ { x : 1 }, { y : 2 }, { x : 1 } ] } } }"));
+ Status apply() const {
+ return _mod.apply();
}
- TEST(SimpleMod, PrepareOKTargetNotFound) {
- Document doc(fromjson("{}"));
- Mod mod(fromjson("{ $addToSet : { a : 1 } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
- }
-
- TEST(SimpleMod, PrepareOKTargetFound) {
- Document doc(fromjson("{ a : [ 1 ] }"));
- Mod mod(fromjson("{ $addToSet : { a : 1 } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [ 1 ] } }"), logDoc);
- }
-
- TEST(SimpleMod, PrepareInvalidTargetNumber) {
- Document doc(fromjson("{ a : 1 }"));
- Mod mod(fromjson("{ $addToSet : { a : 1 } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(mod.prepare(doc.root(), "", &execInfo));
- }
-
- TEST(SimpleMod, PrepareInvalidTarget) {
- Document doc(fromjson("{ a : {} }"));
- Mod mod(fromjson("{ $addToSet : { a : 1 } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(mod.prepare(doc.root(), "", &execInfo));
- }
-
- TEST(SimpleMod, ApplyAndLogEmptyDocument) {
- Document doc(fromjson("{}"));
- Mod mod(fromjson("{ $addToSet : { a : 1 } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [ 1 ] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [ 1 ] } }"), logDoc);
- }
-
- TEST(SimpleMod, ApplyAndLogEmptyArray) {
- Document doc(fromjson("{ a : [] }"));
- Mod mod(fromjson("{ $addToSet : { a : 1 } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [ 1 ] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [ 1 ] } }"), logDoc);
- }
-
- TEST(SimpleEachMod, ApplyAndLogEmptyDocument) {
- Document doc(fromjson("{}"));
- Mod mod(fromjson("{ $addToSet : { a : { $each : [1, 2, 3] } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [ 1, 2, 3 ] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [ 1, 2, 3 ] } }"), logDoc);
- }
-
- TEST(SimpleEachMod, ApplyAndLogEmptyArray) {
- Document doc(fromjson("{ a : [] }"));
- Mod mod(fromjson("{ $addToSet : { a : { $each : [1, 2, 3] } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [ 1, 2, 3 ] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [ 1, 2, 3 ] } }"), logDoc);
+ Status log(LogBuilder* logBuilder) const {
+ return _mod.log(logBuilder);
}
- TEST(SimpleMod, ApplyAndLogPopulatedArray) {
- Document doc(fromjson("{ a : [ 'x' ] }"));
- Mod mod(fromjson("{ $addToSet : { a : 1 } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [ 'x', 1 ] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [ 'x', 1 ] } }"), logDoc);
- }
-
- TEST(SimpleEachMod, ApplyAndLogPopulatedArray) {
- Document doc(fromjson("{ a : [ 'x' ] }"));
- Mod mod(fromjson("{ $addToSet : { a : { $each : [1, 2, 3] } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [ 'x', 1, 2, 3 ] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [ 'x', 1, 2, 3 ] } }"), logDoc);
- }
-
- TEST(NoOp, AddOneExistingIsNoOp) {
- Document doc(fromjson("{ a : [ 1, 2, 3 ] }"));
- Mod mod(fromjson("{ $addToSet : { a : 1 } }"));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [ 1, 2, 3 ] } }"), logDoc);
- }
-
- TEST(NoOp, AddSeveralExistingIsNoOp) {
- Document doc(fromjson("{ a : [ 1, 2, 3 ] }"));
- Mod mod(fromjson("{ $addToSet : { a : { $each : [1, 2] } } }"));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [ 1, 2, 3 ] } }"), logDoc);
- }
-
- TEST(NoOp, AddAllExistingIsNoOp) {
- Document doc(fromjson("{ a : [ 1, 2, 3 ] }"));
- Mod mod(fromjson("{ $addToSet : { a : { $each : [1, 2, 3] } } }"));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [ 1, 2, 3 ] } }"), logDoc);
- }
-
- TEST(Deduplication, ExistingDuplicatesArePreserved) {
- Document doc(fromjson("{ a : [ 1, 1, 2, 1, 2, 2 ] }"));
- Mod mod(fromjson("{ $addToSet : { a : 3 } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [ 1, 1, 2, 1, 2, 2, 3] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [ 1, 1, 2, 1, 2, 2, 3] } }"), logDoc);
- }
-
- TEST(Deduplication, NewDuplicatesAreElided) {
- Document doc(fromjson("{ a : [ 1, 1, 2, 1, 2, 2 ] }"));
- Mod mod(fromjson("{ $addToSet : { a : { $each : [ 4, 1, 3, 2, 3, 1, 3, 3, 2, 4] } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [ 1, 1, 2, 1, 2, 2, 4, 3] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [ 1, 1, 2, 1, 2, 2, 4, 3] } }"), logDoc);
- }
-
- TEST(Regressions, SERVER_12848) {
- // Proof that the mod works ok (the real issue was in validate).
-
- Document doc(fromjson("{ _id : 1, a : [ 1, [ ] ] }"));
- Mod mod(fromjson("{ $addToSet : { 'a.1' : 1 } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ _id : 1, a : [ 1, [ 1 ] ] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { 'a.1' : [ 1 ] } }"), logDoc);
+ ModifierAddToSet& mod() {
+ return _mod;
}
-} // namespace
+private:
+ BSONObj _modObj;
+ ModifierAddToSet _mod;
+};
+
+TEST(Init, FailToInitWithInvalidValue) {
+ BSONObj modObj;
+ ModifierAddToSet mod;
+
+ modObj = fromjson("{ $addToSet : { a : { 'x.$.y' : 'bad' } } }");
+ ASSERT_NOT_OK(mod.init(modObj["$addToSet"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+ modObj = fromjson("{ $addToSet : { a : { $each : [ { 'x.$.y' : 'bad' } ] } } }");
+ ASSERT_NOT_OK(mod.init(modObj["$addToSet"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+
+ // An int is not valid after $each
+ modObj = fromjson("{ $addToSet : { a : { $each : 0 } } }");
+ ASSERT_NOT_OK(mod.init(modObj["$addToSet"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+
+ // An object is not valid after $each
+ modObj = fromjson("{ $addToSet : { a : { $each : { a : 1 } } } }");
+ ASSERT_NOT_OK(mod.init(modObj["$addToSet"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
+
+TEST(Init, ParsesSimple) {
+ Mod(fromjson("{ $addToSet : { a : 1 } }"));
+ Mod(fromjson("{ $addToSet : { a : 'foo' } }"));
+ Mod(fromjson("{ $addToSet : { a : {} } }"));
+ Mod(fromjson("{ $addToSet : { a : { x : 1 } } }"));
+ Mod(fromjson("{ $addToSet : { a : [] } }"));
+ Mod(fromjson("{ $addToSet : { a : [1, 2] } } }"));
+ Mod(fromjson("{ $addToSet : { 'a.b' : 1 } }"));
+ Mod(fromjson("{ $addToSet : { 'a.b' : 'foo' } }"));
+ Mod(fromjson("{ $addToSet : { 'a.b' : {} } }"));
+ Mod(fromjson("{ $addToSet : { 'a.b' : { x : 1} } }"));
+ Mod(fromjson("{ $addToSet : { 'a.b' : [] } }"));
+ Mod(fromjson("{ $addToSet : { 'a.b' : [1, 2] } } }"));
+}
+
+TEST(Init, ParsesEach) {
+ Mod(fromjson("{ $addToSet : { a : { $each : [] } } }"));
+ Mod(fromjson("{ $addToSet : { a : { $each : [ 1 ] } } }"));
+ Mod(fromjson("{ $addToSet : { a : { $each : [ 1, 2 ] } } }"));
+ Mod(fromjson("{ $addToSet : { a : { $each : [ 1, 2, 1 ] } } }"));
+ Mod(fromjson("{ $addToSet : { a : { $each : [ {} ] } } }"));
+ Mod(fromjson("{ $addToSet : { a : { $each : [ { x : 1 } ] } } }"));
+ Mod(fromjson("{ $addToSet : { a : { $each : [ { x : 1 }, { y : 2 } ] } } }"));
+ Mod(fromjson("{ $addToSet : { a : { $each : [ { x : 1 }, { y : 2 }, { x : 1 } ] } } }"));
+}
+
+TEST(SimpleMod, PrepareOKTargetNotFound) {
+ Document doc(fromjson("{}"));
+ Mod mod(fromjson("{ $addToSet : { a : 1 } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+}
+
+TEST(SimpleMod, PrepareOKTargetFound) {
+ Document doc(fromjson("{ a : [ 1 ] }"));
+ Mod mod(fromjson("{ $addToSet : { a : 1 } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_TRUE(execInfo.noOp);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : [ 1 ] } }"), logDoc);
+}
+
+TEST(SimpleMod, PrepareInvalidTargetNumber) {
+ Document doc(fromjson("{ a : 1 }"));
+ Mod mod(fromjson("{ $addToSet : { a : 1 } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_NOT_OK(mod.prepare(doc.root(), "", &execInfo));
+}
+
+TEST(SimpleMod, PrepareInvalidTarget) {
+ Document doc(fromjson("{ a : {} }"));
+ Mod mod(fromjson("{ $addToSet : { a : 1 } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_NOT_OK(mod.prepare(doc.root(), "", &execInfo));
+}
+
+TEST(SimpleMod, ApplyAndLogEmptyDocument) {
+ Document doc(fromjson("{}"));
+ Mod mod(fromjson("{ $addToSet : { a : 1 } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : [ 1 ] }"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : [ 1 ] } }"), logDoc);
+}
+
+TEST(SimpleMod, ApplyAndLogEmptyArray) {
+ Document doc(fromjson("{ a : [] }"));
+ Mod mod(fromjson("{ $addToSet : { a : 1 } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : [ 1 ] }"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : [ 1 ] } }"), logDoc);
+}
+
+TEST(SimpleEachMod, ApplyAndLogEmptyDocument) {
+ Document doc(fromjson("{}"));
+ Mod mod(fromjson("{ $addToSet : { a : { $each : [1, 2, 3] } } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : [ 1, 2, 3 ] }"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : [ 1, 2, 3 ] } }"), logDoc);
+}
+
+TEST(SimpleEachMod, ApplyAndLogEmptyArray) {
+ Document doc(fromjson("{ a : [] }"));
+ Mod mod(fromjson("{ $addToSet : { a : { $each : [1, 2, 3] } } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : [ 1, 2, 3 ] }"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : [ 1, 2, 3 ] } }"), logDoc);
+}
+
+TEST(SimpleMod, ApplyAndLogPopulatedArray) {
+ Document doc(fromjson("{ a : [ 'x' ] }"));
+ Mod mod(fromjson("{ $addToSet : { a : 1 } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : [ 'x', 1 ] }"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : [ 'x', 1 ] } }"), logDoc);
+}
+
+TEST(SimpleEachMod, ApplyAndLogPopulatedArray) {
+ Document doc(fromjson("{ a : [ 'x' ] }"));
+ Mod mod(fromjson("{ $addToSet : { a : { $each : [1, 2, 3] } } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : [ 'x', 1, 2, 3 ] }"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : [ 'x', 1, 2, 3 ] } }"), logDoc);
+}
+
+TEST(NoOp, AddOneExistingIsNoOp) {
+ Document doc(fromjson("{ a : [ 1, 2, 3 ] }"));
+ Mod mod(fromjson("{ $addToSet : { a : 1 } }"));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_TRUE(execInfo.noOp);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : [ 1, 2, 3 ] } }"), logDoc);
+}
+
+TEST(NoOp, AddSeveralExistingIsNoOp) {
+ Document doc(fromjson("{ a : [ 1, 2, 3 ] }"));
+ Mod mod(fromjson("{ $addToSet : { a : { $each : [1, 2] } } }"));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_TRUE(execInfo.noOp);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : [ 1, 2, 3 ] } }"), logDoc);
+}
+
+TEST(NoOp, AddAllExistingIsNoOp) {
+ Document doc(fromjson("{ a : [ 1, 2, 3 ] }"));
+ Mod mod(fromjson("{ $addToSet : { a : { $each : [1, 2, 3] } } }"));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_TRUE(execInfo.noOp);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : [ 1, 2, 3 ] } }"), logDoc);
+}
+
+TEST(Deduplication, ExistingDuplicatesArePreserved) {
+ Document doc(fromjson("{ a : [ 1, 1, 2, 1, 2, 2 ] }"));
+ Mod mod(fromjson("{ $addToSet : { a : 3 } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : [ 1, 1, 2, 1, 2, 2, 3] }"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : [ 1, 1, 2, 1, 2, 2, 3] } }"), logDoc);
+}
+
+TEST(Deduplication, NewDuplicatesAreElided) {
+ Document doc(fromjson("{ a : [ 1, 1, 2, 1, 2, 2 ] }"));
+ Mod mod(fromjson("{ $addToSet : { a : { $each : [ 4, 1, 3, 2, 3, 1, 3, 3, 2, 4] } } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : [ 1, 1, 2, 1, 2, 2, 4, 3] }"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : [ 1, 1, 2, 1, 2, 2, 4, 3] } }"), logDoc);
+}
+
+TEST(Regressions, SERVER_12848) {
+ // Proof that the mod works ok (the real issue was in validate).
+
+ Document doc(fromjson("{ _id : 1, a : [ 1, [ ] ] }"));
+ Mod mod(fromjson("{ $addToSet : { 'a.1' : 1 } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ _id : 1, a : [ 1, [ 1 ] ] }"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { 'a.1' : [ 1 ] } }"), logDoc);
+}
+
+} // namespace
diff --git a/src/mongo/db/ops/modifier_bit.cpp b/src/mongo/db/ops/modifier_bit.cpp
index c6d221875f9..bdddb9415f2 100644
--- a/src/mongo/db/ops/modifier_bit.cpp
+++ b/src/mongo/db/ops/modifier_bit.cpp
@@ -38,263 +38,233 @@
namespace mongo {
- namespace mb = mutablebson;
- namespace str = mongoutils::str;
+namespace mb = mutablebson;
+namespace str = mongoutils::str;
- struct ModifierBit::PreparedState {
+struct ModifierBit::PreparedState {
+ PreparedState(mutablebson::Document& doc)
+ : doc(doc), idxFound(0), elemFound(doc.end()), noOp(false) {}
- PreparedState(mutablebson::Document& doc)
- : doc(doc)
- , idxFound(0)
- , elemFound(doc.end())
- , noOp(false) {
- }
+ // Document that is going to be changed.
+ mutablebson::Document& doc;
+
+ // Index in _fieldRef for which an Element exist in the document.
+ size_t idxFound;
- // Document that is going to be changed.
- mutablebson::Document& doc;
+ // Element corresponding to _fieldRef[0.._idxFound].
+ mutablebson::Element elemFound;
- // Index in _fieldRef for which an Element exist in the document.
- size_t idxFound;
+ // Value to be applied.
+ SafeNum newValue;
- // Element corresponding to _fieldRef[0.._idxFound].
- mutablebson::Element elemFound;
+ // True if this update is a no-op
+ bool noOp;
+};
- // Value to be applied.
- SafeNum newValue;
+ModifierBit::ModifierBit() : ModifierInterface(), _fieldRef(), _posDollar(0), _ops() {}
- // True if this update is a no-op
- bool noOp;
- };
+ModifierBit::~ModifierBit() {}
- ModifierBit::ModifierBit()
- : ModifierInterface ()
- , _fieldRef()
- , _posDollar(0)
- , _ops() {
+Status ModifierBit::init(const BSONElement& modExpr, const Options& opts, bool* positional) {
+ // Perform standard field name and updateable checks.
+ _fieldRef.parse(modExpr.fieldName());
+ Status status = fieldchecker::isUpdatable(_fieldRef);
+ if (!status.isOK()) {
+ return status;
}
- ModifierBit::~ModifierBit() {
+ // If a $-positional operator was used, get the index in which it occurred
+ // and ensure only one occurrence.
+ size_t foundCount;
+ bool foundDollar = fieldchecker::isPositional(_fieldRef, &_posDollar, &foundCount);
+
+ if (positional)
+ *positional = foundDollar;
+
+ if (foundDollar && foundCount > 1) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "Too many positional (i.e. '$') elements found in path '"
+ << _fieldRef.dottedField() << "'");
}
- Status ModifierBit::init(const BSONElement& modExpr, const Options& opts,
- bool* positional) {
+ if (modExpr.type() != mongo::Object)
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "The $bit modifier is not compatible with a "
+ << typeName(modExpr.type())
+ << ". You must pass in an embedded document: "
+ "{$bit: {field: {and/or/xor: #}}");
- // Perform standard field name and updateable checks.
- _fieldRef.parse(modExpr.fieldName());
- Status status = fieldchecker::isUpdatable(_fieldRef);
- if (! status.isOK()) {
- return status;
- }
+ BSONObjIterator opsIterator(modExpr.embeddedObject());
- // If a $-positional operator was used, get the index in which it occurred
- // and ensure only one occurrence.
- size_t foundCount;
- bool foundDollar = fieldchecker::isPositional(_fieldRef, &_posDollar, &foundCount);
+ while (opsIterator.more()) {
+ BSONElement curOp = opsIterator.next();
- if (positional)
- *positional = foundDollar;
+ const StringData payloadFieldName = curOp.fieldName();
- if (foundDollar && foundCount > 1) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "Too many positional (i.e. '$') elements found in path '"
- << _fieldRef.dottedField() << "'");
- }
+ SafeNumOp op = NULL;
- if (modExpr.type() != mongo::Object)
+ if (payloadFieldName == "and") {
+ op = &SafeNum::bitAnd;
+ } else if (payloadFieldName == "or") {
+ op = &SafeNum::bitOr;
+ } else if (payloadFieldName == "xor") {
+ op = &SafeNum::bitXor;
+ } else {
return Status(ErrorCodes::BadValue,
- str::stream() << "The $bit modifier is not compatible with a "
- << typeName(modExpr.type())
- << ". You must pass in an embedded document: "
- "{$bit: {field: {and/or/xor: #}}");
-
- BSONObjIterator opsIterator(modExpr.embeddedObject());
-
- while (opsIterator.more()) {
- BSONElement curOp = opsIterator.next();
-
- const StringData payloadFieldName = curOp.fieldName();
-
- SafeNumOp op = NULL;
-
- if (payloadFieldName == "and") {
- op = &SafeNum::bitAnd;
- }
- else if (payloadFieldName == "or") {
- op = &SafeNum::bitOr;
- }
- else if (payloadFieldName == "xor") {
- op = &SafeNum::bitXor;
- }
- else {
- return Status(
- ErrorCodes::BadValue,
- str::stream() << "The $bit modifier only supports 'and', 'or', and 'xor', not '"
- << payloadFieldName
- << "' which is an unknown operator: {" << curOp << "}");
- }
-
- if ((curOp.type() != mongo::NumberInt) &&
- (curOp.type() != mongo::NumberLong))
- return Status(
- ErrorCodes::BadValue,
- str::stream() << "The $bit modifier field must be an Integer(32/64 bit); a '"
- << typeName(curOp.type())
- << "' is not supported here: {" << curOp << "}");
-
- const OpEntry entry = {SafeNum(curOp), op};
- _ops.push_back(entry);
+ str::stream()
+ << "The $bit modifier only supports 'and', 'or', and 'xor', not '"
+ << payloadFieldName << "' which is an unknown operator: {" << curOp
+ << "}");
}
- dassert(!_ops.empty());
+ if ((curOp.type() != mongo::NumberInt) && (curOp.type() != mongo::NumberLong))
+ return Status(ErrorCodes::BadValue,
+ str::stream()
+ << "The $bit modifier field must be an Integer(32/64 bit); a '"
+ << typeName(curOp.type()) << "' is not supported here: {" << curOp
+ << "}");
- return Status::OK();
+ const OpEntry entry = {SafeNum(curOp), op};
+ _ops.push_back(entry);
}
- Status ModifierBit::prepare(mutablebson::Element root,
- StringData matchedField,
- ExecInfo* execInfo) {
-
- _preparedState.reset(new PreparedState(root.getDocument()));
-
- // If we have a $-positional field, it is time to bind it to an actual field part.
- if (_posDollar) {
- if (matchedField.empty()) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "The positional operator did not find the match "
- "needed from the query. Unexpanded update: "
- << _fieldRef.dottedField());
- }
- _fieldRef.setPart(_posDollar, matchedField);
- }
+ dassert(!_ops.empty());
- // Locate the field name in 'root'.
- Status status = pathsupport::findLongestPrefix(_fieldRef,
- root,
- &_preparedState->idxFound,
- &_preparedState->elemFound);
+ return Status::OK();
+}
+Status ModifierBit::prepare(mutablebson::Element root,
+ StringData matchedField,
+ ExecInfo* execInfo) {
+ _preparedState.reset(new PreparedState(root.getDocument()));
- // FindLongestPrefix may say the path does not exist at all, which is fine here, or
- // that the path was not viable or otherwise wrong, in which case, the mod cannot
- // proceed.
- if (status.code() == ErrorCodes::NonExistentPath) {
- _preparedState->elemFound = root.getDocument().end();
- }
- else if (!status.isOK()) {
- return status;
+ // If we have a $-positional field, it is time to bind it to an actual field part.
+ if (_posDollar) {
+ if (matchedField.empty()) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "The positional operator did not find the match "
+ "needed from the query. Unexpanded update: "
+ << _fieldRef.dottedField());
}
+ _fieldRef.setPart(_posDollar, matchedField);
+ }
- // We register interest in the field name. The driver needs this info to sort out if
- // there is any conflict among mods.
- execInfo->fieldRef[0] = &_fieldRef;
-
- //
- // in-place and no-op logic
- //
-
- // If the field path is not fully present, then this mod cannot be in place, nor is a
- // noOp.
- if (!_preparedState->elemFound.ok() ||
- _preparedState->idxFound < (_fieldRef.numParts() - 1)) {
- // If no target element exists, the value we will write is the result of applying
- // the operation to a zero-initialized integer element.
- _preparedState->newValue = apply(SafeNum(static_cast<int>(0)));
- return Status::OK();
- }
+ // Locate the field name in 'root'.
+ Status status = pathsupport::findLongestPrefix(
+ _fieldRef, root, &_preparedState->idxFound, &_preparedState->elemFound);
- if (!_preparedState->elemFound.isIntegral()) {
- mb::Element idElem = mb::findElementNamed(root.leftChild(), "_id");
- return Status(
- ErrorCodes::BadValue,
- str::stream() << "Cannot apply $bit to a value of non-integral type."
- << idElem.toString()
- << " has the field " << _preparedState->elemFound.getFieldName()
- << " of non-integer type "
- << typeName(_preparedState->elemFound.getType()));
- }
- const SafeNum currentValue = _preparedState->elemFound.getValueSafeNum();
+ // FindLongestPrefix may say the path does not exist at all, which is fine here, or
+ // that the path was not viable or otherwise wrong, in which case, the mod cannot
+ // proceed.
+ if (status.code() == ErrorCodes::NonExistentPath) {
+ _preparedState->elemFound = root.getDocument().end();
+ } else if (!status.isOK()) {
+ return status;
+ }
- // Apply the op over the existing value and the mod value, and capture the result.
- _preparedState->newValue = apply(currentValue);
+ // We register interest in the field name. The driver needs this info to sort out if
+ // there is any conflict among mods.
+ execInfo->fieldRef[0] = &_fieldRef;
- if (!_preparedState->newValue.isValid()) {
- // TODO: Include list of ops, if that is easy, at some future point.
- return Status(ErrorCodes::BadValue,
- str::stream() << "Failed to apply $bit operations to current value: "
- << currentValue.debugString());
- }
- // If the values are identical (same type, same value), then this is a no-op.
- if (_preparedState->newValue.isIdentical(currentValue)) {
- _preparedState->noOp = execInfo->noOp = true;
- return Status::OK();
- }
+ //
+ // in-place and no-op logic
+ //
+ // If the field path is not fully present, then this mod cannot be in place, nor is a
+ // noOp.
+ if (!_preparedState->elemFound.ok() || _preparedState->idxFound < (_fieldRef.numParts() - 1)) {
+ // If no target element exists, the value we will write is the result of applying
+ // the operation to a zero-initialized integer element.
+ _preparedState->newValue = apply(SafeNum(static_cast<int>(0)));
return Status::OK();
}
- Status ModifierBit::apply() const {
- dassert(_preparedState->noOp == false);
-
- // If there's no need to create any further field part, the $bit is simply a value
- // assignment.
- if (_preparedState->elemFound.ok() &&
- _preparedState->idxFound == (_fieldRef.numParts() - 1)) {
- return _preparedState->elemFound.setValueSafeNum(_preparedState->newValue);
- }
-
- //
- // Complete document path logic
- //
+ if (!_preparedState->elemFound.isIntegral()) {
+ mb::Element idElem = mb::findElementNamed(root.leftChild(), "_id");
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "Cannot apply $bit to a value of non-integral type."
+ << idElem.toString() << " has the field "
+ << _preparedState->elemFound.getFieldName()
+ << " of non-integer type "
+ << typeName(_preparedState->elemFound.getType()));
+ }
- // Creates the final element that's going to be $set in 'doc'.
- mutablebson::Document& doc = _preparedState->doc;
- StringData lastPart = _fieldRef.getPart(_fieldRef.numParts() - 1);
- mutablebson::Element elemToSet = doc.makeElementSafeNum(lastPart, _preparedState->newValue);
- if (!elemToSet.ok()) {
- return Status(ErrorCodes::InternalError, "can't create new element");
- }
+ const SafeNum currentValue = _preparedState->elemFound.getValueSafeNum();
- // Now, we can be in two cases here, as far as attaching the element being set goes:
- // (a) none of the parts in the element's path exist, or (b) some parts of the path
- // exist but not all.
- if (!_preparedState->elemFound.ok()) {
- _preparedState->elemFound = doc.root();
- _preparedState->idxFound = 0;
- }
- else {
- _preparedState->idxFound++;
- }
+ // Apply the op over the existing value and the mod value, and capture the result.
+ _preparedState->newValue = apply(currentValue);
- // createPathAt() will complete the path and attach 'elemToSet' at the end of it.
- return pathsupport::createPathAt(_fieldRef,
- _preparedState->idxFound,
- _preparedState->elemFound,
- elemToSet);
+ if (!_preparedState->newValue.isValid()) {
+ // TODO: Include list of ops, if that is easy, at some future point.
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "Failed to apply $bit operations to current value: "
+ << currentValue.debugString());
+ }
+ // If the values are identical (same type, same value), then this is a no-op.
+ if (_preparedState->newValue.isIdentical(currentValue)) {
+ _preparedState->noOp = execInfo->noOp = true;
+ return Status::OK();
}
- Status ModifierBit::log(LogBuilder* logBuilder) const {
+ return Status::OK();
+}
- mutablebson::Element logElement = logBuilder->getDocument().makeElementSafeNum(
- _fieldRef.dottedField(),
- _preparedState->newValue);
+Status ModifierBit::apply() const {
+ dassert(_preparedState->noOp == false);
- if (!logElement.ok()) {
- return Status(ErrorCodes::InternalError,
- str::stream() << "Could not append entry to $bit oplog entry: "
- << "set '" << _fieldRef.dottedField() << "' -> "
- << _preparedState->newValue.debugString() );
- }
- return logBuilder->addToSets(logElement);
+ // If there's no need to create any further field part, the $bit is simply a value
+ // assignment.
+ if (_preparedState->elemFound.ok() && _preparedState->idxFound == (_fieldRef.numParts() - 1)) {
+ return _preparedState->elemFound.setValueSafeNum(_preparedState->newValue);
+ }
+ //
+ // Complete document path logic
+ //
+
+ // Creates the final element that's going to be $set in 'doc'.
+ mutablebson::Document& doc = _preparedState->doc;
+ StringData lastPart = _fieldRef.getPart(_fieldRef.numParts() - 1);
+ mutablebson::Element elemToSet = doc.makeElementSafeNum(lastPart, _preparedState->newValue);
+ if (!elemToSet.ok()) {
+ return Status(ErrorCodes::InternalError, "can't create new element");
}
- SafeNum ModifierBit::apply(SafeNum value) const {
- OpEntries::const_iterator where = _ops.begin();
- const OpEntries::const_iterator end = _ops.end();
- for (; where != end; ++where)
- value = (value.*(where->op))(where->val);
- return value;
+ // Now, we can be in two cases here, as far as attaching the element being set goes:
+ // (a) none of the parts in the element's path exist, or (b) some parts of the path
+ // exist but not all.
+ if (!_preparedState->elemFound.ok()) {
+ _preparedState->elemFound = doc.root();
+ _preparedState->idxFound = 0;
+ } else {
+ _preparedState->idxFound++;
}
-} // namespace mongo
+ // createPathAt() will complete the path and attach 'elemToSet' at the end of it.
+ return pathsupport::createPathAt(
+ _fieldRef, _preparedState->idxFound, _preparedState->elemFound, elemToSet);
+}
+
+Status ModifierBit::log(LogBuilder* logBuilder) const {
+ mutablebson::Element logElement = logBuilder->getDocument().makeElementSafeNum(
+ _fieldRef.dottedField(), _preparedState->newValue);
+
+ if (!logElement.ok()) {
+ return Status(ErrorCodes::InternalError,
+ str::stream() << "Could not append entry to $bit oplog entry: "
+ << "set '" << _fieldRef.dottedField() << "' -> "
+ << _preparedState->newValue.debugString());
+ }
+ return logBuilder->addToSets(logElement);
+}
+
+SafeNum ModifierBit::apply(SafeNum value) const {
+ OpEntries::const_iterator where = _ops.begin();
+ const OpEntries::const_iterator end = _ops.end();
+ for (; where != end; ++where)
+ value = (value.*(where->op))(where->val);
+ return value;
+}
+
+} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_bit.h b/src/mongo/db/ops/modifier_bit.h
index 812ac8f41e1..7ecc416a42d 100644
--- a/src/mongo/db/ops/modifier_bit.h
+++ b/src/mongo/db/ops/modifier_bit.h
@@ -39,61 +39,57 @@
namespace mongo {
- class LogBuilder;
+class LogBuilder;
- class ModifierBit : public ModifierInterface {
- MONGO_DISALLOW_COPYING(ModifierBit);
+class ModifierBit : public ModifierInterface {
+ MONGO_DISALLOW_COPYING(ModifierBit);
- public:
+public:
+ ModifierBit();
+ virtual ~ModifierBit();
- ModifierBit();
- virtual ~ModifierBit();
+ /**
+ * A 'modExpr' is a BSONElement {<fieldname>: <value>} coming from a $bit mod such as
+ * {$bit: {<field: { [and|or] : <value>}}. init() extracts the field name, the
+ * operation subtype, and the value to be assigned to it from 'modExpr'. It returns OK
+ * if successful or a status describing the error.
+ */
+ virtual Status init(const BSONElement& modExpr, const Options& opts, bool* positional = NULL);
- /**
- * A 'modExpr' is a BSONElement {<fieldname>: <value>} coming from a $bit mod such as
- * {$bit: {<field: { [and|or] : <value>}}. init() extracts the field name, the
- * operation subtype, and the value to be assigned to it from 'modExpr'. It returns OK
- * if successful or a status describing the error.
- */
- virtual Status init(const BSONElement& modExpr, const Options& opts,
- bool* positional = NULL);
+ /** Validates the potential application of the init'ed mod to the given Element and
+ * configures the internal state of the mod as necessary.
+ */
+ virtual Status prepare(mutablebson::Element root, StringData matchedField, ExecInfo* execInfo);
- /** Validates the potential application of the init'ed mod to the given Element and
- * configures the internal state of the mod as necessary.
- */
- virtual Status prepare(mutablebson::Element root,
- StringData matchedField,
- ExecInfo* execInfo);
+ /** Updates the Element used in prepare with the effects of the $bit operation */
+ virtual Status apply() const;
- /** Updates the Element used in prepare with the effects of the $bit operation */
- virtual Status apply() const;
+ /** Converts the effects of this $bit into an equivalent $set */
+ virtual Status log(LogBuilder* logBuilder) const;
- /** Converts the effects of this $bit into an equivalent $set */
- virtual Status log(LogBuilder* logBuilder) const;
+private:
+ SafeNum apply(SafeNum value) const;
- private:
- SafeNum apply(SafeNum value) const;
+ // Access to each component of fieldName that's the target of this mod.
+ FieldRef _fieldRef;
- // Access to each component of fieldName that's the target of this mod.
- FieldRef _fieldRef;
+ // 0 or index for $-positional in _fieldRef.
+ size_t _posDollar;
- // 0 or index for $-positional in _fieldRef.
- size_t _posDollar;
+ // The operator on SafeNum that we will invoke.
+ typedef SafeNum (SafeNum::*SafeNumOp)(const SafeNum&) const;
- // The operator on SafeNum that we will invoke.
- typedef SafeNum (SafeNum::* SafeNumOp)(const SafeNum&) const;
-
- struct OpEntry {
- SafeNum val;
- SafeNumOp op;
- };
+ struct OpEntry {
+ SafeNum val;
+ SafeNumOp op;
+ };
- typedef std::vector<OpEntry> OpEntries;
+ typedef std::vector<OpEntry> OpEntries;
- OpEntries _ops;
+ OpEntries _ops;
- struct PreparedState;
- std::unique_ptr<PreparedState> _preparedState;
- };
+ struct PreparedState;
+ std::unique_ptr<PreparedState> _preparedState;
+};
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_bit_test.cpp b/src/mongo/db/ops/modifier_bit_test.cpp
index 3d19808757e..59e13f89e24 100644
--- a/src/mongo/db/ops/modifier_bit_test.cpp
+++ b/src/mongo/db/ops/modifier_bit_test.cpp
@@ -40,697 +40,693 @@
namespace {
- using mongo::BSONObj;
- using mongo::LogBuilder;
- using mongo::ModifierBit;
- using mongo::ModifierInterface;
- using mongo::Status;
- using mongo::StringData;
- using mongo::fromjson;
- using mongo::mutablebson::ConstElement;
- using mongo::mutablebson::Document;
- using mongo::mutablebson::Element;
-
- /** Helper to build and manipulate a $bit mod. */
- class Mod {
- public:
- Mod() : _mod() {}
-
- explicit Mod(BSONObj modObj)
- : _modObj(modObj)
- , _mod() {
- ASSERT_OK(_mod.init(_modObj["$bit"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
-
- }
-
- 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);
- }
-
- ModifierBit& mod() { return _mod; }
-
- private:
- BSONObj _modObj;
- ModifierBit _mod;
- };
-
-
- TEST(Init, FailToInitWithInvalidValue) {
- BSONObj modObj;
- ModifierBit mod;
-
- // String is an invalid $bit argument
- modObj = fromjson("{ $bit : { a : '' } }");
- ASSERT_NOT_OK(mod.init(modObj["$bit"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
-
- // Array is an invalid $bit argument
- modObj = fromjson("{ $bit : { a : [] } }");
- ASSERT_NOT_OK(mod.init(modObj["$bit"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
-
- // An object with value not in ('and', 'or') is an invalid $bit argument
- modObj = fromjson("{ $bit : { a : { foo : 4 } } }");
- ASSERT_NOT_OK(mod.init(modObj["$bit"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
-
- // The argument to the sub-operator must be numeric
- modObj = fromjson("{ $bit : { a : { or : [] } } }");
- ASSERT_NOT_OK(mod.init(modObj["$bit"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
-
- modObj = fromjson("{ $bit : { a : { or : 'foo' } } }");
- ASSERT_NOT_OK(mod.init(modObj["$bit"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
-
- // The argument to the sub-operator must be integral
- modObj = fromjson("{ $bit : { a : { or : 1.0 } } }");
- ASSERT_NOT_OK(mod.init(modObj["$bit"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
+using mongo::BSONObj;
+using mongo::LogBuilder;
+using mongo::ModifierBit;
+using mongo::ModifierInterface;
+using mongo::Status;
+using mongo::StringData;
+using mongo::fromjson;
+using mongo::mutablebson::ConstElement;
+using mongo::mutablebson::Document;
+using mongo::mutablebson::Element;
- TEST(Init, ParsesAndInt) {
- Mod mod(BSON("$bit" << BSON("a" << BSON("and" << static_cast<int>(1)))));
- }
+/** Helper to build and manipulate a $bit mod. */
+class Mod {
+public:
+ Mod() : _mod() {}
- TEST(Init, ParsesOrInt) {
- Mod mod(BSON("$bit" << BSON("a" << BSON("or" << static_cast<int>(1)))));
+ explicit Mod(BSONObj modObj) : _modObj(modObj), _mod() {
+ ASSERT_OK(_mod.init(_modObj["$bit"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
}
- TEST(Init, ParsesXorInt) {
- Mod mod(BSON("$bit" << BSON("a" << BSON("xor" << static_cast<int>(1)))));
+ Status prepare(Element root, StringData matchedField, ModifierInterface::ExecInfo* execInfo) {
+ return _mod.prepare(root, matchedField, execInfo);
}
- TEST(Init, ParsesAndLong) {
- Mod mod(BSON("$bit" << BSON("a" << BSON("and" << static_cast<long long>(1)))));
+ Status apply() const {
+ return _mod.apply();
}
- TEST(Init, ParsesOrLong) {
- Mod mod(BSON("$bit" << BSON("a" << BSON("or" << static_cast<long long>(1)))));
+ Status log(LogBuilder* logBuilder) const {
+ return _mod.log(logBuilder);
}
- TEST(Init, ParsesXorLong) {
- Mod mod(BSON("$bit" << BSON("a" << BSON("xor" << static_cast<long long>(1)))));
+ ModifierBit& mod() {
+ return _mod;
}
- TEST(SimpleMod, PrepareOKTargetNotFound) {
- Document doc(fromjson("{}"));
- Mod mod(fromjson("{ $bit : { a : { and : 1 } } }"));
+private:
+ BSONObj _modObj;
+ ModifierBit _mod;
+};
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- }
+TEST(Init, FailToInitWithInvalidValue) {
+ BSONObj modObj;
+ ModifierBit mod;
- TEST(SimpleMod, PrepareOKTargetFound) {
- Document doc(fromjson("{ a : 1 }"));
- Mod mod(fromjson("{ $bit : { a : { and : 1 } } }"));
+ // String is an invalid $bit argument
+ modObj = fromjson("{ $bit : { a : '' } }");
+ ASSERT_NOT_OK(mod.init(modObj["$bit"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
- ModifierInterface::ExecInfo execInfo;
+ // Array is an invalid $bit argument
+ modObj = fromjson("{ $bit : { a : [] } }");
+ ASSERT_NOT_OK(mod.init(modObj["$bit"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
+ // An object with value not in ('and', 'or') is an invalid $bit argument
+ modObj = fromjson("{ $bit : { a : { foo : 4 } } }");
+ ASSERT_NOT_OK(mod.init(modObj["$bit"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 1 } }"), logDoc);
- }
+ // The argument to the sub-operator must be numeric
+ modObj = fromjson("{ $bit : { a : { or : [] } } }");
+ ASSERT_NOT_OK(mod.init(modObj["$bit"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
- TEST(SimpleMod, PrepareSimpleNonNumericObject) {
- Document doc(fromjson("{ a : {} }"));
- Mod mod(fromjson("{ $bit : { a : { or : 1 } } }"));
+ modObj = fromjson("{ $bit : { a : { or : 'foo' } } }");
+ ASSERT_NOT_OK(mod.init(modObj["$bit"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(mod.prepare(doc.root(), "", &execInfo));
- }
+ // The argument to the sub-operator must be integral
+ modObj = fromjson("{ $bit : { a : { or : 1.0 } } }");
+ ASSERT_NOT_OK(mod.init(modObj["$bit"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
- TEST(SimpleMod, PrepareSimpleNonNumericArray) {
+TEST(Init, ParsesAndInt) {
+ Mod mod(BSON("$bit" << BSON("a" << BSON("and" << static_cast<int>(1)))));
+}
- Document doc(fromjson("{ a : [] }"));
- Mod mod(fromjson("{ $bit : { a : { and : 1 } } }"));
+TEST(Init, ParsesOrInt) {
+ Mod mod(BSON("$bit" << BSON("a" << BSON("or" << static_cast<int>(1)))));
+}
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(mod.prepare(doc.root(), "", &execInfo));
- }
+TEST(Init, ParsesXorInt) {
+ Mod mod(BSON("$bit" << BSON("a" << BSON("xor" << static_cast<int>(1)))));
+}
- TEST(SimpleMod, PrepareSimpleNonNumericString) {
- Document doc(fromjson("{ a : '' }"));
- Mod mod(fromjson("{ $bit : { a : { or : 1 } } }"));
+TEST(Init, ParsesAndLong) {
+ Mod mod(BSON("$bit" << BSON("a" << BSON("and" << static_cast<long long>(1)))));
+}
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(mod.prepare(doc.root(), "", &execInfo));
- }
+TEST(Init, ParsesOrLong) {
+ Mod mod(BSON("$bit" << BSON("a" << BSON("or" << static_cast<long long>(1)))));
+}
- TEST(SimpleMod, ApplyAndLogEmptyDocumentAnd) {
- Document doc(fromjson("{}"));
- Mod mod(fromjson("{ $bit : { a : { and : 1 } } }"));
+TEST(Init, ParsesXorLong) {
+ Mod mod(BSON("$bit" << BSON("a" << BSON("xor" << static_cast<long long>(1)))));
+}
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
+TEST(SimpleMod, PrepareOKTargetNotFound) {
+ Document doc(fromjson("{}"));
+ Mod mod(fromjson("{ $bit : { a : { and : 1 } } }"));
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 0 }"), doc);
+ ModifierInterface::ExecInfo execInfo;
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 0 } }"), logDoc);
- }
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+}
- TEST(SimpleMod, ApplyAndLogEmptyDocumentOr) {
- Document doc(fromjson("{}"));
- Mod mod(fromjson("{ $bit : { a : { or : 1 } } }"));
+TEST(SimpleMod, PrepareOKTargetFound) {
+ Document doc(fromjson("{ a : 1 }"));
+ Mod mod(fromjson("{ $bit : { a : { and : 1 } } }"));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
+ ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 1 }"), doc);
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.noOp);
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 1 } }"), logDoc);
- }
-
- TEST(SimpleMod, ApplyAndLogEmptyDocumentXor) {
- Document doc(fromjson("{}"));
- Mod mod(fromjson("{ $bit : { a : { xor : 1 } } }"));
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : 1 } }"), logDoc);
+}
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
+TEST(SimpleMod, PrepareSimpleNonNumericObject) {
+ Document doc(fromjson("{ a : {} }"));
+ Mod mod(fromjson("{ $bit : { a : { or : 1 } } }"));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_NOT_OK(mod.prepare(doc.root(), "", &execInfo));
+}
+
+TEST(SimpleMod, PrepareSimpleNonNumericArray) {
+ Document doc(fromjson("{ a : [] }"));
+ Mod mod(fromjson("{ $bit : { a : { and : 1 } } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_NOT_OK(mod.prepare(doc.root(), "", &execInfo));
+}
+
+TEST(SimpleMod, PrepareSimpleNonNumericString) {
+ Document doc(fromjson("{ a : '' }"));
+ Mod mod(fromjson("{ $bit : { a : { or : 1 } } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_NOT_OK(mod.prepare(doc.root(), "", &execInfo));
+}
+
+TEST(SimpleMod, ApplyAndLogEmptyDocumentAnd) {
+ Document doc(fromjson("{}"));
+ Mod mod(fromjson("{ $bit : { a : { and : 1 } } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : 0 }"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : 0 } }"), logDoc);
+}
+
+TEST(SimpleMod, ApplyAndLogEmptyDocumentOr) {
+ Document doc(fromjson("{}"));
+ Mod mod(fromjson("{ $bit : { a : { or : 1 } } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : 1 }"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : 1 } }"), logDoc);
+}
+
+TEST(SimpleMod, ApplyAndLogEmptyDocumentXor) {
+ Document doc(fromjson("{}"));
+ Mod mod(fromjson("{ $bit : { a : { xor : 1 } } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : 1 }"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : 1 } }"), logDoc);
+}
+
+TEST(SimpleMod, ApplyAndLogSimpleDocumentAnd) {
+ Document doc(fromjson("{ a : 5 }"));
+ Mod mod(fromjson("{ $bit : { a : { and : 6 } } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : 4 }"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : 4 } }"), logDoc);
+}
+
+TEST(SimpleMod, ApplyAndLogSimpleDocumentOr) {
+ Document doc(fromjson("{ a : 5 }"));
+ Mod mod(fromjson("{ $bit : { a : { or : 6 } } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : 7 }"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : 7 } }"), logDoc);
+}
+
+TEST(SimpleMod, ApplyAndLogSimpleDocumentXor) {
+ Document doc(fromjson("{ a : 5 }"));
+ Mod mod(fromjson("{ $bit : { a : { xor : 6 } } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : 3 }"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : 3 } }"), logDoc);
+}
+
+TEST(InPlace, IntToIntAndIsInPlace) {
+ Document doc(BSON("a" << static_cast<int>(1)));
+ Mod mod(BSON("$bit" << BSON("a" << BSON("and" << static_cast<int>(1)))));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.noOp);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<int>(1))), logDoc);
+}
+
+TEST(InPlace, IntToIntOrIsInPlace) {
+ Document doc(BSON("a" << static_cast<int>(1)));
+ Mod mod(BSON("$bit" << BSON("a" << BSON("or" << static_cast<int>(1)))));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.noOp);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<int>(1))), logDoc);
+}
+
+TEST(InPlace, IntToIntXorIsInPlace) {
+ Document doc(BSON("a" << static_cast<int>(1)));
+ Mod mod(BSON("$bit" << BSON("a" << BSON("xor" << static_cast<int>(1)))));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<int>(0))), logDoc);
+}
+
+TEST(InPlace, LongToLongAndIsInPlace) {
+ Document doc(BSON("a" << static_cast<long long>(1)));
+ Mod mod(BSON("$bit" << BSON("a" << BSON("and" << static_cast<long long>(1)))));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.noOp);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<long long>(1))), logDoc);
+}
+
+TEST(InPlace, LongToLongOrIsInPlace) {
+ Document doc(BSON("a" << static_cast<long long>(1)));
+ Mod mod(BSON("$bit" << BSON("a" << BSON("or" << static_cast<long long>(1)))));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.noOp);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<long long>(1))), logDoc);
+}
+
+TEST(InPlace, LongToLongXorIsInPlace) {
+ Document doc(BSON("a" << static_cast<long long>(1)));
+ Mod mod(BSON("$bit" << BSON("a" << BSON("xor" << static_cast<long long>(1)))));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<long long>(0))), logDoc);
+}
+
+TEST(InPlace, IntToLongAndIsNotInPlace) {
+ Document doc(BSON("a" << static_cast<int>(1)));
+ Mod mod(BSON("$bit" << BSON("a" << BSON("and" << static_cast<long long>(1)))));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+}
+
+TEST(InPlace, IntToLongOrIsNotInPlace) {
+ Document doc(BSON("a" << static_cast<int>(1)));
+ Mod mod(BSON("$bit" << BSON("a" << BSON("or" << static_cast<long long>(1)))));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+}
+
+TEST(InPlace, IntToLongXorIsNotInPlace) {
+ Document doc(BSON("a" << static_cast<int>(1)));
+ Mod mod(BSON("$bit" << BSON("a" << BSON("xor" << static_cast<long long>(1)))));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+}
+
+TEST(NoOp, IntAnd) {
+ Document doc(BSON("a" << static_cast<int>(0xABCD1234U)));
+ Mod mod(BSON("$bit" << BSON("a" << BSON("and" << static_cast<int>(0xFFFFFFFFU)))));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.noOp);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<int>(0xABCD1234U))), logDoc);
+}
+
+TEST(NoOp, IntOr) {
+ Document doc(BSON("a" << static_cast<int>(0xABCD1234U)));
+ Mod mod(BSON("$bit" << BSON("a" << BSON("or" << static_cast<int>(0x0U)))));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.noOp);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<int>(0xABCD1234U))), logDoc);
+}
+
+TEST(NoOp, IntXor) {
+ Document doc(BSON("a" << static_cast<int>(0xABCD1234U)));
+ Mod mod(BSON("$bit" << BSON("a" << BSON("xor" << static_cast<int>(0x0U)))));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.noOp);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<int>(0xABCD1234U))), logDoc);
+}
+
+TEST(NoOp, LongAnd) {
+ Document doc(BSON("a" << static_cast<long long>(0xABCD1234EF981234ULL)));
+ Mod mod(
+ BSON("$bit" << BSON("a" << BSON("and" << static_cast<long long>(0xFFFFFFFFFFFFFFFFULL)))));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.noOp);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<long long>(0xABCD1234EF981234ULL))),
+ logDoc);
+}
+
+TEST(NoOp, LongOr) {
+ Document doc(BSON("a" << static_cast<long long>(0xABCD1234EF981234ULL)));
+ Mod mod(BSON("$bit" << BSON("a" << BSON("or" << static_cast<long long>(0x0ULL)))));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.noOp);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<long long>(0xABCD1234EF981234ULL))),
+ logDoc);
+}
+
+TEST(NoOp, LongXor) {
+ Document doc(BSON("a" << static_cast<long long>(0xABCD1234EF981234ULL)));
+ Mod mod(BSON("$bit" << BSON("a" << BSON("xor" << static_cast<long long>(0x0ULL)))));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.noOp);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<long long>(0xABCD1234EF981234ULL))),
+ logDoc);
+}
+
+TEST(Upcasting, UpcastIntToLongAnd) {
+ Document doc(BSON("a" << static_cast<int>(1)));
+ Mod mod(BSON("$bit" << BSON("a" << BSON("and" << static_cast<long long>(1)))));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : 1 }"), doc);
+ ASSERT_EQUALS(mongo::NumberLong, doc.root()["a"].getType());
+}
+
+TEST(Upcasting, UpcastIntToLongOr) {
+ Document doc(BSON("a" << static_cast<int>(1)));
+ Mod mod(BSON("$bit" << BSON("a" << BSON("or" << static_cast<long long>(1)))));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : 1 }"), doc);
+ ASSERT_EQUALS(mongo::NumberLong, doc.root()["a"].getType());
+}
+
+TEST(Upcasting, UpcastIntToLongXor) {
+ Document doc(BSON("a" << static_cast<int>(1)));
+ Mod mod(BSON("$bit" << BSON("a" << BSON("xor" << static_cast<long long>(0)))));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : 1 }"), doc);
+ ASSERT_EQUALS(mongo::NumberLong, doc.root()["a"].getType());
+}
+
+TEST(Upcasting, LongsStayLongsAnd) {
+ Document doc(BSON("a" << static_cast<long long>(1)));
+ Mod mod(BSON("$bit" << BSON("a" << BSON("and" << static_cast<int>(2)))));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : 0 }"), doc);
+ ASSERT_EQUALS(mongo::NumberLong, doc.root()["a"].getType());
+}
+
+TEST(Upcasting, LongsStayLongsOr) {
+ Document doc(BSON("a" << static_cast<long long>(1)));
+ Mod mod(BSON("$bit" << BSON("a" << BSON("or" << static_cast<int>(2)))));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : 3 }"), doc);
+ ASSERT_EQUALS(mongo::NumberLong, doc.root()["a"].getType());
+}
+
+TEST(Upcasting, LongsStayLongsXor) {
+ Document doc(BSON("a" << static_cast<long long>(1)));
+ Mod mod(BSON("$bit" << BSON("a" << BSON("xor" << static_cast<int>(1)))));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : 0 }"), doc);
+ ASSERT_EQUALS(mongo::NumberLong, doc.root()["a"].getType());
+}
+
+// The following tests are re-created from the previous $bit tests in updatetests.cpp. They
+// are probably redundant with the tests above in various ways.
+
+TEST(DbUpdateTests, BitRewriteExistingField) {
+ Document doc(BSON("a" << static_cast<int>(0)));
+ Mod mod(BSON("$bit" << BSON("a" << BSON("or" << static_cast<int>(1)))));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(BSON("a" << static_cast<int>(1)), doc);
+ ASSERT_EQUALS(mongo::NumberInt, doc.root()["a"].getType());
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<int>(1))), logDoc);
+}
+
+TEST(DbUpdateTests, BitRewriteNonExistingField) {
+ Document doc(BSON("a" << static_cast<int>(0)));
+ Mod mod(BSON("$bit" << BSON("b" << BSON("or" << static_cast<int>(1)))));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(BSON("a" << static_cast<int>(0) << "b" << static_cast<int>(1)), doc);
+ ASSERT_EQUALS(mongo::NumberInt, doc.root()["a"].getType());
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(BSON("$set" << BSON("b" << static_cast<int>(1))), logDoc);
+}
+
+TEST(DbUpdateTests, Bit1_1) {
+ Document doc(BSON("_id" << 1 << "x" << 3));
+ Mod mod(BSON("$bit" << BSON("x" << BSON("and" << 2))));
+ const BSONObj result(BSON("_id" << 1 << "x" << (3 & 2)));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ if (!execInfo.noOp)
ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 1 }"), doc);
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 1 } }"), logDoc);
- }
+ ASSERT_EQUALS(result, doc);
- TEST(SimpleMod, ApplyAndLogSimpleDocumentAnd) {
- Document doc(fromjson("{ a : 5 }"));
- Mod mod(fromjson("{ $bit : { a : { and : 6 } } }"));
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(BSON("$set" << BSON("x" << (3 & 2))), logDoc);
+}
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
+TEST(DbUpdateTests, Bit1_2) {
+ Document doc(BSON("_id" << 1 << "x" << 1));
+ Mod mod(BSON("$bit" << BSON("x" << BSON("or" << 4))));
+ const BSONObj result(BSON("_id" << 1 << "x" << (1 | 4)));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ if (!execInfo.noOp)
ASSERT_OK(mod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 4 }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 4 } }"), logDoc);
- }
-
- TEST(SimpleMod, ApplyAndLogSimpleDocumentOr) {
- Document doc(fromjson("{ a : 5 }"));
- Mod mod(fromjson("{ $bit : { a : { or : 6 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
+ ASSERT_EQUALS(result, doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(BSON("$set" << BSON("x" << (1 | 4))), logDoc);
+}
+
+TEST(DbUpdateTests, Bit1_3) {
+ Document doc(BSON("_id" << 1 << "x" << 3));
+ Mod mod1(BSON("$bit" << BSON("x" << BSON("and" << 2))));
+ Mod mod2(BSON("$bit" << BSON("x" << BSON("or" << 8))));
+ const BSONObj result(BSON("_id" << 1 << "x" << ((3 & 2) | 8)));
+
+ ModifierInterface::ExecInfo execInfo1;
+ ASSERT_OK(mod1.prepare(doc.root(), "", &execInfo1));
+ if (!execInfo1.noOp)
+ ASSERT_OK(mod1.apply());
+
+ ModifierInterface::ExecInfo execInfo2;
+ ASSERT_OK(mod2.prepare(doc.root(), "", &execInfo2));
+ if (!execInfo2.noOp)
+ ASSERT_OK(mod2.apply());
+
+ ASSERT_EQUALS(result, doc);
+}
+
+TEST(DbUpdateTests, Bit1_3_Combined) {
+ Document doc(BSON("_id" << 1 << "x" << 3));
+ Mod mod(BSON("$bit" << BSON("x" << BSON("and" << 2 << "or" << 8))));
+ const BSONObj result(BSON("_id" << 1 << "x" << ((3 & 2) | 8)));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ if (!execInfo.noOp)
ASSERT_OK(mod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 7 }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 7 } }"), logDoc);
- }
-
- TEST(SimpleMod, ApplyAndLogSimpleDocumentXor) {
- Document doc(fromjson("{ a : 5 }"));
- Mod mod(fromjson("{ $bit : { a : { xor : 6 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
+ ASSERT_EQUALS(result, doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(BSON("$set" << BSON("x" << ((3 & 2) | 8))), logDoc);
+}
+
+TEST(DbUpdateTests, Bit1_4) {
+ Document doc(BSON("_id" << 1 << "x" << 3));
+ Mod mod1(BSON("$bit" << BSON("x" << BSON("or" << 2))));
+ Mod mod2(BSON("$bit" << BSON("x" << BSON("and" << 8))));
+ const BSONObj result(BSON("_id" << 1 << "x" << ((3 | 2) & 8)));
+
+ ModifierInterface::ExecInfo execInfo1;
+ ASSERT_OK(mod1.prepare(doc.root(), "", &execInfo1));
+ if (!execInfo1.noOp)
+ ASSERT_OK(mod1.apply());
+
+ ModifierInterface::ExecInfo execInfo2;
+ ASSERT_OK(mod2.prepare(doc.root(), "", &execInfo2));
+ if (!execInfo2.noOp)
+ ASSERT_OK(mod2.apply());
+
+ ASSERT_EQUALS(result, doc);
+}
+
+TEST(DbUpdateTests, Bit1_4_Combined) {
+ Document doc(BSON("_id" << 1 << "x" << 3));
+ Mod mod(BSON("$bit" << BSON("x" << BSON("or" << 2 << "and" << 8))));
+ const BSONObj result(BSON("_id" << 1 << "x" << ((3 | 2) & 8)));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ if (!execInfo.noOp)
ASSERT_OK(mod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 3 }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 3 } }"), logDoc);
- }
-
- TEST(InPlace, IntToIntAndIsInPlace) {
- Document doc(BSON("a" << static_cast<int>(1)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("and" << static_cast<int>(1)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<int>(1))), logDoc);
- }
-
- TEST(InPlace, IntToIntOrIsInPlace) {
- Document doc(BSON("a" << static_cast<int>(1)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("or" << static_cast<int>(1)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<int>(1))), logDoc);
- }
-
- TEST(InPlace, IntToIntXorIsInPlace) {
- Document doc(BSON("a" << static_cast<int>(1)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("xor" << static_cast<int>(1)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<int>(0))), logDoc);
- }
-
- TEST(InPlace, LongToLongAndIsInPlace) {
- Document doc(BSON("a" << static_cast<long long>(1)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("and" << static_cast<long long>(1)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<long long>(1))), logDoc);
- }
-
- TEST(InPlace, LongToLongOrIsInPlace) {
- Document doc(BSON("a" << static_cast<long long>(1)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("or" << static_cast<long long>(1)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<long long>(1))), logDoc);
- }
-
- TEST(InPlace, LongToLongXorIsInPlace) {
- Document doc(BSON("a" << static_cast<long long>(1)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("xor" << static_cast<long long>(1)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<long long>(0))), logDoc);
- }
-
- TEST(InPlace, IntToLongAndIsNotInPlace) {
- Document doc(BSON("a" << static_cast<int>(1)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("and" << static_cast<long long>(1)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- }
-
- TEST(InPlace, IntToLongOrIsNotInPlace) {
- Document doc(BSON("a" << static_cast<int>(1)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("or" << static_cast<long long>(1)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- }
-
- TEST(InPlace, IntToLongXorIsNotInPlace) {
- Document doc(BSON("a" << static_cast<int>(1)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("xor" << static_cast<long long>(1)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- }
-
- TEST(NoOp, IntAnd) {
- Document doc(BSON("a" << static_cast<int>(0xABCD1234U)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("and" << static_cast<int>(0xFFFFFFFFU)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<int>(0xABCD1234U))), logDoc);
- }
-
- TEST(NoOp, IntOr) {
- Document doc(BSON("a" << static_cast<int>(0xABCD1234U)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("or" << static_cast<int>(0x0U)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<int>(0xABCD1234U))), logDoc);
- }
-
- TEST(NoOp, IntXor) {
- Document doc(BSON("a" << static_cast<int>(0xABCD1234U)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("xor" << static_cast<int>(0x0U)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<int>(0xABCD1234U))), logDoc);
- }
-
- TEST(NoOp, LongAnd) {
- Document doc(BSON("a" << static_cast<long long>(0xABCD1234EF981234ULL)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("and" <<
- static_cast<long long>(0xFFFFFFFFFFFFFFFFULL)))));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("a" <<
- static_cast<long long>(0xABCD1234EF981234ULL))), logDoc);
- }
-
- TEST(NoOp, LongOr) {
- Document doc(BSON("a" << static_cast<long long>(0xABCD1234EF981234ULL)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("or" << static_cast<long long>(0x0ULL)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("a" <<
- static_cast<long long>(0xABCD1234EF981234ULL))), logDoc);
- }
- TEST(NoOp, LongXor) {
- Document doc(BSON("a" << static_cast<long long>(0xABCD1234EF981234ULL)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("xor" << static_cast<long long>(0x0ULL)))));
+ ASSERT_EQUALS(result, doc);
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("a" <<
- static_cast<long long>(0xABCD1234EF981234ULL))), logDoc);
- }
-
- TEST(Upcasting, UpcastIntToLongAnd) {
- Document doc(BSON("a" << static_cast<int>(1)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("and" << static_cast<long long>(1)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 1 }"), doc);
- ASSERT_EQUALS(mongo::NumberLong, doc.root()["a"].getType());
- }
-
- TEST(Upcasting, UpcastIntToLongOr) {
- Document doc(BSON("a" << static_cast<int>(1)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("or" << static_cast<long long>(1)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 1 }"), doc);
- ASSERT_EQUALS(mongo::NumberLong, doc.root()["a"].getType());
- }
-
- TEST(Upcasting, UpcastIntToLongXor) {
- Document doc(BSON("a" << static_cast<int>(1)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("xor" << static_cast<long long>(0)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 1 }"), doc);
- ASSERT_EQUALS(mongo::NumberLong, doc.root()["a"].getType());
- }
-
- TEST(Upcasting, LongsStayLongsAnd) {
- Document doc(BSON("a" << static_cast<long long>(1)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("and" << static_cast<int>(2)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 0 }"), doc);
- ASSERT_EQUALS(mongo::NumberLong, doc.root()["a"].getType());
- }
-
- TEST(Upcasting, LongsStayLongsOr) {
- Document doc(BSON("a" << static_cast<long long>(1)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("or" << static_cast<int>(2)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 3 }"), doc);
- ASSERT_EQUALS(mongo::NumberLong, doc.root()["a"].getType());
- }
-
- TEST(Upcasting, LongsStayLongsXor) {
- Document doc(BSON("a" << static_cast<long long>(1)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("xor" << static_cast<int>(1)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 0 }"), doc);
- ASSERT_EQUALS(mongo::NumberLong, doc.root()["a"].getType());
- }
-
- // The following tests are re-created from the previous $bit tests in updatetests.cpp. They
- // are probably redundant with the tests above in various ways.
-
- TEST(DbUpdateTests, BitRewriteExistingField) {
- Document doc(BSON("a" << static_cast<int>(0)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("or" << static_cast<int>(1)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(BSON("a" << static_cast<int>(1)), doc);
- ASSERT_EQUALS(mongo::NumberInt, doc.root()["a"].getType());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<int>(1))), logDoc);
- }
-
- TEST(DbUpdateTests, BitRewriteNonExistingField) {
- Document doc(BSON("a" << static_cast<int>(0)));
- Mod mod(BSON("$bit" << BSON("b" << BSON("or" << static_cast<int>(1)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(BSON("a" << static_cast<int>(0) << "b" << static_cast<int>(1)), doc);
- ASSERT_EQUALS(mongo::NumberInt, doc.root()["a"].getType());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("b" << static_cast<int>(1))), logDoc);
- }
-
- TEST(DbUpdateTests, Bit1_1) {
- Document doc(BSON("_id" << 1 << "x" << 3));
- Mod mod(BSON("$bit" << BSON("x" << BSON("and" << 2))));
- const BSONObj result(BSON("_id" << 1 << "x" << (3 & 2)));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- if (!execInfo.noOp)
- ASSERT_OK(mod.apply());
-
- ASSERT_EQUALS(result, doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("x" << (3 & 2))), logDoc);
- }
-
- TEST(DbUpdateTests, Bit1_2) {
- Document doc(BSON("_id" << 1 << "x" << 1));
- Mod mod(BSON("$bit" << BSON("x" << BSON("or" << 4))));
- const BSONObj result(BSON("_id" << 1 << "x" << (1 | 4)));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- if (!execInfo.noOp)
- ASSERT_OK(mod.apply());
-
- ASSERT_EQUALS(result, doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("x" << (1 | 4))), logDoc);
- }
-
- TEST(DbUpdateTests, Bit1_3) {
- Document doc(BSON("_id" << 1 << "x" << 3));
- Mod mod1(BSON("$bit" << BSON("x" << BSON("and" << 2))));
- Mod mod2(BSON("$bit" << BSON("x" << BSON("or" << 8))));
- const BSONObj result(BSON("_id" << 1 << "x" << ((3 & 2) | 8)));
-
- ModifierInterface::ExecInfo execInfo1;
- ASSERT_OK(mod1.prepare(doc.root(), "", &execInfo1));
- if (!execInfo1.noOp)
- ASSERT_OK(mod1.apply());
-
- ModifierInterface::ExecInfo execInfo2;
- ASSERT_OK(mod2.prepare(doc.root(), "", &execInfo2));
- if (!execInfo2.noOp)
- ASSERT_OK(mod2.apply());
-
- ASSERT_EQUALS(result, doc);
- }
-
- TEST(DbUpdateTests, Bit1_3_Combined) {
- Document doc(BSON("_id" << 1 << "x" << 3));
- Mod mod(BSON("$bit" << BSON("x" << BSON("and" << 2 << "or" << 8))));
- const BSONObj result(BSON("_id" << 1 << "x" << ((3 & 2) | 8)));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- if (!execInfo.noOp)
- ASSERT_OK(mod.apply());
-
- ASSERT_EQUALS(result, doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("x" << ((3 & 2) | 8))), logDoc);
- }
-
- TEST(DbUpdateTests, Bit1_4) {
- Document doc(BSON("_id" << 1 << "x" << 3));
- Mod mod1(BSON("$bit" << BSON("x" << BSON("or" << 2))));
- Mod mod2(BSON("$bit" << BSON("x" << BSON("and" << 8))));
- const BSONObj result(BSON("_id" << 1 << "x" << ((3 | 2) & 8)));
-
- ModifierInterface::ExecInfo execInfo1;
- ASSERT_OK(mod1.prepare(doc.root(), "", &execInfo1));
- if (!execInfo1.noOp)
- ASSERT_OK(mod1.apply());
-
- ModifierInterface::ExecInfo execInfo2;
- ASSERT_OK(mod2.prepare(doc.root(), "", &execInfo2));
- if (!execInfo2.noOp)
- ASSERT_OK(mod2.apply());
-
- ASSERT_EQUALS(result, doc);
- }
-
- TEST(DbUpdateTests, Bit1_4_Combined) {
- Document doc(BSON("_id" << 1 << "x" << 3));
- Mod mod(BSON("$bit" << BSON("x" << BSON("or" << 2 << "and" << 8))));
- const BSONObj result(BSON("_id" << 1 << "x" << ((3 | 2) & 8)));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- if (!execInfo.noOp)
- ASSERT_OK(mod.apply());
-
- ASSERT_EQUALS(result, doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("x" << ((3 | 2) & 8))), logDoc);
- }
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(BSON("$set" << BSON("x" << ((3 | 2) & 8))), logDoc);
+}
-} // namespace
+} // namespace
diff --git a/src/mongo/db/ops/modifier_compare.cpp b/src/mongo/db/ops/modifier_compare.cpp
index 6d37e4b2797..36f800202e4 100644
--- a/src/mongo/db/ops/modifier_compare.cpp
+++ b/src/mongo/db/ops/modifier_compare.cpp
@@ -37,156 +37,137 @@
namespace mongo {
- namespace str = mongoutils::str;
+namespace str = mongoutils::str;
- struct ModifierCompare::PreparedState {
+struct ModifierCompare::PreparedState {
+ PreparedState(mutablebson::Document& targetDoc)
+ : doc(targetDoc), idxFound(0), elemFound(doc.end()) {}
- PreparedState(mutablebson::Document& targetDoc)
- : doc(targetDoc)
- , idxFound(0)
- , elemFound(doc.end()) {
- }
+ // Document that is going to be changed.
+ mutablebson::Document& doc;
- // Document that is going to be changed.
- mutablebson::Document& doc;
+ // Index in _fieldRef for which an Element exist in the document.
+ size_t idxFound;
- // Index in _fieldRef for which an Element exist in the document.
- size_t idxFound;
+ // Element corresponding to _fieldRef[0.._idxFound].
+ mutablebson::Element elemFound;
+};
- // Element corresponding to _fieldRef[0.._idxFound].
- mutablebson::Element elemFound;
- };
+ModifierCompare::ModifierCompare(ModifierCompare::ModifierCompareMode mode)
+ : _mode(mode), _pathReplacementPosition(0) {}
- ModifierCompare::ModifierCompare(ModifierCompare::ModifierCompareMode mode)
- : _mode(mode)
- , _pathReplacementPosition(0) {
- }
+ModifierCompare::~ModifierCompare() {}
- ModifierCompare::~ModifierCompare() {
+Status ModifierCompare::init(const BSONElement& modExpr, const Options& opts, bool* positional) {
+ _updatePath.parse(modExpr.fieldName());
+ Status status = fieldchecker::isUpdatable(_updatePath);
+ if (!status.isOK()) {
+ return status;
}
- Status ModifierCompare::init(const BSONElement& modExpr, const Options& opts,
- bool* positional) {
-
- _updatePath.parse(modExpr.fieldName());
- Status status = fieldchecker::isUpdatable(_updatePath);
- if (!status.isOK()) {
- return status;
- }
+ // If a $-positional operator was used, get the index in which it occurred
+ // and ensure only one occurrence.
+ size_t foundCount;
+ bool foundDollar =
+ fieldchecker::isPositional(_updatePath, &_pathReplacementPosition, &foundCount);
- // If a $-positional operator was used, get the index in which it occurred
- // and ensure only one occurrence.
- size_t foundCount;
- bool foundDollar = fieldchecker::isPositional(
- _updatePath, &_pathReplacementPosition, &foundCount);
-
- if (positional)
- *positional = foundDollar;
-
- if (foundDollar && foundCount > 1) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "Too many positional (i.e. '$') elements found in path '"
- << _updatePath.dottedField() << "'");
- }
+ if (positional)
+ *positional = foundDollar;
- // Store value for later.
- _val = modExpr;
- return Status::OK();
+ if (foundDollar && foundCount > 1) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "Too many positional (i.e. '$') elements found in path '"
+ << _updatePath.dottedField() << "'");
}
- Status ModifierCompare::prepare(mutablebson::Element root,
+ // Store value for later.
+ _val = modExpr;
+ return Status::OK();
+}
+
+Status ModifierCompare::prepare(mutablebson::Element root,
StringData matchedField,
ExecInfo* execInfo) {
+ _preparedState.reset(new PreparedState(root.getDocument()));
- _preparedState.reset(new PreparedState(root.getDocument()));
-
- // If we have a $-positional field, it is time to bind it to an actual field part.
- if (_pathReplacementPosition) {
- if (matchedField.empty()) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "The positional operator did not find the match "
- "needed from the query. Unexpanded update: "
- << _updatePath.dottedField());
- }
- _updatePath.setPart(_pathReplacementPosition, matchedField);
- }
-
- // Locate the field name in 'root'. Note that we may not have all the parts in the path
- // in the doc -- which is fine. Our goal now is merely to reason about whether this mod
- // apply is a noOp or whether is can be in place. The remaining path, if missing, will
- // be created during the apply.
- Status status = pathsupport::findLongestPrefix(_updatePath,
- root,
- &_preparedState->idxFound,
- &_preparedState->elemFound);
-
- // FindLongestPrefix may say the path does not exist at all, which is fine here, or
- // that the path was not viable or otherwise wrong, in which case, the mod cannot
- // proceed.
- if (status.code() == ErrorCodes::NonExistentPath) {
- _preparedState->elemFound = root.getDocument().end();
- }
- else if (!status.isOK()) {
- return status;
- }
-
- // We register interest in the field name. The driver needs this info to sort out if
- // there is any conflict among mods.
- execInfo->fieldRef[0] = &_updatePath;
-
- const bool destExists = (_preparedState->elemFound.ok() &&
- _preparedState->idxFound == (_updatePath.numParts() - 1));
- if (!destExists) {
- execInfo->noOp = false;
- }
- else {
- const int compareVal = _preparedState->elemFound.compareWithBSONElement(_val, false);
- execInfo->noOp = (compareVal == 0) ||
- ((_mode == ModifierCompare::MAX) ?
- (compareVal > 0) : (compareVal < 0));
+ // If we have a $-positional field, it is time to bind it to an actual field part.
+ if (_pathReplacementPosition) {
+ if (matchedField.empty()) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "The positional operator did not find the match "
+ "needed from the query. Unexpanded update: "
+ << _updatePath.dottedField());
}
+ _updatePath.setPart(_pathReplacementPosition, matchedField);
+ }
- return Status::OK();
+ // Locate the field name in 'root'. Note that we may not have all the parts in the path
+ // in the doc -- which is fine. Our goal now is merely to reason about whether this mod
+ // apply is a noOp or whether is can be in place. The remaining path, if missing, will
+ // be created during the apply.
+ Status status = pathsupport::findLongestPrefix(
+ _updatePath, root, &_preparedState->idxFound, &_preparedState->elemFound);
+
+ // FindLongestPrefix may say the path does not exist at all, which is fine here, or
+ // that the path was not viable or otherwise wrong, in which case, the mod cannot
+ // proceed.
+ if (status.code() == ErrorCodes::NonExistentPath) {
+ _preparedState->elemFound = root.getDocument().end();
+ } else if (!status.isOK()) {
+ return status;
}
- Status ModifierCompare::apply() const {
+ // We register interest in the field name. The driver needs this info to sort out if
+ // there is any conflict among mods.
+ execInfo->fieldRef[0] = &_updatePath;
+
+ const bool destExists = (_preparedState->elemFound.ok() &&
+ _preparedState->idxFound == (_updatePath.numParts() - 1));
+ if (!destExists) {
+ execInfo->noOp = false;
+ } else {
+ const int compareVal = _preparedState->elemFound.compareWithBSONElement(_val, false);
+ execInfo->noOp = (compareVal == 0) ||
+ ((_mode == ModifierCompare::MAX) ? (compareVal > 0) : (compareVal < 0));
+ }
- const bool destExists = (_preparedState->elemFound.ok() &&
- _preparedState->idxFound == (_updatePath.numParts() - 1));
- // If there's no need to create any further field part, the $set is simply a value
- // assignment.
- if (destExists) {
- return _preparedState->elemFound.setValueBSONElement(_val);
- }
+ return Status::OK();
+}
- mutablebson::Document& doc = _preparedState->doc;
- StringData lastPart = _updatePath.getPart(_updatePath.numParts() - 1);
- // If the element exists and is the same type, then that is what we want to work with
- mutablebson::Element elemToSet = doc.makeElementWithNewFieldName(lastPart, _val);
- if (!elemToSet.ok()) {
- return Status(ErrorCodes::InternalError, "can't create new element");
- }
-
- // Now, we can be in two cases here, as far as attaching the element being set goes:
- // (a) none of the parts in the element's path exist, or (b) some parts of the path
- // exist but not all.
- if (!_preparedState->elemFound.ok()) {
- _preparedState->elemFound = doc.root();
- _preparedState->idxFound = 0;
- }
- else {
- _preparedState->idxFound++;
- }
+Status ModifierCompare::apply() const {
+ const bool destExists = (_preparedState->elemFound.ok() &&
+ _preparedState->idxFound == (_updatePath.numParts() - 1));
+ // If there's no need to create any further field part, the $set is simply a value
+ // assignment.
+ if (destExists) {
+ return _preparedState->elemFound.setValueBSONElement(_val);
+ }
- // createPathAt() will complete the path and attach 'elemToSet' at the end of it.
- return pathsupport::createPathAt(_updatePath,
- _preparedState->idxFound,
- _preparedState->elemFound,
- elemToSet);
+ mutablebson::Document& doc = _preparedState->doc;
+ StringData lastPart = _updatePath.getPart(_updatePath.numParts() - 1);
+ // If the element exists and is the same type, then that is what we want to work with
+ mutablebson::Element elemToSet = doc.makeElementWithNewFieldName(lastPart, _val);
+ if (!elemToSet.ok()) {
+ return Status(ErrorCodes::InternalError, "can't create new element");
}
- Status ModifierCompare::log(LogBuilder* logBuilder) const {
- return logBuilder->addToSetsWithNewFieldName(_updatePath.dottedField(), _val);
+ // Now, we can be in two cases here, as far as attaching the element being set goes:
+ // (a) none of the parts in the element's path exist, or (b) some parts of the path
+ // exist but not all.
+ if (!_preparedState->elemFound.ok()) {
+ _preparedState->elemFound = doc.root();
+ _preparedState->idxFound = 0;
+ } else {
+ _preparedState->idxFound++;
}
-} // namespace mongo
+ // createPathAt() will complete the path and attach 'elemToSet' at the end of it.
+ return pathsupport::createPathAt(
+ _updatePath, _preparedState->idxFound, _preparedState->elemFound, elemToSet);
+}
+
+Status ModifierCompare::log(LogBuilder* logBuilder) const {
+ return logBuilder->addToSetsWithNewFieldName(_updatePath.dottedField(), _val);
+}
+
+} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_compare.h b/src/mongo/db/ops/modifier_compare.h
index 8285f3e10ad..f0be887d136 100644
--- a/src/mongo/db/ops/modifier_compare.h
+++ b/src/mongo/db/ops/modifier_compare.h
@@ -38,73 +38,68 @@
namespace mongo {
- class LogBuilder;
-
- class ModifierCompare : public ModifierInterface {
- MONGO_DISALLOW_COPYING(ModifierCompare);
-
- public:
-
- enum ModifierCompareMode { MAX, MIN };
- explicit ModifierCompare(ModifierCompareMode mode = MAX);
-
- virtual ~ModifierCompare();
-
- //
- // Modifier interface implementation
- //
-
- /**
- * A 'modExpr' is a BSONElement {<fieldname>: <value>} coming from a $set mod such as
- * {$set: {<fieldname: <value>}}. init() extracts the field name and the value to be
- * assigned to it from 'modExpr'. It returns OK if successful or a status describing
- * the error.
- */
- virtual Status init(const BSONElement& modExpr, const Options& opts,
- bool* positional = NULL);
-
- /**
- * Looks up the field name in the sub-tree rooted at 'root', and binds, if necessary,
- * the '$' field part using the 'matchedfield' number. prepare() returns OK and
- * fills in 'execInfo' with information of whether this mod is a no-op on 'root' and
- * whether it is an in-place candidate. Otherwise, returns a status describing the
- * error.
- */
- virtual Status prepare(mutablebson::Element root,
- StringData matchedField,
- ExecInfo* execInfo);
-
- /**
- * Applies the prepared mod over the element 'root' specified in the prepare()
- * call. Returns OK if successful or a status describing the error.
- */
- virtual Status apply() const;
-
- /**
- * Adds a log entry to logRoot corresponding to the operation applied here. Returns OK
- * if successful or a status describing the error.
- */
- virtual Status log(LogBuilder* logBuilder) const;
-
- private:
-
- // Compare mode: min/max
- const ModifierCompareMode _mode;
-
- // Access to each component of fieldName that's the target of this mod.
- FieldRef _updatePath;
-
- // 0 or index for $-positional in _updatePath.
- size_t _pathReplacementPosition;
-
- // Element of the mod expression.
- BSONElement _val;
-
- // The instance of the field in the provided doc. This state is valid after a
- // prepare() was issued and until a log() is issued. The document this mod is
- // being prepared against must be live throughout all the calls.
- struct PreparedState;
- std::unique_ptr<PreparedState> _preparedState;
- };
-
-} // namespace mongo
+class LogBuilder;
+
+class ModifierCompare : public ModifierInterface {
+ MONGO_DISALLOW_COPYING(ModifierCompare);
+
+public:
+ enum ModifierCompareMode { MAX, MIN };
+ explicit ModifierCompare(ModifierCompareMode mode = MAX);
+
+ virtual ~ModifierCompare();
+
+ //
+ // Modifier interface implementation
+ //
+
+ /**
+ * A 'modExpr' is a BSONElement {<fieldname>: <value>} coming from a $set mod such as
+ * {$set: {<fieldname: <value>}}. init() extracts the field name and the value to be
+ * assigned to it from 'modExpr'. It returns OK if successful or a status describing
+ * the error.
+ */
+ virtual Status init(const BSONElement& modExpr, const Options& opts, bool* positional = NULL);
+
+ /**
+ * Looks up the field name in the sub-tree rooted at 'root', and binds, if necessary,
+ * the '$' field part using the 'matchedfield' number. prepare() returns OK and
+ * fills in 'execInfo' with information of whether this mod is a no-op on 'root' and
+ * whether it is an in-place candidate. Otherwise, returns a status describing the
+ * error.
+ */
+ virtual Status prepare(mutablebson::Element root, StringData matchedField, ExecInfo* execInfo);
+
+ /**
+ * Applies the prepared mod over the element 'root' specified in the prepare()
+ * call. Returns OK if successful or a status describing the error.
+ */
+ virtual Status apply() const;
+
+ /**
+ * Adds a log entry to logRoot corresponding to the operation applied here. Returns OK
+ * if successful or a status describing the error.
+ */
+ virtual Status log(LogBuilder* logBuilder) const;
+
+private:
+ // Compare mode: min/max
+ const ModifierCompareMode _mode;
+
+ // Access to each component of fieldName that's the target of this mod.
+ FieldRef _updatePath;
+
+ // 0 or index for $-positional in _updatePath.
+ size_t _pathReplacementPosition;
+
+ // Element of the mod expression.
+ BSONElement _val;
+
+ // The instance of the field in the provided doc. This state is valid after a
+ // prepare() was issued and until a log() is issued. The document this mod is
+ // being prepared against must be live throughout all the calls.
+ struct PreparedState;
+ std::unique_ptr<PreparedState> _preparedState;
+};
+
+} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_compare_test.cpp b/src/mongo/db/ops/modifier_compare_test.cpp
index 9f19dd1d718..fb6bcf8d84f 100644
--- a/src/mongo/db/ops/modifier_compare_test.cpp
+++ b/src/mongo/db/ops/modifier_compare_test.cpp
@@ -39,257 +39,256 @@
namespace {
- using mongo::BSONObj;
- using mongo::LogBuilder;
- using mongo::ModifierCompare;
- using mongo::ModifierInterface;
- using mongo::Status;
- using mongo::StringData;
- using mongo::fromjson;
- using mongo::mutablebson::ConstElement;
- using mongo::mutablebson::Document;
- using mongo::mutablebson::Element;
-
- const char kModNameMin[] = "$min";
- const char kModNameMax[] = "$max";
-
- /** Helper to build and manipulate a $min/max mod. */
- class Mod {
- public:
- Mod() : _mod() {}
-
- explicit Mod(BSONObj modObj)
- : _modObj(modObj)
- , _mod((modObj.firstElement().fieldNameStringData() == "$min") ?
- ModifierCompare::MIN :
- ModifierCompare::MAX) {
- StringData modName = modObj.firstElement().fieldName();
- ASSERT_OK(_mod.init(modObj[modName].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
-
- 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);
- }
-
- ModifierCompare& mod() { return _mod; }
-
- private:
- BSONObj _modObj;
- ModifierCompare _mod;
- };
-
- TEST(Init, ValidValues) {
- BSONObj modObj;
- ModifierCompare mod;
-
- modObj = fromjson("{ $min : { a : 2 } }");
- ASSERT_OK(mod.init(modObj[kModNameMin].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
-
- modObj = fromjson("{ $max : { a : 1 } }");
- ASSERT_OK(mod.init(modObj[kModNameMax].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
-
- modObj = fromjson("{ $min : { a : {$date : 0 } } }");
- ASSERT_OK(mod.init(modObj[kModNameMin].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
+using mongo::BSONObj;
+using mongo::LogBuilder;
+using mongo::ModifierCompare;
+using mongo::ModifierInterface;
+using mongo::Status;
+using mongo::StringData;
+using mongo::fromjson;
+using mongo::mutablebson::ConstElement;
+using mongo::mutablebson::Document;
+using mongo::mutablebson::Element;
+
+const char kModNameMin[] = "$min";
+const char kModNameMax[] = "$max";
+
+/** Helper to build and manipulate a $min/max mod. */
+class Mod {
+public:
+ Mod() : _mod() {}
+
+ explicit Mod(BSONObj modObj)
+ : _modObj(modObj),
+ _mod((modObj.firstElement().fieldNameStringData() == "$min") ? ModifierCompare::MIN
+ : ModifierCompare::MAX) {
+ StringData modName = modObj.firstElement().fieldName();
+ ASSERT_OK(_mod.init(modObj[modName].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
}
- TEST(ExistingNumber, MaxSameNumber) {
- Document doc(fromjson("{a: 1 }"));
- Mod mod(fromjson("{$max: {a: 1} }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
- }
-
- TEST(ExistingNumber, MinSameNumber) {
- Document doc(fromjson("{a: 1 }"));
- Mod mod(fromjson("{$min: {a: 1} }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
- }
-
- TEST(ExistingNumber, MaxNumberIsLess) {
- Document doc(fromjson("{a: 1 }"));
- Mod mod(fromjson("{$max: {a: 0} }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
- }
-
- TEST(ExistingNumber, MinNumberIsMore) {
- Document doc(fromjson("{a: 1 }"));
- Mod mod(fromjson("{$min: {a: 2} }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
- }
-
- TEST(ExistingDouble, MaxSameValInt) {
- Document doc(fromjson("{a: 1.0 }"));
- Mod mod(BSON("$max" << BSON("a" << 1LL)));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
- }
-
- TEST(ExistingDoubleZero, MaxSameValIntZero) {
- Document doc(fromjson("{a: 0.0 }"));
- Mod mod(BSON("$max" << BSON("a" << 0LL)));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
- }
-
- TEST(ExistingDoubleZero, MinSameValIntZero) {
- Document doc(fromjson("{a: 0.0 }"));
- Mod mod(BSON("$min" << BSON("a" << 0LL)));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
- }
-
- TEST(MissingField, MinNumber) {
- Document doc(fromjson("{}"));
- Mod mod(fromjson("{$min: {a: 0} }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
-
- ASSERT_OK(mod.apply());
- ASSERT_EQUALS(fromjson("{a : 0}"), doc);
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 0 } }"), logDoc);
- }
-
- TEST(ExistingNumber, MinNumber) {
- Document doc(fromjson("{a: 1 }"));
- Mod mod(fromjson("{$min: {a: 0} }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
-
- ASSERT_OK(mod.apply());
- ASSERT_EQUALS(fromjson("{a : 0}"), doc);
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 0 } }"), logDoc);
+ Status prepare(Element root, StringData matchedField, ModifierInterface::ExecInfo* execInfo) {
+ return _mod.prepare(root, matchedField, execInfo);
}
- TEST(MissingField, MaxNumber) {
- Document doc(fromjson("{}"));
- Mod mod(fromjson("{$max: {a: 0} }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
-
- ASSERT_OK(mod.apply());
- ASSERT_EQUALS(fromjson("{a : 0}"), doc);
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 0 } }"), logDoc);
+ Status apply() const {
+ return _mod.apply();
}
- TEST(ExistingNumber, MaxNumber) {
- Document doc(fromjson("{a: 1 }"));
- Mod mod(fromjson("{$max: {a: 2} }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
-
- ASSERT_OK(mod.apply());
- ASSERT_EQUALS(fromjson("{a : 2}"), doc);
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 2 } }"), logDoc);
+ Status log(LogBuilder* logBuilder) const {
+ return _mod.log(logBuilder);
}
- TEST(ExistingDate, MaxDate) {
- Document doc(fromjson("{a: {$date: 0} }"));
- Mod mod(fromjson("{$max: {a: {$date: 123123123}} }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
-
- ASSERT_OK(mod.apply());
- ASSERT_EQUALS(fromjson("{a: {$date: 123123123}}"), doc);
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{$set: {a: {$date: 123123123}} }"), logDoc);
- }
-
- TEST(ExistingEmbeddedDoc, MaxDoc) {
- Document doc(fromjson("{a: {b: 2}}"));
- Mod mod(fromjson("{$max: {a: {b: 3}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
-
- ASSERT_OK(mod.apply());
- ASSERT_EQUALS(fromjson("{a: {b: 3}}}"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{$set: {a: {b: 3}} }"), logDoc);
- }
-
- TEST(ExistingEmbeddedDoc, MaxNumber) {
- Document doc(fromjson("{a: {b: 2}}"));
- Mod mod(fromjson("{$max: {a: 3}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
- ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
+ ModifierCompare& mod() {
+ return _mod;
}
-} // namespace
+private:
+ BSONObj _modObj;
+ ModifierCompare _mod;
+};
+
+TEST(Init, ValidValues) {
+ BSONObj modObj;
+ ModifierCompare mod;
+
+ modObj = fromjson("{ $min : { a : 2 } }");
+ ASSERT_OK(mod.init(modObj[kModNameMin].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+
+ modObj = fromjson("{ $max : { a : 1 } }");
+ ASSERT_OK(mod.init(modObj[kModNameMax].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+
+ modObj = fromjson("{ $min : { a : {$date : 0 } } }");
+ ASSERT_OK(mod.init(modObj[kModNameMin].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
+
+TEST(ExistingNumber, MaxSameNumber) {
+ Document doc(fromjson("{a: 1 }"));
+ Mod mod(fromjson("{$max: {a: 1} }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.noOp);
+}
+
+TEST(ExistingNumber, MinSameNumber) {
+ Document doc(fromjson("{a: 1 }"));
+ Mod mod(fromjson("{$min: {a: 1} }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.noOp);
+}
+
+TEST(ExistingNumber, MaxNumberIsLess) {
+ Document doc(fromjson("{a: 1 }"));
+ Mod mod(fromjson("{$max: {a: 0} }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.noOp);
+}
+
+TEST(ExistingNumber, MinNumberIsMore) {
+ Document doc(fromjson("{a: 1 }"));
+ Mod mod(fromjson("{$min: {a: 2} }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.noOp);
+}
+
+TEST(ExistingDouble, MaxSameValInt) {
+ Document doc(fromjson("{a: 1.0 }"));
+ Mod mod(BSON("$max" << BSON("a" << 1LL)));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.noOp);
+}
+
+TEST(ExistingDoubleZero, MaxSameValIntZero) {
+ Document doc(fromjson("{a: 0.0 }"));
+ Mod mod(BSON("$max" << BSON("a" << 0LL)));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.noOp);
+}
+
+TEST(ExistingDoubleZero, MinSameValIntZero) {
+ Document doc(fromjson("{a: 0.0 }"));
+ Mod mod(BSON("$min" << BSON("a" << 0LL)));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.noOp);
+}
+
+TEST(MissingField, MinNumber) {
+ Document doc(fromjson("{}"));
+ Mod mod(fromjson("{$min: {a: 0} }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+ ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
+
+ ASSERT_OK(mod.apply());
+ ASSERT_EQUALS(fromjson("{a : 0}"), doc);
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : 0 } }"), logDoc);
+}
+
+TEST(ExistingNumber, MinNumber) {
+ Document doc(fromjson("{a: 1 }"));
+ Mod mod(fromjson("{$min: {a: 0} }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+ ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
+
+ ASSERT_OK(mod.apply());
+ ASSERT_EQUALS(fromjson("{a : 0}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : 0 } }"), logDoc);
+}
+
+TEST(MissingField, MaxNumber) {
+ Document doc(fromjson("{}"));
+ Mod mod(fromjson("{$max: {a: 0} }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+ ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
+
+ ASSERT_OK(mod.apply());
+ ASSERT_EQUALS(fromjson("{a : 0}"), doc);
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : 0 } }"), logDoc);
+}
+
+TEST(ExistingNumber, MaxNumber) {
+ Document doc(fromjson("{a: 1 }"));
+ Mod mod(fromjson("{$max: {a: 2} }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+ ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
+
+ ASSERT_OK(mod.apply());
+ ASSERT_EQUALS(fromjson("{a : 2}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : 2 } }"), logDoc);
+}
+
+TEST(ExistingDate, MaxDate) {
+ Document doc(fromjson("{a: {$date: 0} }"));
+ Mod mod(fromjson("{$max: {a: {$date: 123123123}} }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+ ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
+
+ ASSERT_OK(mod.apply());
+ ASSERT_EQUALS(fromjson("{a: {$date: 123123123}}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{$set: {a: {$date: 123123123}} }"), logDoc);
+}
+
+TEST(ExistingEmbeddedDoc, MaxDoc) {
+ Document doc(fromjson("{a: {b: 2}}"));
+ Mod mod(fromjson("{$max: {a: {b: 3}}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+ ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
+
+ ASSERT_OK(mod.apply());
+ ASSERT_EQUALS(fromjson("{a: {b: 3}}}"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{$set: {a: {b: 3}} }"), logDoc);
+}
+
+TEST(ExistingEmbeddedDoc, MaxNumber) {
+ Document doc(fromjson("{a: {b: 2}}"));
+ Mod mod(fromjson("{$max: {a: 3}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.noOp);
+ ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
+}
+
+} // namespace
diff --git a/src/mongo/db/ops/modifier_current_date.cpp b/src/mongo/db/ops/modifier_current_date.cpp
index f38cc4e4441..75d0be014e3 100644
--- a/src/mongo/db/ops/modifier_current_date.cpp
+++ b/src/mongo/db/ops/modifier_current_date.cpp
@@ -38,239 +38,215 @@
namespace mongo {
- namespace str = mongoutils::str;
+namespace str = mongoutils::str;
- namespace {
- const char kType[] = "$type";
- const char kDate[] = "date";
- const char kTimestamp[] = "timestamp";
- }
+namespace {
+const char kType[] = "$type";
+const char kDate[] = "date";
+const char kTimestamp[] = "timestamp";
+}
- struct ModifierCurrentDate::PreparedState {
+struct ModifierCurrentDate::PreparedState {
+ PreparedState(mutablebson::Document& doc) : doc(doc), elemFound(doc.end()), idxFound(0) {}
- PreparedState(mutablebson::Document& doc)
- : doc(doc)
- , elemFound(doc.end())
- , idxFound(0) {
- }
+ // Document that is going to be changed.
+ mutablebson::Document& doc;
- // Document that is going to be changed.
- mutablebson::Document& doc;
+ // Element corresponding to _fieldRef[0.._idxFound].
+ mutablebson::Element elemFound;
- // Element corresponding to _fieldRef[0.._idxFound].
- mutablebson::Element elemFound;
+ // Index in _fieldRef for which an Element exist in the document.
+ size_t idxFound;
+};
- // Index in _fieldRef for which an Element exist in the document.
- size_t idxFound;
- };
+ModifierCurrentDate::ModifierCurrentDate() : _pathReplacementPosition(0), _typeIsDate(true) {}
- ModifierCurrentDate::ModifierCurrentDate()
- : _pathReplacementPosition(0)
- , _typeIsDate(true) {
- }
+ModifierCurrentDate::~ModifierCurrentDate() {}
- ModifierCurrentDate::~ModifierCurrentDate() {
+Status ModifierCurrentDate::init(const BSONElement& modExpr,
+ const Options& opts,
+ bool* positional) {
+ _updatePath.parse(modExpr.fieldName());
+ Status status = fieldchecker::isUpdatable(_updatePath);
+ if (!status.isOK()) {
+ return status;
}
- Status ModifierCurrentDate::init(const BSONElement& modExpr, const Options& opts,
- bool* positional) {
-
- _updatePath.parse(modExpr.fieldName());
- Status status = fieldchecker::isUpdatable(_updatePath);
- if (!status.isOK()) {
- return status;
- }
-
- // If a $-positional operator was used, get the index in which it occurred
- // and ensure only one occurrence.
- size_t foundCount;
- bool foundDollar = fieldchecker::isPositional(_updatePath,
- &_pathReplacementPosition,
- &foundCount);
+ // If a $-positional operator was used, get the index in which it occurred
+ // and ensure only one occurrence.
+ size_t foundCount;
+ bool foundDollar =
+ fieldchecker::isPositional(_updatePath, &_pathReplacementPosition, &foundCount);
- if (positional)
- *positional = foundDollar;
+ if (positional)
+ *positional = foundDollar;
- if (foundDollar && foundCount > 1) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "Too many positional (i.e. '$') elements found in path '"
- << _updatePath.dottedField() << "'");
- }
+ if (foundDollar && foundCount > 1) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "Too many positional (i.e. '$') elements found in path '"
+ << _updatePath.dottedField() << "'");
+ }
- // Validate and store the type to produce
- switch (modExpr.type()) {
- case Bool:
- _typeIsDate = true;
- break;
- case Object: {
- const BSONObj argObj = modExpr.embeddedObject();
- const BSONElement typeElem = argObj.getField(kType);
- bool badInput = typeElem.eoo() || !(typeElem.type() == String);
+ // Validate and store the type to produce
+ switch (modExpr.type()) {
+ case Bool:
+ _typeIsDate = true;
+ break;
+ case Object: {
+ const BSONObj argObj = modExpr.embeddedObject();
+ const BSONElement typeElem = argObj.getField(kType);
+ bool badInput = typeElem.eoo() || !(typeElem.type() == String);
+
+ if (!badInput) {
+ std::string typeVal = typeElem.String();
+ badInput = !(typeElem.String() == kDate || typeElem.String() == kTimestamp);
+ if (!badInput)
+ _typeIsDate = (typeVal == kDate);
if (!badInput) {
- std::string typeVal = typeElem.String();
- badInput = !(typeElem.String() == kDate || typeElem.String() == kTimestamp);
- if (!badInput)
- _typeIsDate = (typeVal == kDate);
-
- if (!badInput) {
- // Check to make sure only the $type field was given as an arg
- BSONObjIterator i( argObj );
- const bool onlyHasTypeField = ((i.next().fieldNameStringData() == kType)
- && i.next().eoo());
- if (!onlyHasTypeField) {
- return Status(ErrorCodes::BadValue,
- str::stream() <<
- "The only valid field of the option is '$type': "
- "{$currentDate: {field : {$type: 'date/timestamp'}}}; "
+ // Check to make sure only the $type field was given as an arg
+ BSONObjIterator i(argObj);
+ const bool onlyHasTypeField =
+ ((i.next().fieldNameStringData() == kType) && i.next().eoo());
+ if (!onlyHasTypeField) {
+ return Status(ErrorCodes::BadValue,
+ str::stream()
+ << "The only valid field of the option is '$type': "
+ "{$currentDate: {field : {$type: 'date/timestamp'}}}; "
<< "arg: " << argObj);
- }
-
}
-
- }
-
- if (badInput) {
- return Status(ErrorCodes::BadValue,
- "The '$type' string field is required "
- "to be 'date' or 'timestamp': "
- "{$currentDate: {field : {$type: 'date'}}}");
}
- break;
}
- default:
- return Status(ErrorCodes::BadValue,
- str::stream() << typeName(modExpr.type())
- << " is not valid type for $currentDate."
- " Please use a boolean ('true')"
- " or a $type expression ({$type: 'timestamp/date'}).");
- }
-
- return Status::OK();
- }
-
- Status ModifierCurrentDate::prepare(mutablebson::Element root,
- StringData matchedField,
- ExecInfo* execInfo) {
-
- _preparedState.reset(new PreparedState(root.getDocument()));
- // If we have a $-positional field, it is time to bind it to an actual field part.
- if (_pathReplacementPosition) {
- if (matchedField.empty()) {
+ if (badInput) {
return Status(ErrorCodes::BadValue,
- str::stream() << "The positional operator did not find the match "
- "needed from the query. Unexpanded update: "
- << _updatePath.dottedField());
+ "The '$type' string field is required "
+ "to be 'date' or 'timestamp': "
+ "{$currentDate: {field : {$type: 'date'}}}");
}
- _updatePath.setPart(_pathReplacementPosition, matchedField);
+ break;
}
+ default:
+ return Status(ErrorCodes::BadValue,
+ str::stream() << typeName(modExpr.type())
+ << " is not valid type for $currentDate."
+ " Please use a boolean ('true')"
+ " or a $type expression ({$type: 'timestamp/date'}).");
+ }
- // Locate the field name in 'root'. Note that we may not have all the parts in the path
- // in the doc -- which is fine. Our goal now is merely to reason about whether this mod
- // apply is a noOp or whether is can be in place. The remaining path, if missing, will
- // be created during the apply.
- Status status = pathsupport::findLongestPrefix(_updatePath,
- root,
- &_preparedState->idxFound,
- &_preparedState->elemFound);
-
- // FindLongestPrefix may say the path does not exist at all, which is fine here, or
- // that the path was not viable or otherwise wrong, in which case, the mod cannot
- // proceed.
- if (status.code() == ErrorCodes::NonExistentPath) {
- _preparedState->elemFound = root.getDocument().end();
- }
- else if (!status.isOK()) {
- return status;
- }
+ return Status::OK();
+}
- // We register interest in the field name. The driver needs this info to sort out if
- // there is any conflict among mods.
- execInfo->fieldRef[0] = &_updatePath;
+Status ModifierCurrentDate::prepare(mutablebson::Element root,
+ StringData matchedField,
+ ExecInfo* execInfo) {
+ _preparedState.reset(new PreparedState(root.getDocument()));
- return Status::OK();
+ // If we have a $-positional field, it is time to bind it to an actual field part.
+ if (_pathReplacementPosition) {
+ if (matchedField.empty()) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "The positional operator did not find the match "
+ "needed from the query. Unexpanded update: "
+ << _updatePath.dottedField());
+ }
+ _updatePath.setPart(_pathReplacementPosition, matchedField);
}
- Status ModifierCurrentDate::apply() const {
+ // Locate the field name in 'root'. Note that we may not have all the parts in the path
+ // in the doc -- which is fine. Our goal now is merely to reason about whether this mod
+ // apply is a noOp or whether is can be in place. The remaining path, if missing, will
+ // be created during the apply.
+ Status status = pathsupport::findLongestPrefix(
+ _updatePath, root, &_preparedState->idxFound, &_preparedState->elemFound);
+
+ // FindLongestPrefix may say the path does not exist at all, which is fine here, or
+ // that the path was not viable or otherwise wrong, in which case, the mod cannot
+ // proceed.
+ if (status.code() == ErrorCodes::NonExistentPath) {
+ _preparedState->elemFound = root.getDocument().end();
+ } else if (!status.isOK()) {
+ return status;
+ }
- const bool destExists = (_preparedState->elemFound.ok() &&
- _preparedState->idxFound == (_updatePath.numParts() - 1));
+ // We register interest in the field name. The driver needs this info to sort out if
+ // there is any conflict among mods.
+ execInfo->fieldRef[0] = &_updatePath;
- mutablebson::Document& doc = _preparedState->doc;
- StringData lastPart = _updatePath.getPart(_updatePath.numParts() - 1);
- // If the element exists and is the same type, then that is what we want to work with
- mutablebson::Element elemToSet = destExists ?
- _preparedState->elemFound:
- doc.end() ;
+ return Status::OK();
+}
- if (!destExists) {
- // Creates the final element that's going to be $set in 'doc'.
- // fills in the value with place-holder/empty
+Status ModifierCurrentDate::apply() const {
+ const bool destExists = (_preparedState->elemFound.ok() &&
+ _preparedState->idxFound == (_updatePath.numParts() - 1));
- elemToSet = _typeIsDate ?
- doc.makeElementDate(lastPart, Date_t()) :
- doc.makeElementTimestamp(lastPart, Timestamp());
+ mutablebson::Document& doc = _preparedState->doc;
+ StringData lastPart = _updatePath.getPart(_updatePath.numParts() - 1);
+ // If the element exists and is the same type, then that is what we want to work with
+ mutablebson::Element elemToSet = destExists ? _preparedState->elemFound : doc.end();
- if (!elemToSet.ok()) {
- return Status(ErrorCodes::InternalError, "can't create new element");
- }
+ if (!destExists) {
+ // Creates the final element that's going to be $set in 'doc'.
+ // fills in the value with place-holder/empty
- // Now, we can be in two cases here, as far as attaching the element being set goes:
- // (a) none of the parts in the element's path exist, or (b) some parts of the path
- // exist but not all.
- if (!_preparedState->elemFound.ok()) {
- _preparedState->elemFound = doc.root();
- _preparedState->idxFound = 0;
- }
- else {
- _preparedState->idxFound++;
- }
+ elemToSet = _typeIsDate ? doc.makeElementDate(lastPart, Date_t())
+ : doc.makeElementTimestamp(lastPart, Timestamp());
- // createPathAt() will complete the path and attach 'elemToSet' at the end of it.
- Status s = pathsupport::createPathAt(_updatePath,
- _preparedState->idxFound,
- _preparedState->elemFound,
- elemToSet);
- if (!s.isOK())
- return s;
+ if (!elemToSet.ok()) {
+ return Status(ErrorCodes::InternalError, "can't create new element");
}
- dassert(elemToSet.ok());
-
- // By the time we are here the element is in place and we just need to update the value
- if (_typeIsDate) {
- const mongo::Date_t now = mongo::jsTime();
- Status s = elemToSet.setValueDate(now);
- if (!s.isOK())
- return s;
- }
- else {
- Status s = elemToSet.setValueTimestamp(getNextGlobalTimestamp());
- if (!s.isOK())
- return s;
+ // Now, we can be in two cases here, as far as attaching the element being set goes:
+ // (a) none of the parts in the element's path exist, or (b) some parts of the path
+ // exist but not all.
+ if (!_preparedState->elemFound.ok()) {
+ _preparedState->elemFound = doc.root();
+ _preparedState->idxFound = 0;
+ } else {
+ _preparedState->idxFound++;
}
- // Set the elemFound, idxFound to the changed element for oplog logging.
- _preparedState->elemFound = elemToSet;
- _preparedState->idxFound = (_updatePath.numParts() - 1);
+ // createPathAt() will complete the path and attach 'elemToSet' at the end of it.
+ Status s = pathsupport::createPathAt(
+ _updatePath, _preparedState->idxFound, _preparedState->elemFound, elemToSet);
+ if (!s.isOK())
+ return s;
+ }
- return Status::OK();
+ dassert(elemToSet.ok());
+
+ // By the time we are here the element is in place and we just need to update the value
+ if (_typeIsDate) {
+ const mongo::Date_t now = mongo::jsTime();
+ Status s = elemToSet.setValueDate(now);
+ if (!s.isOK())
+ return s;
+ } else {
+ Status s = elemToSet.setValueTimestamp(getNextGlobalTimestamp());
+ if (!s.isOK())
+ return s;
}
- Status ModifierCurrentDate::log(LogBuilder* logBuilder) const {
- // TODO: None of this checks should be needed unless someone calls if we are a noOp
- // When we cleanup we should build in testing that no-one calls in the noOp case
- const bool destExists = (_preparedState->elemFound.ok() &&
- _preparedState->idxFound == (_updatePath.numParts() - 1));
+ // Set the elemFound, idxFound to the changed element for oplog logging.
+ _preparedState->elemFound = elemToSet;
+ _preparedState->idxFound = (_updatePath.numParts() - 1);
- // If the destination doesn't exist then we have nothing to log.
- // This would only happen if apply isn't called or fails when it had to create an element
- if (!destExists)
- return Status::OK();
+ return Status::OK();
+}
- return logBuilder->addToSetsWithNewFieldName(_updatePath.dottedField(),
- _preparedState->elemFound);
- }
+Status ModifierCurrentDate::log(LogBuilder* logBuilder) const {
+ // TODO: None of this checks should be needed unless someone calls if we are a noOp
+ // When we cleanup we should build in testing that no-one calls in the noOp case
+ const bool destExists = (_preparedState->elemFound.ok() &&
+ _preparedState->idxFound == (_updatePath.numParts() - 1));
+
+ // If the destination doesn't exist then we have nothing to log.
+ // This would only happen if apply isn't called or fails when it had to create an element
+ if (!destExists)
+ return Status::OK();
+
+ return logBuilder->addToSetsWithNewFieldName(_updatePath.dottedField(),
+ _preparedState->elemFound);
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_current_date.h b/src/mongo/db/ops/modifier_current_date.h
index 291c05b377a..d7e1bd5e875 100644
--- a/src/mongo/db/ops/modifier_current_date.h
+++ b/src/mongo/db/ops/modifier_current_date.h
@@ -37,53 +37,48 @@
namespace mongo {
- class LogBuilder;
+class LogBuilder;
- class ModifierCurrentDate : public ModifierInterface {
- MONGO_DISALLOW_COPYING(ModifierCurrentDate);
+class ModifierCurrentDate : public ModifierInterface {
+ MONGO_DISALLOW_COPYING(ModifierCurrentDate);
- public:
+public:
+ ModifierCurrentDate();
+ virtual ~ModifierCurrentDate();
- ModifierCurrentDate();
- virtual ~ModifierCurrentDate();
+ /**
+ * A 'modExpr' is a BSONElement {<fieldname>: <value>} coming
+ * from a $currentDate mod such as
+ * {$currentDate: {<fieldname: true/{$type: "date/timestamp"}}.
+ * init() extracts the field name and the value to be
+ * assigned to it from 'modExpr'. It returns OK if successful or a status describing
+ * the error.
+ */
+ virtual Status init(const BSONElement& modExpr, const Options& opts, bool* positional = NULL);
- /**
- * A 'modExpr' is a BSONElement {<fieldname>: <value>} coming
- * from a $currentDate mod such as
- * {$currentDate: {<fieldname: true/{$type: "date/timestamp"}}.
- * init() extracts the field name and the value to be
- * assigned to it from 'modExpr'. It returns OK if successful or a status describing
- * the error.
- */
- virtual Status init(const BSONElement& modExpr, const Options& opts,
- bool* positional = NULL);
+ /** Evaluates the validity of applying $currentDate.
+ */
+ virtual Status prepare(mutablebson::Element root, StringData matchedField, ExecInfo* execInfo);
- /** Evaluates the validity of applying $currentDate.
- */
- virtual Status prepare(mutablebson::Element root,
- StringData matchedField,
- ExecInfo* execInfo);
+ /** Updates the node passed in prepare with the results from prepare */
+ virtual Status apply() const;
- /** Updates the node passed in prepare with the results from prepare */
- virtual Status apply() const;
+ /** Converts the result into a $set */
+ virtual Status log(LogBuilder* logBuilder) const;
- /** Converts the result into a $set */
- virtual Status log(LogBuilder* logBuilder) const;
+private:
+ // Access to each component of fieldName that's the target of this mod.
+ FieldRef _updatePath;
- private:
+ // 0 or index for $-positional in _updatePath.
+ size_t _pathReplacementPosition;
- // Access to each component of fieldName that's the target of this mod.
- FieldRef _updatePath;
+ // Is the final type being added a Date or Timestamp?
+ bool _typeIsDate;
- // 0 or index for $-positional in _updatePath.
- size_t _pathReplacementPosition;
+ // State which changes with each call of the mod.
+ struct PreparedState;
+ std::unique_ptr<PreparedState> _preparedState;
+};
- // Is the final type being added a Date or Timestamp?
- bool _typeIsDate;
-
- // State which changes with each call of the mod.
- struct PreparedState;
- std::unique_ptr<PreparedState> _preparedState;
- };
-
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_current_date_test.cpp b/src/mongo/db/ops/modifier_current_date_test.cpp
index 51eb4a79659..ecb4b86bac8 100644
--- a/src/mongo/db/ops/modifier_current_date_test.cpp
+++ b/src/mongo/db/ops/modifier_current_date_test.cpp
@@ -39,327 +39,323 @@
namespace {
- using mongo::BSONObj;
- using mongo::LogBuilder;
- using mongo::ModifierCurrentDate;
- using mongo::ModifierInterface;
- using mongo::Timestamp;
- using mongo::Status;
- using mongo::StringData;
- using mongo::fromjson;
- using mongo::mutablebson::ConstElement;
- using mongo::mutablebson::Document;
- using mongo::mutablebson::Element;
-
- /**
- * Helper to validate oplog entries in the tests below.
- */
- void validateOplogEntry(BSONObj& oplogFormat, Document& doc){
- // Ensure that the field is the same
- ASSERT_EQUALS(oplogFormat.firstElement().fieldName(),
- doc.root().leftChild().getFieldName());
-
- // Ensure the field names are the same
- ASSERT_EQUALS(oplogFormat.firstElement().embeddedObject().firstElement().fieldName(),
- doc.root().leftChild().leftChild().getFieldName());
-
- // Ensure the type is the same in the document as the oplog
- ASSERT_EQUALS(oplogFormat.firstElement().embeddedObject().firstElement().type(),
- doc.root().leftChild().leftChild().getType());
+using mongo::BSONObj;
+using mongo::LogBuilder;
+using mongo::ModifierCurrentDate;
+using mongo::ModifierInterface;
+using mongo::Timestamp;
+using mongo::Status;
+using mongo::StringData;
+using mongo::fromjson;
+using mongo::mutablebson::ConstElement;
+using mongo::mutablebson::Document;
+using mongo::mutablebson::Element;
+
+/**
+ * Helper to validate oplog entries in the tests below.
+ */
+void validateOplogEntry(BSONObj& oplogFormat, Document& doc) {
+ // Ensure that the field is the same
+ ASSERT_EQUALS(oplogFormat.firstElement().fieldName(), doc.root().leftChild().getFieldName());
+
+ // Ensure the field names are the same
+ ASSERT_EQUALS(oplogFormat.firstElement().embeddedObject().firstElement().fieldName(),
+ doc.root().leftChild().leftChild().getFieldName());
+
+ // Ensure the type is the same in the document as the oplog
+ ASSERT_EQUALS(oplogFormat.firstElement().embeddedObject().firstElement().type(),
+ doc.root().leftChild().leftChild().getType());
+}
+
+/** Helper to build and manipulate a $currentDate mod. */
+class Mod {
+public:
+ Mod() : _mod() {}
+
+ explicit Mod(BSONObj modObj) : _modObj(modObj), _mod() {
+ ASSERT_OK(_mod.init(_modObj["$currentDate"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
}
- /** Helper to build and manipulate a $currentDate mod. */
- class Mod {
- public:
- Mod() : _mod() {}
+ Status prepare(Element root, StringData matchedField, ModifierInterface::ExecInfo* execInfo) {
+ return _mod.prepare(root, matchedField, execInfo);
+ }
- explicit Mod(BSONObj modObj)
- : _modObj(modObj)
- , _mod() {
- ASSERT_OK(_mod.init(_modObj["$currentDate"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
+ Status apply() const {
+ return _mod.apply();
+ }
- }
+ Status log(LogBuilder* logBuilder) const {
+ return _mod.log(logBuilder);
+ }
- Status prepare(Element root,
- StringData matchedField,
- ModifierInterface::ExecInfo* execInfo) {
- return _mod.prepare(root, matchedField, execInfo);
- }
+ ModifierCurrentDate& mod() {
+ return _mod;
+ }
- Status apply() const {
- return _mod.apply();
- }
+private:
+ BSONObj _modObj;
+ ModifierCurrentDate _mod;
+};
- Status log(LogBuilder* logBuilder) const {
- return _mod.log(logBuilder);
- }
+TEST(Init, ValidValues) {
+ BSONObj modObj;
+ ModifierCurrentDate mod;
- ModifierCurrentDate& mod() { return _mod; }
+ modObj = fromjson("{ $currentDate : { a : true } }");
+ ASSERT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
- private:
- BSONObj _modObj;
- ModifierCurrentDate _mod;
- };
+ modObj = fromjson("{ $currentDate : { a : {$type : 'timestamp' } } }");
+ ASSERT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
- TEST(Init, ValidValues) {
- BSONObj modObj;
- ModifierCurrentDate mod;
+ modObj = fromjson("{ $currentDate : { a : {$type : 'date' } } }");
+ ASSERT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
- modObj = fromjson("{ $currentDate : { a : true } }");
- ASSERT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
+TEST(Init, FailToInitWithInvalidValue) {
+ BSONObj modObj;
+ ModifierCurrentDate mod;
- modObj = fromjson("{ $currentDate : { a : {$type : 'timestamp' } } }");
- ASSERT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
+ // String is an invalid $currentDate argument
+ modObj = fromjson("{ $currentDate : { a : 'Oct 11, 2001' } }");
+ ASSERT_NOT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
ModifierInterface::Options::normal()));
- modObj = fromjson("{ $currentDate : { a : {$type : 'date' } } }");
- ASSERT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
+ // Array is an invalid $currentDate argument
+ modObj = fromjson("{ $currentDate : { a : [] } }");
+ ASSERT_NOT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
ModifierInterface::Options::normal()));
- }
-
- TEST(Init, FailToInitWithInvalidValue) {
- BSONObj modObj;
- ModifierCurrentDate mod;
-
- // String is an invalid $currentDate argument
- modObj = fromjson("{ $currentDate : { a : 'Oct 11, 2001' } }");
- ASSERT_NOT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
-
- // Array is an invalid $currentDate argument
- modObj = fromjson("{ $currentDate : { a : [] } }");
- ASSERT_NOT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
-
- // Number is an invalid $currentDate argument
- modObj = fromjson("{ $currentDate : { a : 1 } }");
- ASSERT_NOT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
-
- // Regex is an invalid $currentDate argument
- modObj = fromjson("{ $currentDate : { a : /1/ } }");
- ASSERT_NOT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
-
- // An object with missing $type field is an invalid $currentDate argument
- modObj = fromjson("{ $currentDate : { a : { foo : 4 } } }");
- ASSERT_NOT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
-
- // An object with extra fields, including the $type field is bad
- modObj = fromjson("{ $currentDate : { a : { $type: 'date', foo : 4 } } }");
- ASSERT_NOT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
-
- // An object with extra fields, including the $type field is bad
- modObj = fromjson("{ $currentDate : { a : { foo: 4, $type : 'date' } } }");
- ASSERT_NOT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
-
- // An object with non-date/timestamp $type field is an invalid $currentDate argument
- modObj = fromjson("{ $currentDate : { a : { $type : 4 } } }");
- ASSERT_NOT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
-
- // An object with non-date/timestamp $type field is an invalid $currentDate argument
- modObj = fromjson("{ $currentDate : { a : { $type : 'foo' } } }");
- ASSERT_NOT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
-
- TEST(BoolInput, EmptyStartDoc) {
- Document doc(fromjson("{ }"));
- Mod mod(fromjson("{ $currentDate : { a : true } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
-
- BSONObj olderDateObj = fromjson("{ a : { $date : 0 } }");
- ASSERT_OK(mod.apply());
- ASSERT_LESS_THAN(olderDateObj, doc.getObject());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- BSONObj oplogFormat = fromjson("{ $set : { a : { $date : 0 } } }");
- validateOplogEntry(oplogFormat, logDoc);
- }
-
- TEST(DateInput, EmptyStartDoc) {
- Document doc(fromjson("{ }"));
- Mod mod(fromjson("{ $currentDate : { a : {$type: 'date' } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
-
- BSONObj olderDateObj = fromjson("{ a : { $date : 0 } }");
- ASSERT_OK(mod.apply());
- ASSERT_LESS_THAN(olderDateObj, doc.getObject());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- BSONObj oplogFormat = fromjson("{ $set : { a : { $date : 0 } } }");
- validateOplogEntry(oplogFormat, logDoc);
- }
- TEST(TimestampInput, EmptyStartDoc) {
- Document doc(fromjson("{ }"));
- Mod mod(fromjson("{ $currentDate : { a : {$type : 'timestamp' } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
-
- mongo::Timestamp ts;
- BSONObj olderDateObj = BSON("a" << ts);
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_LESS_THAN(olderDateObj, doc.getObject());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- BSONObj oplogFormat = fromjson("{ $set : { a : { $timestamp : {t:0, i:0} } } }");
- validateOplogEntry(oplogFormat, logDoc);
- }
-
- TEST(BoolInput, ExistingStringDoc) {
- Document doc(fromjson("{ a: 'a' }"));
- Mod mod(fromjson("{ $currentDate : { a : true } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
-
- BSONObj olderDateObj = fromjson("{ a : { $date : 0 } }");
- ASSERT_OK(mod.apply());
- ASSERT_LESS_THAN(olderDateObj, doc.getObject());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- BSONObj oplogFormat = fromjson("{ $set : { a : { $date : 0 } } }");
- validateOplogEntry(oplogFormat, logDoc);
- }
+ // Number is an invalid $currentDate argument
+ modObj = fromjson("{ $currentDate : { a : 1 } }");
+ ASSERT_NOT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
- TEST(BoolInput, ExistingDateDoc) {
- Document doc(fromjson("{ a: {$date: 0 } }"));
- Mod mod(fromjson("{ $currentDate : { a : true } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
-
- BSONObj olderDateObj = fromjson("{ a : { $date : 0 } }");
- ASSERT_OK(mod.apply());
- ASSERT_LESS_THAN(olderDateObj, doc.getObject());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- BSONObj oplogFormat = fromjson("{ $set : { a : { $date : 0 } } }");
- validateOplogEntry(oplogFormat, logDoc);
- }
+ // Regex is an invalid $currentDate argument
+ modObj = fromjson("{ $currentDate : { a : /1/ } }");
+ ASSERT_NOT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
- TEST(DateInput, ExistingDateDoc) {
- Document doc(fromjson("{ a: {$date: 0 } }"));
- Mod mod(fromjson("{ $currentDate : { a : {$type: 'date' } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
-
- BSONObj olderDateObj = fromjson("{ a : { $date : 0 } }");
- ASSERT_OK(mod.apply());
- ASSERT_LESS_THAN(olderDateObj, doc.getObject());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- BSONObj oplogFormat = fromjson("{ $set : { a : { $date : 0 } } }");
- validateOplogEntry(oplogFormat, logDoc);
- }
+ // An object with missing $type field is an invalid $currentDate argument
+ modObj = fromjson("{ $currentDate : { a : { foo : 4 } } }");
+ ASSERT_NOT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
- TEST(TimestampInput, ExistingDateDoc) {
- Document doc(fromjson("{ a: {$date: 0 } }"));
- Mod mod(fromjson("{ $currentDate : { a : {$type : 'timestamp' } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
-
- mongo::Timestamp ts;
- BSONObj olderDateObj = BSON("a" << ts);
- ASSERT_OK(mod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled()); //Same Size as Date
- ASSERT_LESS_THAN(olderDateObj, doc.getObject());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- BSONObj oplogFormat = fromjson("{ $set : { a : { $timestamp : {t:0, i:0} } } }");
- validateOplogEntry(oplogFormat, logDoc);
- }
+ // An object with extra fields, including the $type field is bad
+ modObj = fromjson("{ $currentDate : { a : { $type: 'date', foo : 4 } } }");
+ ASSERT_NOT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
- TEST(TimestampInput, ExistingEmbeddedDateDoc) {
- Document doc(fromjson("{ a: {b: {$date: 0 } } }"));
- Mod mod(fromjson("{ $currentDate : { 'a.b' : {$type : 'timestamp' } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_EQUALS("a.b", execInfo.fieldRef[0]->dottedField());
-
- mongo::Timestamp ts;
- BSONObj olderDateObj = BSON("a" << BSON( "b" << ts));
- ASSERT_OK(mod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled()); //Same Size as Date
- ASSERT_LESS_THAN(olderDateObj, doc.getObject());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- BSONObj oplogFormat = fromjson("{ $set : { 'a.b' : { $timestamp : {t:0, i:0} } } }");
- validateOplogEntry(oplogFormat, logDoc);
- }
+ // An object with extra fields, including the $type field is bad
+ modObj = fromjson("{ $currentDate : { a : { foo: 4, $type : 'date' } } }");
+ ASSERT_NOT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
- TEST(DottedTimestampInput, EmptyStartDoc) {
- Document doc(fromjson("{ }"));
- Mod mod(fromjson("{ $currentDate : { 'a.b' : {$type : 'timestamp' } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_EQUALS("a.b", execInfo.fieldRef[0]->dottedField());
-
- mongo::Timestamp ts;
- BSONObj olderDateObj = BSON("a" << BSON( "b" << ts));
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_LESS_THAN(olderDateObj, doc.getObject());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- BSONObj oplogFormat = fromjson("{ $set : { 'a.b' : { $timestamp : {t:0, i:0} } } }");
- validateOplogEntry(oplogFormat, logDoc);
- }
+ // An object with non-date/timestamp $type field is an invalid $currentDate argument
+ modObj = fromjson("{ $currentDate : { a : { $type : 4 } } }");
+ ASSERT_NOT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
-} // namespace
+ // An object with non-date/timestamp $type field is an invalid $currentDate argument
+ modObj = fromjson("{ $currentDate : { a : { $type : 'foo' } } }");
+ ASSERT_NOT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
+
+TEST(BoolInput, EmptyStartDoc) {
+ Document doc(fromjson("{ }"));
+ Mod mod(fromjson("{ $currentDate : { a : true } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+ ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
+
+ BSONObj olderDateObj = fromjson("{ a : { $date : 0 } }");
+ ASSERT_OK(mod.apply());
+ ASSERT_LESS_THAN(olderDateObj, doc.getObject());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ BSONObj oplogFormat = fromjson("{ $set : { a : { $date : 0 } } }");
+ validateOplogEntry(oplogFormat, logDoc);
+}
+
+TEST(DateInput, EmptyStartDoc) {
+ Document doc(fromjson("{ }"));
+ Mod mod(fromjson("{ $currentDate : { a : {$type: 'date' } } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+ ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
+
+ BSONObj olderDateObj = fromjson("{ a : { $date : 0 } }");
+ ASSERT_OK(mod.apply());
+ ASSERT_LESS_THAN(olderDateObj, doc.getObject());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ BSONObj oplogFormat = fromjson("{ $set : { a : { $date : 0 } } }");
+ validateOplogEntry(oplogFormat, logDoc);
+}
+
+TEST(TimestampInput, EmptyStartDoc) {
+ Document doc(fromjson("{ }"));
+ Mod mod(fromjson("{ $currentDate : { a : {$type : 'timestamp' } } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+ ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
+
+ mongo::Timestamp ts;
+ BSONObj olderDateObj = BSON("a" << ts);
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_LESS_THAN(olderDateObj, doc.getObject());
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ BSONObj oplogFormat = fromjson("{ $set : { a : { $timestamp : {t:0, i:0} } } }");
+ validateOplogEntry(oplogFormat, logDoc);
+}
+
+TEST(BoolInput, ExistingStringDoc) {
+ Document doc(fromjson("{ a: 'a' }"));
+ Mod mod(fromjson("{ $currentDate : { a : true } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+ ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
+
+ BSONObj olderDateObj = fromjson("{ a : { $date : 0 } }");
+ ASSERT_OK(mod.apply());
+ ASSERT_LESS_THAN(olderDateObj, doc.getObject());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ BSONObj oplogFormat = fromjson("{ $set : { a : { $date : 0 } } }");
+ validateOplogEntry(oplogFormat, logDoc);
+}
+
+TEST(BoolInput, ExistingDateDoc) {
+ Document doc(fromjson("{ a: {$date: 0 } }"));
+ Mod mod(fromjson("{ $currentDate : { a : true } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+ ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
+
+ BSONObj olderDateObj = fromjson("{ a : { $date : 0 } }");
+ ASSERT_OK(mod.apply());
+ ASSERT_LESS_THAN(olderDateObj, doc.getObject());
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ BSONObj oplogFormat = fromjson("{ $set : { a : { $date : 0 } } }");
+ validateOplogEntry(oplogFormat, logDoc);
+}
+
+TEST(DateInput, ExistingDateDoc) {
+ Document doc(fromjson("{ a: {$date: 0 } }"));
+ Mod mod(fromjson("{ $currentDate : { a : {$type: 'date' } } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+ ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
+
+ BSONObj olderDateObj = fromjson("{ a : { $date : 0 } }");
+ ASSERT_OK(mod.apply());
+ ASSERT_LESS_THAN(olderDateObj, doc.getObject());
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ BSONObj oplogFormat = fromjson("{ $set : { a : { $date : 0 } } }");
+ validateOplogEntry(oplogFormat, logDoc);
+}
+
+TEST(TimestampInput, ExistingDateDoc) {
+ Document doc(fromjson("{ a: {$date: 0 } }"));
+ Mod mod(fromjson("{ $currentDate : { a : {$type : 'timestamp' } } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+ ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
+
+ mongo::Timestamp ts;
+ BSONObj olderDateObj = BSON("a" << ts);
+ ASSERT_OK(mod.apply());
+ ASSERT_TRUE(doc.isInPlaceModeEnabled()); // Same Size as Date
+ ASSERT_LESS_THAN(olderDateObj, doc.getObject());
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ BSONObj oplogFormat = fromjson("{ $set : { a : { $timestamp : {t:0, i:0} } } }");
+ validateOplogEntry(oplogFormat, logDoc);
+}
+
+TEST(TimestampInput, ExistingEmbeddedDateDoc) {
+ Document doc(fromjson("{ a: {b: {$date: 0 } } }"));
+ Mod mod(fromjson("{ $currentDate : { 'a.b' : {$type : 'timestamp' } } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+ ASSERT_EQUALS("a.b", execInfo.fieldRef[0]->dottedField());
+
+ mongo::Timestamp ts;
+ BSONObj olderDateObj = BSON("a" << BSON("b" << ts));
+ ASSERT_OK(mod.apply());
+ ASSERT_TRUE(doc.isInPlaceModeEnabled()); // Same Size as Date
+ ASSERT_LESS_THAN(olderDateObj, doc.getObject());
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ BSONObj oplogFormat = fromjson("{ $set : { 'a.b' : { $timestamp : {t:0, i:0} } } }");
+ validateOplogEntry(oplogFormat, logDoc);
+}
+
+TEST(DottedTimestampInput, EmptyStartDoc) {
+ Document doc(fromjson("{ }"));
+ Mod mod(fromjson("{ $currentDate : { 'a.b' : {$type : 'timestamp' } } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+ ASSERT_EQUALS("a.b", execInfo.fieldRef[0]->dottedField());
+
+ mongo::Timestamp ts;
+ BSONObj olderDateObj = BSON("a" << BSON("b" << ts));
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_LESS_THAN(olderDateObj, doc.getObject());
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ BSONObj oplogFormat = fromjson("{ $set : { 'a.b' : { $timestamp : {t:0, i:0} } } }");
+ validateOplogEntry(oplogFormat, logDoc);
+}
+
+} // namespace
diff --git a/src/mongo/db/ops/modifier_inc.cpp b/src/mongo/db/ops/modifier_inc.cpp
index a967e0a734a..b683a1f9561 100644
--- a/src/mongo/db/ops/modifier_inc.cpp
+++ b/src/mongo/db/ops/modifier_inc.cpp
@@ -38,260 +38,229 @@
namespace mongo {
- namespace mb = mutablebson;
- namespace str = mongoutils::str;
+namespace mb = mutablebson;
+namespace str = mongoutils::str;
- struct ModifierInc::PreparedState {
+struct ModifierInc::PreparedState {
+ PreparedState(mutablebson::Document& doc)
+ : doc(doc), idxFound(0), elemFound(doc.end()), newValue(), noOp(false) {}
- PreparedState(mutablebson::Document& doc)
- : doc(doc)
- , idxFound(0)
- , elemFound(doc.end())
- , newValue()
- , noOp(false) {
- }
+ // Document that is going to be changed.
+ mutablebson::Document& doc;
+
+ // Index in _fieldRef for which an Element exist in the document.
+ size_t idxFound;
+
+ // Element corresponding to _fieldRef[0.._idxFound].
+ mutablebson::Element elemFound;
- // Document that is going to be changed.
- mutablebson::Document& doc;
+ // Value to be applied
+ SafeNum newValue;
- // Index in _fieldRef for which an Element exist in the document.
- size_t idxFound;
+ // This $inc is a no-op?
+ bool noOp;
+};
- // Element corresponding to _fieldRef[0.._idxFound].
- mutablebson::Element elemFound;
+ModifierInc::ModifierInc(ModifierIncMode mode)
+ : ModifierInterface(), _mode(mode), _fieldRef(), _posDollar(0), _val() {}
- // Value to be applied
- SafeNum newValue;
+ModifierInc::~ModifierInc() {}
- // This $inc is a no-op?
- bool noOp;
- };
+Status ModifierInc::init(const BSONElement& modExpr, const Options& opts, bool* positional) {
+ //
+ // field name analysis
+ //
- ModifierInc::ModifierInc(ModifierIncMode mode)
- : ModifierInterface ()
- , _mode(mode)
- , _fieldRef()
- , _posDollar(0)
- , _val() {
+ // Perform standard field name and updateable checks.
+ _fieldRef.parse(modExpr.fieldName());
+ Status status = fieldchecker::isUpdatable(_fieldRef);
+ if (!status.isOK()) {
+ return status;
}
- ModifierInc::~ModifierInc() {
+ // If a $-positional operator was used, get the index in which it occurred
+ // and ensure only one occurrence.
+ size_t foundCount;
+ bool foundDollar = fieldchecker::isPositional(_fieldRef, &_posDollar, &foundCount);
+
+ if (positional)
+ *positional = foundDollar;
+
+ if (foundDollar && foundCount > 1) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "Too many positional (i.e. '$') elements found in path '"
+ << _fieldRef.dottedField() << "'");
}
- Status ModifierInc::init(const BSONElement& modExpr, const Options& opts,
- bool* positional) {
+ //
+ // value analysis
+ //
- //
- // field name analysis
- //
+ if (!modExpr.isNumber()) {
+ // TODO: Context for mod error messages would be helpful
+ // include mod code, etc.
+ return Status(ErrorCodes::TypeMismatch,
+ str::stream() << "Cannot " << (_mode == MODE_INC ? "increment" : "multiply")
+ << " with non-numeric argument: {" << modExpr << "}");
+ }
- // Perform standard field name and updateable checks.
- _fieldRef.parse(modExpr.fieldName());
- Status status = fieldchecker::isUpdatable(_fieldRef);
- if (! status.isOK()) {
- return status;
- }
+ _val = modExpr;
+ dassert(_val.isValid());
- // If a $-positional operator was used, get the index in which it occurred
- // and ensure only one occurrence.
- size_t foundCount;
- bool foundDollar = fieldchecker::isPositional(_fieldRef, &_posDollar, &foundCount);
+ return Status::OK();
+}
- if (positional)
- *positional = foundDollar;
+Status ModifierInc::prepare(mutablebson::Element root,
+ StringData matchedField,
+ ExecInfo* execInfo) {
+ _preparedState.reset(new PreparedState(root.getDocument()));
- if (foundDollar && foundCount > 1) {
+ // If we have a $-positional field, it is time to bind it to an actual field part.
+ if (_posDollar) {
+ if (matchedField.empty()) {
return Status(ErrorCodes::BadValue,
- str::stream() << "Too many positional (i.e. '$') elements found in path '"
- << _fieldRef.dottedField() << "'");
+ str::stream() << "The positional operator did not find the match "
+ "needed from the query. Unexpanded update: "
+ << _fieldRef.dottedField());
}
+ _fieldRef.setPart(_posDollar, matchedField);
+ }
- //
- // value analysis
- //
-
- if (!modExpr.isNumber()) {
- // TODO: Context for mod error messages would be helpful
- // include mod code, etc.
- return Status(ErrorCodes::TypeMismatch,
- str::stream() << "Cannot "
- << (_mode == MODE_INC ? "increment" : "multiply")
- << " with non-numeric argument: {"
- << modExpr << "}");
- }
+ // Locate the field name in 'root'. Note that we may not have all the parts in the path
+ // in the doc -- which is fine. Our goal now is merely to reason about whether this mod
+ // apply is a noOp or whether is can be in place. The remaining path, if missing, will
+ // be created during the apply.
+ Status status = pathsupport::findLongestPrefix(
+ _fieldRef, root, &_preparedState->idxFound, &_preparedState->elemFound);
+
+ // FindLongestPrefix may say the path does not exist at all, which is fine here, or
+ // that the path was not viable or otherwise wrong, in which case, the mod cannot
+ // proceed.
+ if (status.code() == ErrorCodes::NonExistentPath) {
+ _preparedState->elemFound = root.getDocument().end();
+ } else if (!status.isOK()) {
+ return status;
+ }
- _val = modExpr;
- dassert(_val.isValid());
+ // We register interest in the field name. The driver needs this info to sort out if
+ // there is any conflict among mods.
+ execInfo->fieldRef[0] = &_fieldRef;
+
+ // Capture the value we are going to write. At this point, there may not be a value
+ // against which to operate, so the result will be simply _val.
+ _preparedState->newValue = _val;
+
+ //
+ // in-place and no-op logic
+ //
+ // If the field path is not fully present, then this mod cannot be in place, nor is a
+ // noOp.
+ if (!_preparedState->elemFound.ok() || _preparedState->idxFound < (_fieldRef.numParts() - 1)) {
+ // For multiplication, we treat ops against missing as yielding zero. We take
+ // advantage here of the promotion rules for SafeNum; the expression below will
+ // always yield a zero of the same type of operand that the user provided
+ // (e.g. double).
+ if (_mode == MODE_MUL)
+ _preparedState->newValue *= SafeNum(static_cast<int>(0));
return Status::OK();
}
- Status ModifierInc::prepare(mutablebson::Element root,
- StringData matchedField,
- ExecInfo* execInfo) {
-
- _preparedState.reset(new PreparedState(root.getDocument()));
-
- // If we have a $-positional field, it is time to bind it to an actual field part.
- if (_posDollar) {
- if (matchedField.empty()) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "The positional operator did not find the match "
- "needed from the query. Unexpanded update: "
- << _fieldRef.dottedField());
- }
- _fieldRef.setPart(_posDollar, matchedField);
- }
-
- // Locate the field name in 'root'. Note that we may not have all the parts in the path
- // in the doc -- which is fine. Our goal now is merely to reason about whether this mod
- // apply is a noOp or whether is can be in place. The remaining path, if missing, will
- // be created during the apply.
- Status status = pathsupport::findLongestPrefix(_fieldRef,
- root,
- &_preparedState->idxFound,
- &_preparedState->elemFound);
-
- // FindLongestPrefix may say the path does not exist at all, which is fine here, or
- // that the path was not viable or otherwise wrong, in which case, the mod cannot
- // proceed.
- if (status.code() == ErrorCodes::NonExistentPath) {
- _preparedState->elemFound = root.getDocument().end();
- }
- else if (!status.isOK()) {
- return status;
- }
-
- // We register interest in the field name. The driver needs this info to sort out if
- // there is any conflict among mods.
- execInfo->fieldRef[0] = &_fieldRef;
-
- // Capture the value we are going to write. At this point, there may not be a value
- // against which to operate, so the result will be simply _val.
- _preparedState->newValue = _val;
-
- //
- // in-place and no-op logic
- //
- // If the field path is not fully present, then this mod cannot be in place, nor is a
- // noOp.
- if (!_preparedState->elemFound.ok() ||
- _preparedState->idxFound < (_fieldRef.numParts() - 1)) {
-
- // For multiplication, we treat ops against missing as yielding zero. We take
- // advantage here of the promotion rules for SafeNum; the expression below will
- // always yield a zero of the same type of operand that the user provided
- // (e.g. double).
- if (_mode == MODE_MUL)
- _preparedState->newValue *= SafeNum(static_cast<int>(0));
-
- return Status::OK();
- }
-
- // If the value being $inc'ed is the same as the one already in the doc, than this is a
- // noOp.
- if (!_preparedState->elemFound.isNumeric()) {
- mb::Element idElem = mb::findFirstChildNamed(root, "_id");
- return Status(
- ErrorCodes::TypeMismatch,
- str::stream() << "Cannot apply "
- << (_mode == MODE_INC ? "$inc" : "$mul")
- << " to a value of non-numeric type. {"
- << idElem.toString()
- << "} has the field '" << _preparedState->elemFound.getFieldName()
- << "' of non-numeric type "
- << typeName(_preparedState->elemFound.getType()));
- }
- const SafeNum currentValue = _preparedState->elemFound.getValueSafeNum();
-
- // Update newValue w.r.t to the current value of the found element.
- if (_mode == MODE_INC)
- _preparedState->newValue += currentValue;
- else
- _preparedState->newValue *= currentValue;
-
- // If the result of the addition is invalid, we must return an error.
- if (!_preparedState->newValue.isValid()) {
- mb::Element idElem = mb::findFirstChildNamed(root, "_id");
- return Status(ErrorCodes::BadValue,
- str::stream() << "Failed to apply $inc operations to current value ("
- << currentValue.debugString() << ") for document {"
- << idElem.toString() << "}");
- }
-
- // If the values are identical (same type, same value), then this is a no-op.
- if (_preparedState->newValue.isIdentical(currentValue)) {
- _preparedState->noOp = execInfo->noOp = true;
- return Status::OK();
- }
+ // If the value being $inc'ed is the same as the one already in the doc, than this is a
+ // noOp.
+ if (!_preparedState->elemFound.isNumeric()) {
+ mb::Element idElem = mb::findFirstChildNamed(root, "_id");
+ return Status(ErrorCodes::TypeMismatch,
+ str::stream() << "Cannot apply " << (_mode == MODE_INC ? "$inc" : "$mul")
+ << " to a value of non-numeric type. {" << idElem.toString()
+ << "} has the field '"
+ << _preparedState->elemFound.getFieldName()
+ << "' of non-numeric type "
+ << typeName(_preparedState->elemFound.getType()));
+ }
+ const SafeNum currentValue = _preparedState->elemFound.getValueSafeNum();
+
+ // Update newValue w.r.t to the current value of the found element.
+ if (_mode == MODE_INC)
+ _preparedState->newValue += currentValue;
+ else
+ _preparedState->newValue *= currentValue;
+
+ // If the result of the addition is invalid, we must return an error.
+ if (!_preparedState->newValue.isValid()) {
+ mb::Element idElem = mb::findFirstChildNamed(root, "_id");
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "Failed to apply $inc operations to current value ("
+ << currentValue.debugString() << ") for document {"
+ << idElem.toString() << "}");
+ }
+ // If the values are identical (same type, same value), then this is a no-op.
+ if (_preparedState->newValue.isIdentical(currentValue)) {
+ _preparedState->noOp = execInfo->noOp = true;
return Status::OK();
}
- Status ModifierInc::apply() const {
- dassert(_preparedState->noOp == false);
+ return Status::OK();
+}
- // If there's no need to create any further field part, the $inc is simply a value
- // assignment.
- if (_preparedState->elemFound.ok() &&
- _preparedState->idxFound == (_fieldRef.numParts() - 1)) {
- return _preparedState->elemFound.setValueSafeNum(_preparedState->newValue);
- }
+Status ModifierInc::apply() const {
+ dassert(_preparedState->noOp == false);
- //
- // Complete document path logic
- //
-
- // Creates the final element that's going to be $set in 'doc'.
- mutablebson::Document& doc = _preparedState->doc;
- StringData lastPart = _fieldRef.getPart(_fieldRef.numParts() - 1);
- mutablebson::Element elemToSet = doc.makeElementSafeNum(lastPart, _preparedState->newValue);
- if (!elemToSet.ok()) {
- return Status(ErrorCodes::InternalError, "can't create new element");
- }
+ // If there's no need to create any further field part, the $inc is simply a value
+ // assignment.
+ if (_preparedState->elemFound.ok() && _preparedState->idxFound == (_fieldRef.numParts() - 1)) {
+ return _preparedState->elemFound.setValueSafeNum(_preparedState->newValue);
+ }
- // Now, we can be in two cases here, as far as attaching the element being set goes:
- // (a) none of the parts in the element's path exist, or (b) some parts of the path
- // exist but not all.
- if (!_preparedState->elemFound.ok()) {
- _preparedState->elemFound = doc.root();
- _preparedState->idxFound = 0;
- }
- else {
- _preparedState->idxFound++;
- }
+ //
+ // Complete document path logic
+ //
- // createPathAt() will complete the path and attach 'elemToSet' at the end of it.
- return pathsupport::createPathAt(_fieldRef,
- _preparedState->idxFound,
- _preparedState->elemFound,
- elemToSet);
+ // Creates the final element that's going to be $set in 'doc'.
+ mutablebson::Document& doc = _preparedState->doc;
+ StringData lastPart = _fieldRef.getPart(_fieldRef.numParts() - 1);
+ mutablebson::Element elemToSet = doc.makeElementSafeNum(lastPart, _preparedState->newValue);
+ if (!elemToSet.ok()) {
+ return Status(ErrorCodes::InternalError, "can't create new element");
}
- Status ModifierInc::log(LogBuilder* logBuilder) const {
+ // Now, we can be in two cases here, as far as attaching the element being set goes:
+ // (a) none of the parts in the element's path exist, or (b) some parts of the path
+ // exist but not all.
+ if (!_preparedState->elemFound.ok()) {
+ _preparedState->elemFound = doc.root();
+ _preparedState->idxFound = 0;
+ } else {
+ _preparedState->idxFound++;
+ }
- dassert(_preparedState->newValue.isValid());
+ // createPathAt() will complete the path and attach 'elemToSet' at the end of it.
+ return pathsupport::createPathAt(
+ _fieldRef, _preparedState->idxFound, _preparedState->elemFound, elemToSet);
+}
- // We'd like to create an entry such as {$set: {<fieldname>: <value>}} under 'logRoot'.
- // We start by creating the {$set: ...} Element.
- mutablebson::Document& doc = logBuilder->getDocument();
+Status ModifierInc::log(LogBuilder* logBuilder) const {
+ dassert(_preparedState->newValue.isValid());
- // Then we create the {<fieldname>: <value>} Element.
- mutablebson::Element logElement = doc.makeElementSafeNum(
- _fieldRef.dottedField(),
- _preparedState->newValue);
+ // We'd like to create an entry such as {$set: {<fieldname>: <value>}} under 'logRoot'.
+ // We start by creating the {$set: ...} Element.
+ mutablebson::Document& doc = logBuilder->getDocument();
- if (!logElement.ok()) {
- return Status(ErrorCodes::InternalError,
- str::stream() << "Could not append entry to "
- << (_mode == MODE_INC ? "$inc" : "$mul")
- << " oplog entry: "
- << "set '" << _fieldRef.dottedField() << "' -> "
- << _preparedState->newValue.debugString() );
- }
+ // Then we create the {<fieldname>: <value>} Element.
+ mutablebson::Element logElement =
+ doc.makeElementSafeNum(_fieldRef.dottedField(), _preparedState->newValue);
- // Now, we attach the {<fieldname>: <value>} Element under the {$set: ...} segment.
- return logBuilder->addToSets(logElement);
+ if (!logElement.ok()) {
+ return Status(ErrorCodes::InternalError,
+ str::stream() << "Could not append entry to "
+ << (_mode == MODE_INC ? "$inc" : "$mul") << " oplog entry: "
+ << "set '" << _fieldRef.dottedField() << "' -> "
+ << _preparedState->newValue.debugString());
}
-} // namespace mongo
+ // Now, we attach the {<fieldname>: <value>} Element under the {$set: ...} segment.
+ return logBuilder->addToSets(logElement);
+}
+
+} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_inc.h b/src/mongo/db/ops/modifier_inc.h
index cfb7bbdf25d..17c76acf88f 100644
--- a/src/mongo/db/ops/modifier_inc.h
+++ b/src/mongo/db/ops/modifier_inc.h
@@ -37,64 +37,57 @@
namespace mongo {
- class LogBuilder;
-
- class ModifierInc : public ModifierInterface {
- MONGO_DISALLOW_COPYING(ModifierInc);
-
- public:
-
- // TODO: This is a shortcut to implementing $mul by hijacking $inc. In the near future,
- // we should consider either pulling $mul into its own operator, or creating a general
- // purpose "numeric binary op" operator. Potentially, that operator could also subsume
- // $bit (thought there are some subtleties, like that $bit can have multiple
- // operations, and doing so with arbirary math operations introduces potential
- // associativity difficulties). At the very least, if this mechanism is retained, then
- // this class should be renamed at some point away from ModifierInc.
- enum ModifierIncMode {
- MODE_INC,
- MODE_MUL
- };
-
- ModifierInc(ModifierIncMode mode = MODE_INC);
- virtual ~ModifierInc();
-
- /**
- * A 'modExpr' is a BSONElement {<fieldname>: <value>} coming from a $inc mod such as
- * {$inc: {<fieldname: <value>}}. init() extracts the field name and the value to be
- * assigned to it from 'modExpr'. It returns OK if successful or a status describing
- * the error.
- */
- virtual Status init(const BSONElement& modExpr, const Options& opts,
- bool* positional = NULL);
-
- /** Evaluates the validity of applying $inc to the identified node, and computes
- * effects, handling upcasting and overflow as necessary.
- */
- virtual Status prepare(mutablebson::Element root,
- StringData matchedField,
- ExecInfo* execInfo);
-
- /** Updates the node passed in prepare with the results of the $inc */
- virtual Status apply() const;
-
- /** Converts the result of the $inc into an equivalent $set under logRoot */
- virtual Status log(LogBuilder* logBuilder) const;
-
- private:
- const ModifierIncMode _mode;
-
- // Access to each component of fieldName that's the target of this mod.
- FieldRef _fieldRef;
-
- // 0 or index for $-positional in _fieldRef.
- size_t _posDollar;
-
- // Element of the $set expression.
- SafeNum _val;
-
- struct PreparedState;
- std::unique_ptr<PreparedState> _preparedState;
- };
-
-} // namespace mongo
+class LogBuilder;
+
+class ModifierInc : public ModifierInterface {
+ MONGO_DISALLOW_COPYING(ModifierInc);
+
+public:
+ // TODO: This is a shortcut to implementing $mul by hijacking $inc. In the near future,
+ // we should consider either pulling $mul into its own operator, or creating a general
+ // purpose "numeric binary op" operator. Potentially, that operator could also subsume
+ // $bit (thought there are some subtleties, like that $bit can have multiple
+ // operations, and doing so with arbirary math operations introduces potential
+ // associativity difficulties). At the very least, if this mechanism is retained, then
+ // this class should be renamed at some point away from ModifierInc.
+ enum ModifierIncMode { MODE_INC, MODE_MUL };
+
+ ModifierInc(ModifierIncMode mode = MODE_INC);
+ virtual ~ModifierInc();
+
+ /**
+ * A 'modExpr' is a BSONElement {<fieldname>: <value>} coming from a $inc mod such as
+ * {$inc: {<fieldname: <value>}}. init() extracts the field name and the value to be
+ * assigned to it from 'modExpr'. It returns OK if successful or a status describing
+ * the error.
+ */
+ virtual Status init(const BSONElement& modExpr, const Options& opts, bool* positional = NULL);
+
+ /** Evaluates the validity of applying $inc to the identified node, and computes
+ * effects, handling upcasting and overflow as necessary.
+ */
+ virtual Status prepare(mutablebson::Element root, StringData matchedField, ExecInfo* execInfo);
+
+ /** Updates the node passed in prepare with the results of the $inc */
+ virtual Status apply() const;
+
+ /** Converts the result of the $inc into an equivalent $set under logRoot */
+ virtual Status log(LogBuilder* logBuilder) const;
+
+private:
+ const ModifierIncMode _mode;
+
+ // Access to each component of fieldName that's the target of this mod.
+ FieldRef _fieldRef;
+
+ // 0 or index for $-positional in _fieldRef.
+ size_t _posDollar;
+
+ // Element of the $set expression.
+ SafeNum _val;
+
+ struct PreparedState;
+ std::unique_ptr<PreparedState> _preparedState;
+};
+
+} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_inc_test.cpp b/src/mongo/db/ops/modifier_inc_test.cpp
index 8a97bc490e7..52ab8d0e41c 100644
--- a/src/mongo/db/ops/modifier_inc_test.cpp
+++ b/src/mongo/db/ops/modifier_inc_test.cpp
@@ -42,483 +42,482 @@
namespace {
- using mongo::BSONObj;
- using mongo::LogBuilder;
- using mongo::ModifierInc;
- using mongo::ModifierInterface;
- using mongo::NumberInt;
- using mongo::Status;
- using mongo::StringData;
- using mongo::fromjson;
- using mongo::mutablebson::ConstElement;
- using mongo::mutablebson::Document;
- using mongo::mutablebson::Element;
- using mongo::mutablebson::countChildren;
-
- /** Helper to build and manipulate a $inc mod. */
- class Mod {
- public:
-
- explicit Mod(BSONObj modObj)
- : _modObj(modObj)
- , _mod(mongoutils::str::equals(modObj.firstElement().fieldName(), "$mul") ?
- ModifierInc::MODE_MUL : ModifierInc::MODE_INC) {
- StringData modName = modObj.firstElement().fieldName();
- ASSERT_OK(_mod.init(_modObj[modName].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
-
- 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);
- }
-
- ModifierInc& mod() { return _mod; }
-
- private:
- BSONObj _modObj;
- ModifierInc _mod;
- };
-
- TEST(Init, FailToInitWithInvalidValue) {
- BSONObj modObj;
- ModifierInc mod;
-
- // String is an invalid increment argument
- modObj = fromjson("{ $inc : { a : '' } }");
- ASSERT_NOT_OK(mod.init(modObj["$inc"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
-
- // Object is an invalid increment argument
- modObj = fromjson("{ $inc : { a : {} } }");
- ASSERT_NOT_OK(mod.init(modObj["$inc"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
-
- // Array is an invalid increment argument
- modObj = fromjson("{ $inc : { a : [] } }");
- ASSERT_NOT_OK(mod.init(modObj["$inc"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
+using mongo::BSONObj;
+using mongo::LogBuilder;
+using mongo::ModifierInc;
+using mongo::ModifierInterface;
+using mongo::NumberInt;
+using mongo::Status;
+using mongo::StringData;
+using mongo::fromjson;
+using mongo::mutablebson::ConstElement;
+using mongo::mutablebson::Document;
+using mongo::mutablebson::Element;
+using mongo::mutablebson::countChildren;
+
+/** Helper to build and manipulate a $inc mod. */
+class Mod {
+public:
+ explicit Mod(BSONObj modObj)
+ : _modObj(modObj),
+ _mod(mongoutils::str::equals(modObj.firstElement().fieldName(), "$mul")
+ ? ModifierInc::MODE_MUL
+ : ModifierInc::MODE_INC) {
+ StringData modName = modObj.firstElement().fieldName();
+ ASSERT_OK(_mod.init(_modObj[modName].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
}
- TEST(Init, InitParsesNumberInt) {
- Mod incMod(BSON("$inc" << BSON("a" << static_cast<int>(1))));
+ Status prepare(Element root, StringData matchedField, ModifierInterface::ExecInfo* execInfo) {
+ return _mod.prepare(root, matchedField, execInfo);
}
- TEST(Init, InitParsesNumberLong) {
- Mod incMod(BSON("$inc" << BSON("a" << static_cast<long long>(1))));
+ Status apply() const {
+ return _mod.apply();
}
- TEST(Init, InitParsesNumberDouble) {
- Mod incMod(BSON("$inc" << BSON("a" << 1.0)));
+ Status log(LogBuilder* logBuilder) const {
+ return _mod.log(logBuilder);
}
- TEST(SimpleMod, PrepareSimpleOK) {
- Document doc(fromjson("{ a : 1 }"));
- Mod incMod(fromjson("{ $inc: { a : 1 }}"));
-
- ModifierInterface::ExecInfo execInfo;
-
- ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_FALSE(execInfo.noOp);
- }
-
- TEST(SimpleMod, PrepareSimpleNonNumericObject) {
- Document doc(fromjson("{ a : {} }"));
- Mod incMod(fromjson("{ $inc: { a : 1 }}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(incMod.prepare(doc.root(), "", &execInfo));
- }
-
- TEST(SimpleMod, PrepareSimpleNonNumericArray) {
-
- Document doc(fromjson("{ a : [] }"));
- Mod incMod(fromjson("{ $inc: { a : 1 }}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(incMod.prepare(doc.root(), "", &execInfo));
- }
-
- TEST(SimpleMod, PrepareSimpleNonNumericString) {
- Document doc(fromjson("{ a : '' }"));
- Mod incMod(fromjson("{ $inc: { a : 1 }}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(incMod.prepare(doc.root(), "", &execInfo));
- }
-
- TEST(SimpleMod, ApplyAndLogEmptyDocument) {
- Document doc(fromjson("{}"));
- Mod incMod(fromjson("{ $inc: { a : 1 }}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(incMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 1 }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(incMod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 1 } }"), logDoc);
- }
-
- TEST(SimpleMod, LogWithoutApplyEmptyDocument) {
- Document doc(fromjson("{}"));
- Mod incMod(fromjson("{ $inc: { a : 1 }}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(incMod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 1 } }"), logDoc);
- }
-
- TEST(SimpleMod, ApplyAndLogSimpleDocument) {
- Document doc(fromjson("{ a : 2 }"));
- Mod incMod(fromjson("{ $inc: { a : 1 }}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(incMod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 3 }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(incMod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 3 } }"), logDoc);
+ ModifierInc& mod() {
+ return _mod;
}
- TEST(DottedMod, ApplyAndLogSimpleDocument) {
- Document doc(fromjson("{ a : { b : 2 } }"));
- Mod incMod(fromjson("{ $inc: { 'a.b' : 1 } }"));
+private:
+ BSONObj _modObj;
+ ModifierInc _mod;
+};
+
+TEST(Init, FailToInitWithInvalidValue) {
+ BSONObj modObj;
+ ModifierInc mod;
+
+ // String is an invalid increment argument
+ modObj = fromjson("{ $inc : { a : '' } }");
+ ASSERT_NOT_OK(mod.init(modObj["$inc"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(incMod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : { b : 3 } }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(incMod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { 'a.b' : 3 } }"), logDoc);
- }
-
- TEST(InPlace, IntToInt) {
- Document doc(BSON("a" << static_cast<int>(1)));
- Mod incMod(BSON("$inc" << BSON("a" << static_cast<int>(1))));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- }
-
- TEST(InPlace, LongToLong) {
- Document doc(BSON("a" << static_cast<long long>(1)));
- Mod incMod(BSON("$inc" << BSON("a" << static_cast<long long>(1))));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- }
-
- TEST(InPlace, DoubleToDouble) {
- Document doc(BSON("a" << 1.0));
- Mod incMod(BSON("$inc" << BSON("a" << 1.0 )));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- }
-
- TEST(NoOp, Int) {
- Document doc(BSON("a" << static_cast<int>(1)));
- Mod incMod(BSON("$inc" << BSON("a" << static_cast<int>(0))));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
- }
-
- TEST(NoOp, Long) {
- Document doc(BSON("a" << static_cast<long long>(1)));
- Mod incMod(BSON("$inc" << BSON("a" << static_cast<long long>(0))));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
- }
-
- TEST(NoOp, Double) {
- Document doc(BSON("a" << 1.0));
- Mod incMod(BSON("$inc" << BSON("a" << 0.0)));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
- }
-
- TEST(Upcasting, UpcastIntToLong) {
- // Checks that $inc : NumberLong(0) turns a NumberInt into a NumberLong and logs it
- // correctly.
- Document doc(BSON("a" << static_cast<int>(1)));
- ASSERT_EQUALS(mongo::NumberInt, doc.root()["a"].getType());
-
- Mod incMod(BSON("$inc" << BSON("a" << static_cast<long long>(0))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(incMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 1 }"), doc);
- ASSERT_EQUALS(mongo::NumberLong, doc.root()["a"].getType());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(incMod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 1 } }"), logDoc);
- ASSERT_EQUALS(mongo::NumberLong, logDoc.root()["$set"]["a"].getType());
- }
-
- TEST(Upcasting, UpcastIntToDouble) {
- // Checks that $inc : 0.0 turns a NumberInt into a NumberDouble and logs it
- // correctly.
- Document doc(BSON("a" << static_cast<int>(1)));
- ASSERT_EQUALS(mongo::NumberInt, doc.root()["a"].getType());
-
- Mod incMod(fromjson("{ $inc : { a : 0.0 } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(incMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 1.0 }"), doc);
- ASSERT_EQUALS(mongo::NumberDouble, doc.root()["a"].getType());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(incMod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 1.0 } }"), logDoc);
- ASSERT_EQUALS(mongo::NumberDouble, logDoc.root()["$set"]["a"].getType());
- }
+ // Object is an invalid increment argument
+ modObj = fromjson("{ $inc : { a : {} } }");
+ ASSERT_NOT_OK(mod.init(modObj["$inc"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+
+ // Array is an invalid increment argument
+ modObj = fromjson("{ $inc : { a : [] } }");
+ ASSERT_NOT_OK(mod.init(modObj["$inc"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
+
+TEST(Init, InitParsesNumberInt) {
+ Mod incMod(BSON("$inc" << BSON("a" << static_cast<int>(1))));
+}
+
+TEST(Init, InitParsesNumberLong) {
+ Mod incMod(BSON("$inc" << BSON("a" << static_cast<long long>(1))));
+}
+
+TEST(Init, InitParsesNumberDouble) {
+ Mod incMod(BSON("$inc" << BSON("a" << 1.0)));
+}
+
+TEST(SimpleMod, PrepareSimpleOK) {
+ Document doc(fromjson("{ a : 1 }"));
+ Mod incMod(fromjson("{ $inc: { a : 1 }}"));
+
+ ModifierInterface::ExecInfo execInfo;
+
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_FALSE(execInfo.noOp);
+}
+
+TEST(SimpleMod, PrepareSimpleNonNumericObject) {
+ Document doc(fromjson("{ a : {} }"));
+ Mod incMod(fromjson("{ $inc: { a : 1 }}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_NOT_OK(incMod.prepare(doc.root(), "", &execInfo));
+}
+
+TEST(SimpleMod, PrepareSimpleNonNumericArray) {
+ Document doc(fromjson("{ a : [] }"));
+ Mod incMod(fromjson("{ $inc: { a : 1 }}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_NOT_OK(incMod.prepare(doc.root(), "", &execInfo));
+}
+
+TEST(SimpleMod, PrepareSimpleNonNumericString) {
+ Document doc(fromjson("{ a : '' }"));
+ Mod incMod(fromjson("{ $inc: { a : 1 }}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_NOT_OK(incMod.prepare(doc.root(), "", &execInfo));
+}
+
+TEST(SimpleMod, ApplyAndLogEmptyDocument) {
+ Document doc(fromjson("{}"));
+ Mod incMod(fromjson("{ $inc: { a : 1 }}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(incMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : 1 }"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(incMod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : 1 } }"), logDoc);
+}
+
+TEST(SimpleMod, LogWithoutApplyEmptyDocument) {
+ Document doc(fromjson("{}"));
+ Mod incMod(fromjson("{ $inc: { a : 1 }}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(incMod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : 1 } }"), logDoc);
+}
+
+TEST(SimpleMod, ApplyAndLogSimpleDocument) {
+ Document doc(fromjson("{ a : 2 }"));
+ Mod incMod(fromjson("{ $inc: { a : 1 }}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(incMod.apply());
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : 3 }"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(incMod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : 3 } }"), logDoc);
+}
+
+TEST(DottedMod, ApplyAndLogSimpleDocument) {
+ Document doc(fromjson("{ a : { b : 2 } }"));
+ Mod incMod(fromjson("{ $inc: { 'a.b' : 1 } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(incMod.apply());
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : { b : 3 } }"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(incMod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { 'a.b' : 3 } }"), logDoc);
+}
+
+TEST(InPlace, IntToInt) {
+ Document doc(BSON("a" << static_cast<int>(1)));
+ Mod incMod(BSON("$inc" << BSON("a" << static_cast<int>(1))));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+}
+
+TEST(InPlace, LongToLong) {
+ Document doc(BSON("a" << static_cast<long long>(1)));
+ Mod incMod(BSON("$inc" << BSON("a" << static_cast<long long>(1))));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+}
+
+TEST(InPlace, DoubleToDouble) {
+ Document doc(BSON("a" << 1.0));
+ Mod incMod(BSON("$inc" << BSON("a" << 1.0)));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+}
+
+TEST(NoOp, Int) {
+ Document doc(BSON("a" << static_cast<int>(1)));
+ Mod incMod(BSON("$inc" << BSON("a" << static_cast<int>(0))));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.noOp);
+}
+
+TEST(NoOp, Long) {
+ Document doc(BSON("a" << static_cast<long long>(1)));
+ Mod incMod(BSON("$inc" << BSON("a" << static_cast<long long>(0))));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.noOp);
+}
+
+TEST(NoOp, Double) {
+ Document doc(BSON("a" << 1.0));
+ Mod incMod(BSON("$inc" << BSON("a" << 0.0)));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.noOp);
+}
+
+TEST(Upcasting, UpcastIntToLong) {
+ // Checks that $inc : NumberLong(0) turns a NumberInt into a NumberLong and logs it
+ // correctly.
+ Document doc(BSON("a" << static_cast<int>(1)));
+ ASSERT_EQUALS(mongo::NumberInt, doc.root()["a"].getType());
+
+ Mod incMod(BSON("$inc" << BSON("a" << static_cast<long long>(0))));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(incMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : 1 }"), doc);
+ ASSERT_EQUALS(mongo::NumberLong, doc.root()["a"].getType());
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(incMod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : 1 } }"), logDoc);
+ ASSERT_EQUALS(mongo::NumberLong, logDoc.root()["$set"]["a"].getType());
+}
+
+TEST(Upcasting, UpcastIntToDouble) {
+ // Checks that $inc : 0.0 turns a NumberInt into a NumberDouble and logs it
+ // correctly.
+ Document doc(BSON("a" << static_cast<int>(1)));
+ ASSERT_EQUALS(mongo::NumberInt, doc.root()["a"].getType());
+
+ Mod incMod(fromjson("{ $inc : { a : 0.0 } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(incMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : 1.0 }"), doc);
+ ASSERT_EQUALS(mongo::NumberDouble, doc.root()["a"].getType());
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(incMod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : 1.0 } }"), logDoc);
+ ASSERT_EQUALS(mongo::NumberDouble, logDoc.root()["$set"]["a"].getType());
+}
+
+TEST(Upcasting, UpcastLongToDouble) {
+ // Checks that $inc : 0.0 turns a NumberLong into a NumberDouble and logs it
+ // correctly.
+ Document doc(BSON("a" << static_cast<long long>(1)));
+ ASSERT_EQUALS(mongo::NumberLong, doc.root()["a"].getType());
+
+ Mod incMod(fromjson("{ $inc : { a : 0.0 } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(incMod.apply());
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : 1.0 }"), doc);
+ ASSERT_EQUALS(mongo::NumberDouble, doc.root()["a"].getType());
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(incMod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : 1.0 } }"), logDoc);
+ ASSERT_EQUALS(mongo::NumberDouble, logDoc.root()["$set"]["a"].getType());
+}
+
+TEST(Upcasting, DoublesStayDoubles) {
+ // Checks that $inc : 0 doesn't change a NumberDouble away from double
+ Document doc(fromjson("{ a : 1.0 }"));
+ ASSERT_EQUALS(mongo::NumberDouble, doc.root()["a"].getType());
+
+ Mod incMod(fromjson("{ $inc : { a : 1 } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(incMod.apply());
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : 2.0 }"), doc);
+ ASSERT_EQUALS(mongo::NumberDouble, doc.root()["a"].getType());
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(incMod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : 2.0 } }"), logDoc);
+ ASSERT_EQUALS(mongo::NumberDouble, logDoc.root()["$set"]["a"].getType());
+}
+
+// The only interesting overflow cases are int->long via increment: we never overflow to
+// double, and we never decrease precision on decrement.
+
+TEST(Spilling, OverflowIntToLong) {
+ const int initial_value = std::numeric_limits<int32_t>::max();
+
+ Document doc(BSON("a" << static_cast<int>(initial_value)));
+ ASSERT_EQUALS(mongo::NumberInt, doc.root()["a"].getType());
+
+ Mod incMod(fromjson("{ $inc : { a : 1 } }"));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ const long long target_value = static_cast<long long>(initial_value) + 1;
+
+ ASSERT_OK(incMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(BSON("a" << target_value), doc);
+}
+
+TEST(Spilling, UnderflowIntToLong) {
+ const int initial_value = std::numeric_limits<int32_t>::min();
+
+ Document doc(BSON("a" << static_cast<int>(initial_value)));
+ ASSERT_EQUALS(mongo::NumberInt, doc.root()["a"].getType());
+
+ Mod incMod(fromjson("{ $inc : { a : -1 } }"));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ const long long target_value = static_cast<long long>(initial_value) - 1;
+
+ ASSERT_OK(incMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(BSON("a" << target_value), doc);
+}
+
+TEST(Lifecycle, IncModCanBeReused) {
+ Document doc1(fromjson("{ a : 1 }"));
+ Document doc2(fromjson("{ a : 1 }"));
+
+ Mod incMod(fromjson("{ $inc: { a : 1 }}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc1.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(incMod.apply());
+ ASSERT_TRUE(doc1.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : 2 }"), doc1);
+
+ ASSERT_OK(incMod.prepare(doc2.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(incMod.apply());
+ ASSERT_TRUE(doc2.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : 2 }"), doc2);
+}
+
+// Given the current implementation of $mul, we really only need one test for
+// $mul. However, in the future, we should probably write additional ones, or, perhaps find
+// a way to run all the obove tests in both modes.
+TEST(Multiplication, ApplyAndLogSimpleDocument) {
+ Document doc(fromjson("{ a : { b : 2 } }"));
+ Mod incMod(fromjson("{ $mul: { 'a.b' : 3 } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(incMod.apply());
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : { b : 6 } }"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(incMod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { 'a.b' : 6 } }"), logDoc);
+}
- TEST(Upcasting, UpcastLongToDouble) {
- // Checks that $inc : 0.0 turns a NumberLong into a NumberDouble and logs it
- // correctly.
- Document doc(BSON("a" << static_cast<long long>(1)));
- ASSERT_EQUALS(mongo::NumberLong, doc.root()["a"].getType());
-
- Mod incMod(fromjson("{ $inc : { a : 0.0 } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(incMod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 1.0 }"), doc);
- ASSERT_EQUALS(mongo::NumberDouble, doc.root()["a"].getType());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(incMod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 1.0 } }"), logDoc);
- ASSERT_EQUALS(mongo::NumberDouble, logDoc.root()["$set"]["a"].getType());
- }
-
- TEST(Upcasting, DoublesStayDoubles) {
- // Checks that $inc : 0 doesn't change a NumberDouble away from double
- Document doc(fromjson("{ a : 1.0 }"));
- ASSERT_EQUALS(mongo::NumberDouble, doc.root()["a"].getType());
-
- Mod incMod(fromjson("{ $inc : { a : 1 } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(incMod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 2.0 }"), doc);
- ASSERT_EQUALS(mongo::NumberDouble, doc.root()["a"].getType());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(incMod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 2.0 } }"), logDoc);
- ASSERT_EQUALS(mongo::NumberDouble, logDoc.root()["$set"]["a"].getType());
- }
-
- // The only interesting overflow cases are int->long via increment: we never overflow to
- // double, and we never decrease precision on decrement.
-
- TEST(Spilling, OverflowIntToLong) {
- const int initial_value = std::numeric_limits<int32_t>::max();
-
- Document doc(BSON("a" << static_cast<int>(initial_value)));
- ASSERT_EQUALS(mongo::NumberInt, doc.root()["a"].getType());
-
- Mod incMod(fromjson("{ $inc : { a : 1 } }"));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- const long long target_value = static_cast<long long>(initial_value) + 1;
-
- ASSERT_OK(incMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(BSON("a" << target_value), doc);
- }
-
- TEST(Spilling, UnderflowIntToLong) {
- const int initial_value = std::numeric_limits<int32_t>::min();
-
- Document doc(BSON("a" << static_cast<int>(initial_value)));
- ASSERT_EQUALS(mongo::NumberInt, doc.root()["a"].getType());
-
- Mod incMod(fromjson("{ $inc : { a : -1 } }"));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- const long long target_value = static_cast<long long>(initial_value) - 1;
-
- ASSERT_OK(incMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(BSON("a" << target_value), doc);
- }
-
- TEST(Lifecycle, IncModCanBeReused) {
- Document doc1(fromjson("{ a : 1 }"));
- Document doc2(fromjson("{ a : 1 }"));
-
- Mod incMod(fromjson("{ $inc: { a : 1 }}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(incMod.prepare(doc1.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(incMod.apply());
- ASSERT_TRUE(doc1.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 2 }"), doc1);
-
- ASSERT_OK(incMod.prepare(doc2.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(incMod.apply());
- ASSERT_TRUE(doc2.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 2 }"), doc2);
- }
-
- // Given the current implementation of $mul, we really only need one test for
- // $mul. However, in the future, we should probably write additional ones, or, perhaps find
- // a way to run all the obove tests in both modes.
- TEST(Multiplication, ApplyAndLogSimpleDocument) {
- Document doc(fromjson("{ a : { b : 2 } }"));
- Mod incMod(fromjson("{ $mul: { 'a.b' : 3 } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(incMod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : { b : 6 } }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(incMod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { 'a.b' : 6 } }"), logDoc);
- }
-
- TEST(Multiplication, ApplyAndLogMissingElement) {
- Document doc(fromjson("{ a : 0 }"));
- Mod incMod(fromjson("{ $mul : { b : 3 } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(incMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 0, b : 0 }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(incMod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { b : 0 } }"), logDoc);
- }
-
- TEST(Multiplication, ApplyMissingElementInt) {
- const int int_zero = 0;
- const int int_three = 3;
-
- Document doc(BSON("a" << int_zero));
- Mod incMod(BSON("$mul" << BSON("b" << int_three)));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(incMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(BSON("a" << int_zero << "b" << int_zero), doc);
- ASSERT_EQUALS(mongo::NumberInt, doc.root().rightChild().getType());
- }
-
- TEST(Multiplication, ApplyMissingElementLongLong) {
- const long long ll_zero = 0;
- const long long ll_three = 3;
-
- Document doc(BSON("a" << ll_zero));
- Mod incMod(BSON("$mul" << BSON("b" << ll_three)));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(incMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(BSON("a" << ll_zero << "b" << ll_zero), doc);
- ASSERT_EQUALS(mongo::NumberLong, doc.root().rightChild().getType());
- }
-
- TEST(Multiplication, ApplyMissingElementDouble) {
- const double double_zero = 0;
- const double double_three = 3;
-
- Document doc(BSON("a" << double_zero));
- Mod incMod(BSON("$mul" << BSON("b" << double_three)));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(incMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(BSON("a" << double_zero << "b" << 0), doc);
- ASSERT_EQUALS(mongo::NumberDouble, doc.root().rightChild().getType());
- }
+TEST(Multiplication, ApplyAndLogMissingElement) {
+ Document doc(fromjson("{ a : 0 }"));
+ Mod incMod(fromjson("{ $mul : { b : 3 } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(incMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : 0, b : 0 }"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(incMod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { b : 0 } }"), logDoc);
+}
-} // namespace
+TEST(Multiplication, ApplyMissingElementInt) {
+ const int int_zero = 0;
+ const int int_three = 3;
+
+ Document doc(BSON("a" << int_zero));
+ Mod incMod(BSON("$mul" << BSON("b" << int_three)));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(incMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(BSON("a" << int_zero << "b" << int_zero), doc);
+ ASSERT_EQUALS(mongo::NumberInt, doc.root().rightChild().getType());
+}
+
+TEST(Multiplication, ApplyMissingElementLongLong) {
+ const long long ll_zero = 0;
+ const long long ll_three = 3;
+
+ Document doc(BSON("a" << ll_zero));
+ Mod incMod(BSON("$mul" << BSON("b" << ll_three)));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(incMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(BSON("a" << ll_zero << "b" << ll_zero), doc);
+ ASSERT_EQUALS(mongo::NumberLong, doc.root().rightChild().getType());
+}
+
+TEST(Multiplication, ApplyMissingElementDouble) {
+ const double double_zero = 0;
+ const double double_three = 3;
+
+ Document doc(BSON("a" << double_zero));
+ Mod incMod(BSON("$mul" << BSON("b" << double_three)));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(incMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(BSON("a" << double_zero << "b" << 0), doc);
+ ASSERT_EQUALS(mongo::NumberDouble, doc.root().rightChild().getType());
+}
+
+} // namespace
diff --git a/src/mongo/db/ops/modifier_interface.h b/src/mongo/db/ops/modifier_interface.h
index 3574615e6f4..eba8b6c324d 100644
--- a/src/mongo/db/ops/modifier_interface.h
+++ b/src/mongo/db/ops/modifier_interface.h
@@ -36,160 +36,167 @@
namespace mongo {
- class LogBuilder;
+class LogBuilder;
+/**
+ * Abstract base class for update "modifiers" (a.k.a "$ operators"). To create a new
+ * operator, implement a new derived class.
+ *
+ * A typical call sequence for the class is:
+ *
+ * + init() with the mod arguments
+ *
+ * + For each document that is being touched on that update, the following methods are
+ * going to be called once for that document and in the order the calls appear here.
+ *
+ * + prepare() to check if mod is viable over the document
+ *
+ * + apply(), effectively computing the update
+ *
+ * + log() registering the change in the log for replication purposes
+ *
+ * Again, a modifier implementation may rely on these last three calls being made and in
+ * that particular order and therefore can keep and reuse state between these calls, when
+ * appropriate.
+ *
+ * TODO:
+ * For a reference implementation, see modifier_identity.{h,cpp} used in tests.
+ */
+class ModifierInterface {
+public:
+ virtual ~ModifierInterface() {}
+
+ struct Options;
/**
- * Abstract base class for update "modifiers" (a.k.a "$ operators"). To create a new
- * operator, implement a new derived class.
+ * Returns OK and extracts the parameters for this given mod from 'modExpr'. For
+ * instance, for a $inc, extracts the increment value. The init() method would be
+ * called only once per operand, that is, if a { $inc: { a: 1, b: 1 } } is issued,
+ * there would be one instance of the operator working on 'a' and one on 'b'. In each
+ * case, init() would be called once with the respective bson element.
*
- * A typical call sequence for the class is:
+ * If 'modExpr' is invalid, returns an error status with a reason description.
*
- * + init() with the mod arguments
+ * The optional bool out parameter 'positional', if provided, will be set to 'true' if
+ * the mod requires matched field details to be provided when calling 'prepare'. The
+ * field is optional since this is a hint to the caller about what work is needed to
+ * correctly invoke 'prepare'. It is always legal to provide any match details
+ * unconditionally. The value set in 'positional' if any, is only meaningful if 'init'
+ * returns an OK status.
*
- * + For each document that is being touched on that update, the following methods are
- * going to be called once for that document and in the order the calls appear here.
+ * Note:
*
- * + prepare() to check if mod is viable over the document
+ * + An operator may assume the modExpr passed here will be unchanged throughout all
+ * the mod object lifetime and also that the modExrp's lifetime exceeds the life
+ * time of this mod. Therefore, taking references to elements inside modExpr is
+ * valid.
+ */
+ virtual Status init(const BSONElement& modExpr,
+ const Options& opts,
+ bool* positional = NULL) = 0;
+
+ /**
+ * Returns OK if it would be correct to apply this mod over the document 'root' (e.g, if
+ * we're $inc-ing a field, is that field numeric in the current doc?).
*
- * + apply(), effectively computing the update
+ * If the field this mod is targeted to contains a $-positional parameter, that value
+ * can be bound with 'matchedField', passed by the caller.
*
- * + log() registering the change in the log for replication purposes
+ * In addition, the call also identifies which fields(s) of 'root' the mod is interested
+ * in changing (note that the modifier may want to add a field that's not present in
+ * the document). The call also determines whether it could modify the document in
+ * place and whether it is a no-op for the given document. All this information is in
+ * the passed 'execInfo', which is filled inside the call.
*
- * Again, a modifier implementation may rely on these last three calls being made and in
- * that particular order and therefore can keep and reuse state between these calls, when
- * appropriate.
+ * If the mod cannot be applied over 'root', returns an error status with a reason
+ * description.
*
- * TODO:
- * For a reference implementation, see modifier_identity.{h,cpp} used in tests.
+ * Note that you must provide a meaningful 'matchedField' here, unless 'init' set
+ * 'positional' to 'false', in which case you may pass an empty StringData object.
*/
- class ModifierInterface {
- public:
- virtual ~ModifierInterface() { }
-
- struct Options;
- /**
- * Returns OK and extracts the parameters for this given mod from 'modExpr'. For
- * instance, for a $inc, extracts the increment value. The init() method would be
- * called only once per operand, that is, if a { $inc: { a: 1, b: 1 } } is issued,
- * there would be one instance of the operator working on 'a' and one on 'b'. In each
- * case, init() would be called once with the respective bson element.
- *
- * If 'modExpr' is invalid, returns an error status with a reason description.
- *
- * The optional bool out parameter 'positional', if provided, will be set to 'true' if
- * the mod requires matched field details to be provided when calling 'prepare'. The
- * field is optional since this is a hint to the caller about what work is needed to
- * correctly invoke 'prepare'. It is always legal to provide any match details
- * unconditionally. The value set in 'positional' if any, is only meaningful if 'init'
- * returns an OK status.
- *
- * Note:
- *
- * + An operator may assume the modExpr passed here will be unchanged throughout all
- * the mod object lifetime and also that the modExrp's lifetime exceeds the life
- * time of this mod. Therefore, taking references to elements inside modExpr is
- * valid.
- */
- virtual Status init(const BSONElement& modExpr, const Options& opts,
- bool* positional = NULL) = 0;
-
- /**
- * Returns OK if it would be correct to apply this mod over the document 'root' (e.g, if
- * we're $inc-ing a field, is that field numeric in the current doc?).
- *
- * If the field this mod is targeted to contains a $-positional parameter, that value
- * can be bound with 'matchedField', passed by the caller.
- *
- * In addition, the call also identifies which fields(s) of 'root' the mod is interested
- * in changing (note that the modifier may want to add a field that's not present in
- * the document). The call also determines whether it could modify the document in
- * place and whether it is a no-op for the given document. All this information is in
- * the passed 'execInfo', which is filled inside the call.
- *
- * If the mod cannot be applied over 'root', returns an error status with a reason
- * description.
- *
- * Note that you must provide a meaningful 'matchedField' here, unless 'init' set
- * 'positional' to 'false', in which case you may pass an empty StringData object.
- */
- struct ExecInfo;
- virtual Status prepare(mutablebson::Element root,
- StringData matchedField,
- /* IN-OUT */ ExecInfo* execInfo) = 0;
-
- /**
- * Returns OK and modifies (or adds) an element (or elements) from the 'root' passed on
- * the prepareMod call. This may act on multiple fields but should only be called once
- * per operator.
- *
- * For this call to be issued, the call to 'prepareElem' must have necessarily turned
- * off 'ExecInfo.noOp', ie this mod over this document is not a no-op.
- *
- * If the mod could not be applied, returns an error status with a reason description.
- */
- virtual Status apply() const = 0 ;
-
- /**
- * Returns OK and records the result of this mod in the provided LogBuilder. The mod
- * must have kept enough state to be able to produce the log record (see idempotency
- * note below). This call may be issued even if apply() was not.
- *
- * If the mod could not be logged, returns an error status with a reason description.
- *
- * Idempotency Note:
- *
- * + The modifier must log a mod that is idempotent, ie, applying it more than once
- * to a base collection would produce the same result as applying it only once. For
- * example, a $inc can be switched to a $set for the resulting incremented value,
- * for logging purposes. An array based operator may check the contents of the
- * array before operating on it.
- */
- virtual Status log(LogBuilder* logBuilder) const = 0;
- };
+ struct ExecInfo;
+ virtual Status prepare(mutablebson::Element root,
+ StringData matchedField,
+ /* IN-OUT */ ExecInfo* execInfo) = 0;
/**
- * Options used to control Modifier behavior
+ * Returns OK and modifies (or adds) an element (or elements) from the 'root' passed on
+ * the prepareMod call. This may act on multiple fields but should only be called once
+ * per operator.
+ *
+ * For this call to be issued, the call to 'prepareElem' must have necessarily turned
+ * off 'ExecInfo.noOp', ie this mod over this document is not a no-op.
+ *
+ * If the mod could not be applied, returns an error status with a reason description.
*/
- struct ModifierInterface::Options {
- Options() : fromReplication(false), enforceOkForStorage(true) {}
- Options(bool repl, bool ofs) : fromReplication(repl), enforceOkForStorage(ofs) {}
+ virtual Status apply() const = 0;
- static Options normal() { return Options(false, true); }
- static Options fromRepl() { return Options(true, false); }
- static Options unchecked() { return Options(false, false); }
-
- bool fromReplication;
- bool enforceOkForStorage;
- };
+ /**
+ * Returns OK and records the result of this mod in the provided LogBuilder. The mod
+ * must have kept enough state to be able to produce the log record (see idempotency
+ * note below). This call may be issued even if apply() was not.
+ *
+ * If the mod could not be logged, returns an error status with a reason description.
+ *
+ * Idempotency Note:
+ *
+ * + The modifier must log a mod that is idempotent, ie, applying it more than once
+ * to a base collection would produce the same result as applying it only once. For
+ * example, a $inc can be switched to a $set for the resulting incremented value,
+ * for logging purposes. An array based operator may check the contents of the
+ * array before operating on it.
+ */
+ virtual Status log(LogBuilder* logBuilder) const = 0;
+};
- struct ModifierInterface::ExecInfo {
- static const int MAX_NUM_FIELDS = 2;
+/**
+ * Options used to control Modifier behavior
+ */
+struct ModifierInterface::Options {
+ Options() : fromReplication(false), enforceOkForStorage(true) {}
+ Options(bool repl, bool ofs) : fromReplication(repl), enforceOkForStorage(ofs) {}
+
+ static Options normal() {
+ return Options(false, true);
+ }
+ static Options fromRepl() {
+ return Options(true, false);
+ }
+ static Options unchecked() {
+ return Options(false, false);
+ }
+
+ bool fromReplication;
+ bool enforceOkForStorage;
+};
+
+struct ModifierInterface::ExecInfo {
+ static const int MAX_NUM_FIELDS = 2;
- /**
- * An update mod may specify that it wishes to the applied only if the context
- * of the update turns out a certain way.
- */
- enum UpdateContext {
- // This mod wants to be applied only if the update turns out to be an insert.
- INSERT_CONTEXT,
+ /**
+ * An update mod may specify that it wishes to the applied only if the context
+ * of the update turns out a certain way.
+ */
+ enum UpdateContext {
+ // This mod wants to be applied only if the update turns out to be an insert.
+ INSERT_CONTEXT,
- // This mod wants to be applied only if the update is not an insert.
- UPDATE_CONTEXT,
+ // This mod wants to be applied only if the update is not an insert.
+ UPDATE_CONTEXT,
- // This mod doesn't care if the update will be an update or an upsert.
- ANY_CONTEXT
- };
+ // This mod doesn't care if the update will be an update or an upsert.
+ ANY_CONTEXT
+ };
- ExecInfo() : noOp(false), context(ANY_CONTEXT) {
- for (int i = 0; i < MAX_NUM_FIELDS; i++) {
- fieldRef[i] = NULL;
- }
+ ExecInfo() : noOp(false), context(ANY_CONTEXT) {
+ for (int i = 0; i < MAX_NUM_FIELDS; i++) {
+ fieldRef[i] = NULL;
}
+ }
- // The fields of concern to the driver: no other op may modify the fields listed here.
- FieldRef* fieldRef[MAX_NUM_FIELDS]; // not owned here
- bool noOp;
- UpdateContext context;
- };
+ // The fields of concern to the driver: no other op may modify the fields listed here.
+ FieldRef* fieldRef[MAX_NUM_FIELDS]; // not owned here
+ bool noOp;
+ UpdateContext context;
+};
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_object_replace.cpp b/src/mongo/db/ops/modifier_object_replace.cpp
index 5ebbb4bd342..700f111e5b3 100644
--- a/src/mongo/db/ops/modifier_object_replace.cpp
+++ b/src/mongo/db/ops/modifier_object_replace.cpp
@@ -36,161 +36,151 @@
namespace mongo {
- namespace str = mongoutils::str;
-
- namespace {
- const char idFieldName[] = "_id";
-
- Status fixupTimestamps( const BSONObj& obj ) {
- BSONObjIterator i(obj);
- while (i.more()) {
- BSONElement e = i.next();
-
- // Skip _id field -- we do not replace it
- if (e.type() == bsonTimestamp && e.fieldNameStringData() != idFieldName) {
- // TODO(emilkie): This is not endian-safe.
- unsigned long long &timestamp =
- *(reinterpret_cast<unsigned long long*>(
- const_cast<char *>(e.value())));
- if (timestamp == 0) {
- // performance note, this locks a mutex:
- Timestamp ts(getNextGlobalTimestamp());
- timestamp = ts.asULL();
- }
- }
+namespace str = mongoutils::str;
+
+namespace {
+const char idFieldName[] = "_id";
+
+Status fixupTimestamps(const BSONObj& obj) {
+ BSONObjIterator i(obj);
+ while (i.more()) {
+ BSONElement e = i.next();
+
+ // Skip _id field -- we do not replace it
+ if (e.type() == bsonTimestamp && e.fieldNameStringData() != idFieldName) {
+ // TODO(emilkie): This is not endian-safe.
+ unsigned long long& timestamp =
+ *(reinterpret_cast<unsigned long long*>(const_cast<char*>(e.value())));
+ if (timestamp == 0) {
+ // performance note, this locks a mutex:
+ Timestamp ts(getNextGlobalTimestamp());
+ timestamp = ts.asULL();
}
-
- return Status::OK();
}
}
- struct ModifierObjectReplace::PreparedState {
+ return Status::OK();
+}
+}
- PreparedState(mutablebson::Document* targetDoc)
- : doc(*targetDoc)
- , noOp(false) {
- }
+struct ModifierObjectReplace::PreparedState {
+ PreparedState(mutablebson::Document* targetDoc) : doc(*targetDoc), noOp(false) {}
- // Document that is going to be changed
- mutablebson::Document& doc;
+ // Document that is going to be changed
+ mutablebson::Document& doc;
- // This is a no op
- bool noOp;
+ // This is a no op
+ bool noOp;
+};
- };
+ModifierObjectReplace::ModifierObjectReplace() : _val() {}
- ModifierObjectReplace::ModifierObjectReplace() : _val() {
- }
+ModifierObjectReplace::~ModifierObjectReplace() {}
- ModifierObjectReplace::~ModifierObjectReplace() {
+Status ModifierObjectReplace::init(const BSONElement& modExpr,
+ const Options& opts,
+ bool* positional) {
+ if (modExpr.type() != Object) {
+ // Impossible, really since the caller check this already...
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "Document replacement expects a complete document"
+ " but the type supplied was " << modExpr.type());
}
- Status ModifierObjectReplace::init(const BSONElement& modExpr, const Options& opts,
- bool* positional) {
-
- if (modExpr.type() != Object) {
- // Impossible, really since the caller check this already...
- return Status(ErrorCodes::BadValue,
- str::stream() << "Document replacement expects a complete document"
- " but the type supplied was "
- << modExpr.type());
- }
+ // Object replacements never have positional operator.
+ if (positional)
+ *positional = false;
+
+ // We make a copy of the object here because the update driver does not guarantees, in
+ // the case of object replacement, that the modExpr is going to outlive this mod.
+ _val = modExpr.embeddedObject().getOwned();
+ return fixupTimestamps(_val);
+}
+
+Status ModifierObjectReplace::prepare(mutablebson::Element root,
+ StringData matchedField,
+ ExecInfo* execInfo) {
+ _preparedState.reset(new PreparedState(&root.getDocument()));
+
+ // objectSize checked by binaryEqual (optimization)
+ BSONObj objOld = root.getDocument().getObject();
+ if (objOld.binaryEqual(_val)) {
+ _preparedState->noOp = true;
+ execInfo->noOp = true;
+ }
- // Object replacements never have positional operator.
- if (positional)
- *positional = false;
+ return Status::OK();
+}
- // We make a copy of the object here because the update driver does not guarantees, in
- // the case of object replacement, that the modExpr is going to outlive this mod.
- _val = modExpr.embeddedObject().getOwned();
- return fixupTimestamps(_val);
- }
+Status ModifierObjectReplace::apply() const {
+ dassert(!_preparedState->noOp);
- Status ModifierObjectReplace::prepare(mutablebson::Element root,
- StringData matchedField,
- ExecInfo* execInfo) {
- _preparedState.reset(new PreparedState(&root.getDocument()));
+ // Remove the contents of the provided doc.
+ mutablebson::Document& doc = _preparedState->doc;
+ mutablebson::Element current = doc.root().leftChild();
+ mutablebson::Element srcIdElement = doc.end();
+ while (current.ok()) {
+ mutablebson::Element toRemove = current;
+ current = current.rightSibling();
- // objectSize checked by binaryEqual (optimization)
- BSONObj objOld = root.getDocument().getObject();
- if (objOld.binaryEqual(_val)) {
- _preparedState->noOp = true;
- execInfo->noOp = true;
+ // Skip _id field element -- it should not change
+ if (toRemove.getFieldName() == idFieldName) {
+ srcIdElement = toRemove;
+ continue;
}
- return Status::OK();
+ Status status = toRemove.remove();
+ if (!status.isOK()) {
+ return status;
+ }
}
- Status ModifierObjectReplace::apply() const {
- dassert(!_preparedState->noOp);
-
- // Remove the contents of the provided doc.
- mutablebson::Document& doc = _preparedState->doc;
- mutablebson::Element current = doc.root().leftChild();
- mutablebson::Element srcIdElement = doc.end();
- while (current.ok()) {
- mutablebson::Element toRemove = current;
- current = current.rightSibling();
-
- // Skip _id field element -- it should not change
- if (toRemove.getFieldName() == idFieldName) {
- srcIdElement = toRemove;
+ // Insert the provided contents instead.
+ BSONElement dstIdElement;
+ BSONObjIterator it(_val);
+ while (it.more()) {
+ BSONElement elem = it.next();
+ if (elem.fieldNameStringData() == idFieldName) {
+ dstIdElement = elem;
+
+ // Do not duplicate _id field
+ if (srcIdElement.ok()) {
+ if (srcIdElement.compareWithBSONElement(dstIdElement, true) != 0) {
+ return Status(ErrorCodes::ImmutableField,
+ str::stream() << "The _id field cannot be changed from {"
+ << srcIdElement.toString() << "} to {"
+ << dstIdElement.toString() << "}.");
+ }
continue;
}
-
- Status status = toRemove.remove();
- if (!status.isOK()) {
- return status;
- }
}
- // Insert the provided contents instead.
- BSONElement dstIdElement;
- BSONObjIterator it(_val);
- while (it.more()) {
- BSONElement elem = it.next();
- if (elem.fieldNameStringData() == idFieldName) {
- dstIdElement = elem;
-
- // Do not duplicate _id field
- if (srcIdElement.ok()) {
- if (srcIdElement.compareWithBSONElement(dstIdElement, true) != 0) {
- return Status(ErrorCodes::ImmutableField,
- str::stream() << "The _id field cannot be changed from {"
- << srcIdElement.toString() << "} to {"
- << dstIdElement.toString() << "}.");
- }
- continue;
- }
- }
-
- Status status = doc.root().appendElement(elem);
- if (!status.isOK()) {
- return status;
- }
+ Status status = doc.root().appendElement(elem);
+ if (!status.isOK()) {
+ return status;
}
-
- return Status::OK();
}
- Status ModifierObjectReplace::log(LogBuilder* logBuilder) const {
+ return Status::OK();
+}
- mutablebson::Document& doc = logBuilder->getDocument();
+Status ModifierObjectReplace::log(LogBuilder* logBuilder) const {
+ mutablebson::Document& doc = logBuilder->getDocument();
- mutablebson::Element replacementObject = doc.end();
- Status status = logBuilder->getReplacementObject(&replacementObject);
+ mutablebson::Element replacementObject = doc.end();
+ Status status = logBuilder->getReplacementObject(&replacementObject);
- if (status.isOK()) {
- mutablebson::Element current = _preparedState->doc.root().leftChild();
- while (current.ok()) {
- status = replacementObject.appendElement(current.getValue());
- if (!status.isOK())
- return status;
- current = current.rightSibling();
- }
+ if (status.isOK()) {
+ mutablebson::Element current = _preparedState->doc.root().leftChild();
+ while (current.ok()) {
+ status = replacementObject.appendElement(current.getValue());
+ if (!status.isOK())
+ return status;
+ current = current.rightSibling();
}
-
- return status;
}
-} // namespace mongo
+ return status;
+}
+
+} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_object_replace.h b/src/mongo/db/ops/modifier_object_replace.h
index 4e923e4a7fd..add180d472e 100644
--- a/src/mongo/db/ops/modifier_object_replace.h
+++ b/src/mongo/db/ops/modifier_object_replace.h
@@ -37,62 +37,57 @@
namespace mongo {
- class LogBuilder;
-
- class ModifierObjectReplace : public ModifierInterface {
- MONGO_DISALLOW_COPYING(ModifierObjectReplace);
-
- public:
-
- ModifierObjectReplace();
-
- //
- // Modifier interface implementation
- //
-
- virtual ~ModifierObjectReplace();
-
-
- /**
- * Returns true and takes the embedded object contained in 'modExpr' to be the object
- * we're replacing for. The field name of 'modExpr' is ignored. If 'modExpr' is in an
- * unexpected format or if it can't be parsed for some reason, returns an error status
- * describing the error.
- */
- virtual Status init(const BSONElement& modExpr, const Options& opts,
- bool* positional = NULL);
-
- /**
- * Registers the that 'root' is in the document that we want to fully replace.
- * prepare() returns OK and always fills 'execInfo' with true for
- * noOp.
- */
- virtual Status prepare(mutablebson::Element root,
- StringData matchedField,
- ExecInfo* execInfo);
-
- /**
- * Replaces the document passed in prepare() for the object passed in init(). Returns
- * OK if successful or a status describing the error.
- */
- virtual Status apply() const;
-
- /**
- * Adds a log entry to logRoot corresponding to full object replacement. Returns OK if
- * successful or a status describing the error.
- */
- virtual Status log(LogBuilder* logBuilder) const;
-
- private:
-
- // Object to replace with.
- BSONObj _val;
-
- // The document whose value needs to be replaced. This state is valid after a prepare()
- // was issued and until a log() is issued. The document this mod is being prepared
- // against must e live throughout all the calls.
- struct PreparedState;
- std::unique_ptr<PreparedState> _preparedState;
- };
-
-} // namespace mongo
+class LogBuilder;
+
+class ModifierObjectReplace : public ModifierInterface {
+ MONGO_DISALLOW_COPYING(ModifierObjectReplace);
+
+public:
+ ModifierObjectReplace();
+
+ //
+ // Modifier interface implementation
+ //
+
+ virtual ~ModifierObjectReplace();
+
+
+ /**
+ * Returns true and takes the embedded object contained in 'modExpr' to be the object
+ * we're replacing for. The field name of 'modExpr' is ignored. If 'modExpr' is in an
+ * unexpected format or if it can't be parsed for some reason, returns an error status
+ * describing the error.
+ */
+ virtual Status init(const BSONElement& modExpr, const Options& opts, bool* positional = NULL);
+
+ /**
+ * Registers the that 'root' is in the document that we want to fully replace.
+ * prepare() returns OK and always fills 'execInfo' with true for
+ * noOp.
+ */
+ virtual Status prepare(mutablebson::Element root, StringData matchedField, ExecInfo* execInfo);
+
+ /**
+ * Replaces the document passed in prepare() for the object passed in init(). Returns
+ * OK if successful or a status describing the error.
+ */
+ virtual Status apply() const;
+
+ /**
+ * Adds a log entry to logRoot corresponding to full object replacement. Returns OK if
+ * successful or a status describing the error.
+ */
+ virtual Status log(LogBuilder* logBuilder) const;
+
+private:
+ // Object to replace with.
+ BSONObj _val;
+
+ // The document whose value needs to be replaced. This state is valid after a prepare()
+ // was issued and until a log() is issued. The document this mod is being prepared
+ // against must e live throughout all the calls.
+ struct PreparedState;
+ std::unique_ptr<PreparedState> _preparedState;
+};
+
+} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_object_replace_test.cpp b/src/mongo/db/ops/modifier_object_replace_test.cpp
index 3a5de50fead..083981fb34f 100644
--- a/src/mongo/db/ops/modifier_object_replace_test.cpp
+++ b/src/mongo/db/ops/modifier_object_replace_test.cpp
@@ -43,267 +43,267 @@
namespace {
- using mongo::BSONObj;
- using mongo::fromjson;
- using mongo::LogBuilder;
- using mongo::ModifierInterface;
- using mongo::ModifierObjectReplace;
- using mongo::mutablebson::ConstElement;
- using mongo::mutablebson::countChildren;
- using mongo::mutablebson::Document;
- using mongo::mutablebson::Element;
- using mongo::mutablebson::findFirstChildNamed;
- using mongo::NumberInt;
- using mongo::Status;
- using mongo::StringData;
-
- /** Helper to build and manipulate a $set mod. */
- class Mod {
- public:
- Mod() : _mod() {}
-
- explicit Mod(BSONObj modObj)
- : _mod() {
- _modObj = modObj;
- ASSERT_OK(_mod.init(BSON("" << modObj).firstElement(),
- ModifierInterface::Options::normal()));
- }
-
- 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);
- }
-
- ModifierObjectReplace& mod() { return _mod; }
- BSONObj& obj() { return _modObj; }
-
- private:
- ModifierObjectReplace _mod;
- BSONObj _modObj;
- };
-
- // Normal replacements below
- TEST(Normal, SingleFieldDoc){
- Document doc(fromjson("{_id:1, a:1}"));
- Mod mod(fromjson("{_id:1, b:12}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_EQUALS(doc, fromjson("{_id:1, b:12}"));
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(doc, logDoc);
+using mongo::BSONObj;
+using mongo::fromjson;
+using mongo::LogBuilder;
+using mongo::ModifierInterface;
+using mongo::ModifierObjectReplace;
+using mongo::mutablebson::ConstElement;
+using mongo::mutablebson::countChildren;
+using mongo::mutablebson::Document;
+using mongo::mutablebson::Element;
+using mongo::mutablebson::findFirstChildNamed;
+using mongo::NumberInt;
+using mongo::Status;
+using mongo::StringData;
+
+/** Helper to build and manipulate a $set mod. */
+class Mod {
+public:
+ Mod() : _mod() {}
+
+ explicit Mod(BSONObj modObj) : _mod() {
+ _modObj = modObj;
+ ASSERT_OK(
+ _mod.init(BSON("" << modObj).firstElement(), ModifierInterface::Options::normal()));
}
- TEST(Normal, ComplexDoc){
- Document doc(fromjson("{_id:1, a:1}"));
- Mod mod(fromjson("{_id:1, b:[123], c: {r:true}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_EQUALS(doc, fromjson("{_id:1, b:[123], c: {r:true}}"));
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(doc, logDoc);
+ Status prepare(Element root, StringData matchedField, ModifierInterface::ExecInfo* execInfo) {
+ return _mod.prepare(root, matchedField, execInfo);
}
- TEST(Normal, OnlyIdField){
- Document doc(fromjson("{}"));
- Mod mod(fromjson("{_id:1}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_EQUALS(doc, mod.obj());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(doc, logDoc);
+ Status apply() const {
+ return _mod.apply();
}
- // These updates have to do with updates without an _id field
- // (the existing _id isn't removed)
- TEST(IdLeft, EmptyDocReplacement){
- Document doc(fromjson("{_id:1}"));
- Mod mod(fromjson("{}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_EQUALS(doc, fromjson("{_id:1}"));
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(doc, logDoc);
+ Status log(LogBuilder* logBuilder) const {
+ return _mod.log(logBuilder);
}
- TEST(IdLeft, EmptyDoc){
- Document doc(fromjson("{_id:1}"));
- Mod mod(fromjson("{}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_EQUALS(doc, fromjson("{_id:1}"));
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(doc, logDoc);
+ ModifierObjectReplace& mod() {
+ return _mod;
}
-
- TEST(IdLeft, SingleFieldAddition){
- Document doc(fromjson("{_id:1}"));
- Mod mod(fromjson("{a:1}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_EQUALS(doc, fromjson("{_id:1, a:1}"));
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(doc, logDoc);
- }
-
- TEST(IdLeft, SingleFieldReplaced){
- Document doc(fromjson("{a: []}"));
- Mod mod(fromjson("{a:10}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_EQUALS(doc, mod.obj());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(doc, logDoc);
- }
-
- TEST(IdLeft, SwapFields){
- Document doc(fromjson("{_id:1, a:1}"));
- Mod mod(fromjson("{b:1}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_EQUALS(doc, fromjson("{_id:1, b:1}"));
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(doc, logDoc);
- }
-
- TEST(IdImmutable, ReplaceIdNumber){
- Document doc(fromjson("{_id:1, a:1}"));
- Mod mod(fromjson("{_id:2}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_NOT_OK(mod.apply());
- }
-
- TEST(IdImmutable, ReplaceIdNumberSameVal){
- Document doc(fromjson("{_id:1, a:1}"));
- Mod mod(fromjson("{_id:2}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_NOT_OK(mod.apply());
- }
-
- TEST(IdImmutable, ReplaceEmbeddedId){
- Document doc(fromjson("{_id:{a:1, b:2}, a:1}"));
- Mod mod(fromjson("{_id:{b:2, a:1}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_NOT_OK(mod.apply());
- }
-
- TEST(Timestamp, IdNotReplaced){
- Document doc(fromjson("{}"));
- Mod mod(fromjson("{_id:Timestamp(0,0), a:1}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_EQUALS(doc, mod.obj());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(mod.obj(), logDoc);
-
- Element idElem = findFirstChildNamed(logDoc.root(), "_id");
- ASSERT(idElem.ok());
- ASSERT(idElem.getValueTimestamp().isNull());
-
- }
-
- TEST(Timestamp, ReplaceAll){
- Document doc(fromjson("{}"));
- Mod mod(fromjson("{a:Timestamp(0,0), r:1, x:1, b:Timestamp(0,0)}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
-
- Element elem = findFirstChildNamed(doc.root(), "a");
- ASSERT(elem.ok());
- ASSERT_NOT_EQUALS(0U, elem.getValueTimestamp().getSecs());
- ASSERT_NOT_EQUALS(0U, elem.getValueTimestamp().getInc());
-
- elem = findFirstChildNamed(doc.root(), "b");
- ASSERT(elem.ok());
- ASSERT_NOT_EQUALS(0U, elem.getValueTimestamp().getSecs());
- ASSERT_NOT_EQUALS(0U, elem.getValueTimestamp().getInc());
+ BSONObj& obj() {
+ return _modObj;
}
-} // unnamed namespace
+private:
+ ModifierObjectReplace _mod;
+ BSONObj _modObj;
+};
+
+// Normal replacements below
+TEST(Normal, SingleFieldDoc) {
+ Document doc(fromjson("{_id:1, a:1}"));
+ Mod mod(fromjson("{_id:1, b:12}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_EQUALS(doc, fromjson("{_id:1, b:12}"));
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(doc, logDoc);
+}
+
+TEST(Normal, ComplexDoc) {
+ Document doc(fromjson("{_id:1, a:1}"));
+ Mod mod(fromjson("{_id:1, b:[123], c: {r:true}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_EQUALS(doc, fromjson("{_id:1, b:[123], c: {r:true}}"));
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(doc, logDoc);
+}
+
+TEST(Normal, OnlyIdField) {
+ Document doc(fromjson("{}"));
+ Mod mod(fromjson("{_id:1}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_EQUALS(doc, mod.obj());
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(doc, logDoc);
+}
+
+// These updates have to do with updates without an _id field
+// (the existing _id isn't removed)
+TEST(IdLeft, EmptyDocReplacement) {
+ Document doc(fromjson("{_id:1}"));
+ Mod mod(fromjson("{}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_EQUALS(doc, fromjson("{_id:1}"));
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(doc, logDoc);
+}
+
+TEST(IdLeft, EmptyDoc) {
+ Document doc(fromjson("{_id:1}"));
+ Mod mod(fromjson("{}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_EQUALS(doc, fromjson("{_id:1}"));
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(doc, logDoc);
+}
+
+TEST(IdLeft, SingleFieldAddition) {
+ Document doc(fromjson("{_id:1}"));
+ Mod mod(fromjson("{a:1}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_EQUALS(doc, fromjson("{_id:1, a:1}"));
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(doc, logDoc);
+}
+
+TEST(IdLeft, SingleFieldReplaced) {
+ Document doc(fromjson("{a: []}"));
+ Mod mod(fromjson("{a:10}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_EQUALS(doc, mod.obj());
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(doc, logDoc);
+}
+
+TEST(IdLeft, SwapFields) {
+ Document doc(fromjson("{_id:1, a:1}"));
+ Mod mod(fromjson("{b:1}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_EQUALS(doc, fromjson("{_id:1, b:1}"));
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(doc, logDoc);
+}
+
+TEST(IdImmutable, ReplaceIdNumber) {
+ Document doc(fromjson("{_id:1, a:1}"));
+ Mod mod(fromjson("{_id:2}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+ ASSERT_NOT_OK(mod.apply());
+}
+
+TEST(IdImmutable, ReplaceIdNumberSameVal) {
+ Document doc(fromjson("{_id:1, a:1}"));
+ Mod mod(fromjson("{_id:2}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+ ASSERT_NOT_OK(mod.apply());
+}
+
+TEST(IdImmutable, ReplaceEmbeddedId) {
+ Document doc(fromjson("{_id:{a:1, b:2}, a:1}"));
+ Mod mod(fromjson("{_id:{b:2, a:1}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+ ASSERT_NOT_OK(mod.apply());
+}
+
+TEST(Timestamp, IdNotReplaced) {
+ Document doc(fromjson("{}"));
+ Mod mod(fromjson("{_id:Timestamp(0,0), a:1}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_EQUALS(doc, mod.obj());
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(mod.obj(), logDoc);
+
+ Element idElem = findFirstChildNamed(logDoc.root(), "_id");
+ ASSERT(idElem.ok());
+ ASSERT(idElem.getValueTimestamp().isNull());
+}
+
+TEST(Timestamp, ReplaceAll) {
+ Document doc(fromjson("{}"));
+ Mod mod(fromjson("{a:Timestamp(0,0), r:1, x:1, b:Timestamp(0,0)}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+
+ Element elem = findFirstChildNamed(doc.root(), "a");
+ ASSERT(elem.ok());
+ ASSERT_NOT_EQUALS(0U, elem.getValueTimestamp().getSecs());
+ ASSERT_NOT_EQUALS(0U, elem.getValueTimestamp().getInc());
+
+ elem = findFirstChildNamed(doc.root(), "b");
+ ASSERT(elem.ok());
+ ASSERT_NOT_EQUALS(0U, elem.getValueTimestamp().getSecs());
+ ASSERT_NOT_EQUALS(0U, elem.getValueTimestamp().getInc());
+}
+
+} // unnamed namespace
diff --git a/src/mongo/db/ops/modifier_pop.cpp b/src/mongo/db/ops/modifier_pop.cpp
index e5fd836dea7..c46fdd7a9bf 100644
--- a/src/mongo/db/ops/modifier_pop.cpp
+++ b/src/mongo/db/ops/modifier_pop.cpp
@@ -38,176 +38,161 @@
namespace mongo {
- namespace mb = mutablebson;
- namespace str = mongoutils::str;
+namespace mb = mutablebson;
+namespace str = mongoutils::str;
- struct ModifierPop::PreparedState {
+struct ModifierPop::PreparedState {
+ PreparedState(mutablebson::Document* targetDoc)
+ : doc(*targetDoc),
+ elementToRemove(doc.end()),
+ pathFoundIndex(0),
+ pathFoundElement(doc.end()) {}
- PreparedState(mutablebson::Document* targetDoc)
- : doc(*targetDoc)
- , elementToRemove(doc.end())
- , pathFoundIndex(0)
- , pathFoundElement(doc.end()) {
- }
+ // Document that is going to be changed.
+ mutablebson::Document& doc;
- // Document that is going to be changed.
- mutablebson::Document& doc;
+ // Element to be removed
+ mutablebson::Element elementToRemove;
- // Element to be removed
- mutablebson::Element elementToRemove;
+ // Index in _fieldRef for which an Element exist in the document.
+ size_t pathFoundIndex;
- // Index in _fieldRef for which an Element exist in the document.
- size_t pathFoundIndex;
+ // Element corresponding to _fieldRef[0.._idxFound].
+ mutablebson::Element pathFoundElement;
+};
- // Element corresponding to _fieldRef[0.._idxFound].
- mutablebson::Element pathFoundElement;
- };
+ModifierPop::ModifierPop() : _fieldRef(), _positionalPathIndex(0), _fromTop(false) {}
- ModifierPop::ModifierPop()
- : _fieldRef()
- , _positionalPathIndex(0)
- , _fromTop(false) {
- }
+ModifierPop::~ModifierPop() {}
- ModifierPop::~ModifierPop() {
- }
+Status ModifierPop::init(const BSONElement& modExpr, const Options& opts, bool* positional) {
+ //
+ // field name analysis
+ //
- Status ModifierPop::init(const BSONElement& modExpr, const Options& opts,
- bool* positional) {
- //
- // field name analysis
- //
-
- // Break down the field name into its 'dotted' components (aka parts) and check that
- // there are no empty parts.
- _fieldRef.parse(modExpr.fieldName());
- Status status = fieldchecker::isUpdatable(_fieldRef);
- if (! status.isOK()) {
- return status;
- }
+ // Break down the field name into its 'dotted' components (aka parts) and check that
+ // there are no empty parts.
+ _fieldRef.parse(modExpr.fieldName());
+ Status status = fieldchecker::isUpdatable(_fieldRef);
+ if (!status.isOK()) {
+ return status;
+ }
- // If a $-positional operator was used, get the index in which it occurred
- // and ensure only one occurrence.
- size_t foundCount;
- bool foundDollar = fieldchecker::isPositional(_fieldRef,
- &_positionalPathIndex,
- &foundCount);
+ // If a $-positional operator was used, get the index in which it occurred
+ // and ensure only one occurrence.
+ size_t foundCount;
+ bool foundDollar = fieldchecker::isPositional(_fieldRef, &_positionalPathIndex, &foundCount);
- if (positional)
- *positional = foundDollar;
+ if (positional)
+ *positional = foundDollar;
- if (foundDollar && foundCount > 1) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "Too many positional (i.e. '$') elements found in path '"
- << _fieldRef.dottedField() << "'");
- }
+ if (foundDollar && foundCount > 1) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "Too many positional (i.e. '$') elements found in path '"
+ << _fieldRef.dottedField() << "'");
+ }
- //
- // value analysis
- //
+ //
+ // value analysis
+ //
- // TODO: tighten validation to numbers and just 1/-1 explicitly
- //if (!modExpr.isNumber()) {
- // return Status(ErrorCodes::BadValue, "Must be a number");
- //}
+ // TODO: tighten validation to numbers and just 1/-1 explicitly
+ // if (!modExpr.isNumber()) {
+ // return Status(ErrorCodes::BadValue, "Must be a number");
+ //}
- _fromTop = (modExpr.isNumber() && modExpr.number() < 0) ? true : false;
+ _fromTop = (modExpr.isNumber() && modExpr.number() < 0) ? true : false;
- return Status::OK();
- }
+ return Status::OK();
+}
- Status ModifierPop::prepare(mutablebson::Element root,
- StringData matchedField,
- ExecInfo* execInfo) {
+Status ModifierPop::prepare(mutablebson::Element root,
+ StringData matchedField,
+ ExecInfo* execInfo) {
+ _preparedState.reset(new PreparedState(&root.getDocument()));
- _preparedState.reset(new PreparedState(&root.getDocument()));
+ // If we have a $-positional field, it is time to bind it to an actual field part.
+ if (_positionalPathIndex) {
+ if (matchedField.empty()) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "The positional operator did not find the match "
+ "needed from the query. Unexpanded update: "
+ << _fieldRef.dottedField());
+ }
+ _fieldRef.setPart(_positionalPathIndex, matchedField);
+ }
- // If we have a $-positional field, it is time to bind it to an actual field part.
- if (_positionalPathIndex) {
- if (matchedField.empty()) {
+ // Locate the field name in 'root'. Note that if we don't have the full path in the
+ // doc, there isn't anything to unset, really.
+ Status status = pathsupport::findLongestPrefix(
+ _fieldRef, root, &_preparedState->pathFoundIndex, &_preparedState->pathFoundElement);
+ // Check if we didn't find the full path
+ if (status.isOK()) {
+ const bool destExists = (_preparedState->pathFoundIndex == (_fieldRef.numParts() - 1));
+ if (!destExists) {
+ execInfo->noOp = true;
+ } else {
+ // If the path exists, we require the target field to be already an
+ // array.
+ if (_preparedState->pathFoundElement.getType() != Array) {
+ mb::Element idElem = mb::findFirstChildNamed(root, "_id");
return Status(ErrorCodes::BadValue,
- str::stream() << "The positional operator did not find the match "
- "needed from the query. Unexpanded update: "
- << _fieldRef.dottedField());
+ str::stream()
+ << "Can only $pop from arrays. {" << idElem.toString()
+ << "} has the field '"
+ << _preparedState->pathFoundElement.getFieldName()
+ << "' of non-array type "
+ << typeName(_preparedState->pathFoundElement.getType()));
}
- _fieldRef.setPart(_positionalPathIndex, matchedField);
- }
- // Locate the field name in 'root'. Note that if we don't have the full path in the
- // doc, there isn't anything to unset, really.
- Status status = pathsupport::findLongestPrefix(_fieldRef,
- root,
- &_preparedState->pathFoundIndex,
- &_preparedState->pathFoundElement);
- // Check if we didn't find the full path
- if (status.isOK()) {
- const bool destExists = (_preparedState->pathFoundIndex == (_fieldRef.numParts()-1));
- if (!destExists) {
+ // No children, nothing to do -- not an error state
+ if (!_preparedState->pathFoundElement.hasChildren()) {
execInfo->noOp = true;
} else {
- // If the path exists, we require the target field to be already an
- // array.
- if (_preparedState->pathFoundElement.getType() != Array) {
- mb::Element idElem = mb::findFirstChildNamed(root, "_id");
- return Status(
- ErrorCodes::BadValue,
- str::stream() << "Can only $pop from arrays. {"
- << idElem.toString()
- << "} has the field '"
- << _preparedState->pathFoundElement.getFieldName()
- << "' of non-array type "
- << typeName(_preparedState->pathFoundElement.getType()));
- }
-
- // No children, nothing to do -- not an error state
- if (!_preparedState->pathFoundElement.hasChildren()) {
- execInfo->noOp = true;
- } else {
- _preparedState->elementToRemove = _fromTop ?
- _preparedState->pathFoundElement.leftChild() :
- _preparedState->pathFoundElement.rightChild();
- }
+ _preparedState->elementToRemove = _fromTop
+ ? _preparedState->pathFoundElement.leftChild()
+ : _preparedState->pathFoundElement.rightChild();
}
- } else {
- // Let the caller know we can't do anything given the mod, _fieldRef, and doc.
- execInfo->noOp = true;
- _preparedState->pathFoundElement = root.getDocument().end();
-
- //okay if path not found
- if (status.code() == ErrorCodes::NonExistentPath)
- status = Status::OK();
}
+ } else {
+ // Let the caller know we can't do anything given the mod, _fieldRef, and doc.
+ execInfo->noOp = true;
+ _preparedState->pathFoundElement = root.getDocument().end();
+
+ // okay if path not found
+ if (status.code() == ErrorCodes::NonExistentPath)
+ status = Status::OK();
+ }
- // Let the caller know what field we care about
- execInfo->fieldRef[0] = &_fieldRef;
+ // Let the caller know what field we care about
+ execInfo->fieldRef[0] = &_fieldRef;
- return status;
- }
+ return status;
+}
- Status ModifierPop::apply() const {
- return _preparedState->elementToRemove.remove();
- }
+Status ModifierPop::apply() const {
+ return _preparedState->elementToRemove.remove();
+}
- Status ModifierPop::log(LogBuilder* logBuilder) const {
- // log document
- mutablebson::Document& doc = logBuilder->getDocument();
- const bool pathExists = _preparedState->pathFoundElement.ok() &&
- (_preparedState->pathFoundIndex == (_fieldRef.numParts() - 1));
-
- if (!pathExists)
- return logBuilder->addToUnsets(_fieldRef.dottedField());
-
- // value for the logElement ("field.path.name": <value>)
- mutablebson::Element logElement = doc.makeElementWithNewFieldName(
- _fieldRef.dottedField(),
- _preparedState->pathFoundElement);
-
- if (!logElement.ok()) {
- return Status(ErrorCodes::InternalError,
- str::stream() << "Could not append entry to $pop oplog entry: "
- << "set '" << _fieldRef.dottedField() << "' -> "
- << _preparedState->pathFoundElement.toString() );
- }
- return logBuilder->addToSets(logElement);
+Status ModifierPop::log(LogBuilder* logBuilder) const {
+ // log document
+ mutablebson::Document& doc = logBuilder->getDocument();
+ const bool pathExists = _preparedState->pathFoundElement.ok() &&
+ (_preparedState->pathFoundIndex == (_fieldRef.numParts() - 1));
+
+ if (!pathExists)
+ return logBuilder->addToUnsets(_fieldRef.dottedField());
+
+ // value for the logElement ("field.path.name": <value>)
+ mutablebson::Element logElement =
+ doc.makeElementWithNewFieldName(_fieldRef.dottedField(), _preparedState->pathFoundElement);
+
+ if (!logElement.ok()) {
+ return Status(ErrorCodes::InternalError,
+ str::stream() << "Could not append entry to $pop oplog entry: "
+ << "set '" << _fieldRef.dottedField() << "' -> "
+ << _preparedState->pathFoundElement.toString());
}
-} // namespace mongo
+ return logBuilder->addToSets(logElement);
+}
+} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_pop.h b/src/mongo/db/ops/modifier_pop.h
index 41501275619..b0de117a946 100644
--- a/src/mongo/db/ops/modifier_pop.h
+++ b/src/mongo/db/ops/modifier_pop.h
@@ -38,51 +38,46 @@
namespace mongo {
- class LogBuilder;
+class LogBuilder;
- class ModifierPop : public ModifierInterface {
- MONGO_DISALLOW_COPYING(ModifierPop);
+class ModifierPop : public ModifierInterface {
+ MONGO_DISALLOW_COPYING(ModifierPop);
- public:
+public:
+ ModifierPop();
+ virtual ~ModifierPop();
- ModifierPop();
- virtual ~ModifierPop();
+ /**
+ * The format of this modifier ($pop) is {<fieldname>: <value>}.
+ * If the value is number and greater than -1 then an element is removed from the bottom,
+ * otherwise the top. Currently the value can be any anything but we document
+ * the use of the numbers "1, -1" only.
+ *
+ * Ex. $pop: {'a':1} will remove the last item from this array: [1,2,3] -> [1,2]
+ */
+ virtual Status init(const BSONElement& modExpr, const Options& opts, bool* positional = NULL);
- /**
- * The format of this modifier ($pop) is {<fieldname>: <value>}.
- * If the value is number and greater than -1 then an element is removed from the bottom,
- * otherwise the top. Currently the value can be any anything but we document
- * the use of the numbers "1, -1" only.
- *
- * Ex. $pop: {'a':1} will remove the last item from this array: [1,2,3] -> [1,2]
- */
- virtual Status init(const BSONElement& modExpr, const Options& opts,
- bool* positional = NULL);
+ virtual Status prepare(mutablebson::Element root, StringData matchedField, ExecInfo* execInfo);
- virtual Status prepare(mutablebson::Element root,
- StringData matchedField,
- ExecInfo* execInfo);
+ virtual Status apply() const;
- virtual Status apply() const;
+ virtual Status log(LogBuilder* logBuilder) const;
- virtual Status log(LogBuilder* logBuilder) const;
+private:
+ // Access to each component of fieldName that's the target of this mod.
+ FieldRef _fieldRef;
- private:
+ // 0 or index for $-positional in _fieldRef.
+ size_t _positionalPathIndex;
- // Access to each component of fieldName that's the target of this mod.
- FieldRef _fieldRef;
+ // element position to remove from
+ bool _fromTop;
- // 0 or index for $-positional in _fieldRef.
- size_t _positionalPathIndex;
+ // The instance of the field in the provided doc.
+ // This data is valid after prepare, for use by log and apply
+ struct PreparedState;
+ std::unique_ptr<PreparedState> _preparedState;
+};
- // element position to remove from
- bool _fromTop;
-
- // The instance of the field in the provided doc.
- // This data is valid after prepare, for use by log and apply
- struct PreparedState;
- std::unique_ptr<PreparedState> _preparedState;
- };
-
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_pop_test.cpp b/src/mongo/db/ops/modifier_pop_test.cpp
index 283b9bc5a81..373c61aeca6 100644
--- a/src/mongo/db/ops/modifier_pop_test.cpp
+++ b/src/mongo/db/ops/modifier_pop_test.cpp
@@ -42,275 +42,277 @@
namespace {
- using mongo::Array;
- using mongo::BSONObj;
- using mongo::LogBuilder;
- using mongo::fromjson;
- using mongo::ModifierInterface;
- using mongo::ModifierPop;
- using mongo::Status;
- using mongo::StringData;
- using mongo::mutablebson::Document;
- using mongo::mutablebson::Element;
-
- /** Helper to build and manipulate a $pop mod. */
- class Mod {
- public:
- Mod() : _mod() {}
-
- explicit Mod(BSONObj modObj) {
- _modObj = modObj;
- ASSERT_OK(_mod.init(_modObj["$pop"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
-
- 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);
- }
-
- ModifierPop& mod() { return _mod; }
-
- BSONObj modObj() { return _modObj; }
-
- private:
- ModifierPop _mod;
- BSONObj _modObj;
- };
-
- //
- // Test init values which aren't numbers.
- // These are going to cause a pop from the bottom.
- //
- TEST(Init, StringArg) {
- BSONObj modObj = fromjson("{$pop: {a: 'hi'}}");
- ModifierPop mod;
- ASSERT_OK(mod.init(modObj["$pop"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
+using mongo::Array;
+using mongo::BSONObj;
+using mongo::LogBuilder;
+using mongo::fromjson;
+using mongo::ModifierInterface;
+using mongo::ModifierPop;
+using mongo::Status;
+using mongo::StringData;
+using mongo::mutablebson::Document;
+using mongo::mutablebson::Element;
+
+/** Helper to build and manipulate a $pop mod. */
+class Mod {
+public:
+ Mod() : _mod() {}
+
+ explicit Mod(BSONObj modObj) {
+ _modObj = modObj;
+ ASSERT_OK(_mod.init(_modObj["$pop"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
}
- TEST(Init, BoolTrueArg) {
- BSONObj modObj = fromjson("{$pop: {a: true}}");
- ModifierPop mod;
- ASSERT_OK(mod.init(modObj["$pop"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
+ Status prepare(Element root, StringData matchedField, ModifierInterface::ExecInfo* execInfo) {
+ return _mod.prepare(root, matchedField, execInfo);
}
- TEST(Init, BoolFalseArg) {
- BSONObj modObj = fromjson("{$pop: {a: false}}");
- ModifierPop mod;
- ASSERT_OK(mod.init(modObj["$pop"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
+ Status apply() const {
+ return _mod.apply();
}
- TEST(MissingField, AllButApply) {
- Document doc(fromjson("{a: [1,2]}"));
- Mod mod(fromjson("{$pop: {s: 1}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "s");
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{$unset: {'s': true}}"), logDoc);
+ Status log(LogBuilder* logBuilder) const {
+ return _mod.log(logBuilder);
}
- TEST(SimpleMod, PrepareBottom) {
- Document doc(fromjson("{a: [1,2]}"));
- Mod mod(fromjson("{$pop: {a: 1}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
+ ModifierPop& mod() {
+ return _mod;
}
- TEST(SimpleMod, PrepareApplyBottomO) {
- Document doc(fromjson("{a: [1,2]}"));
- Mod mod(fromjson("{$pop: {a: 0}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(("{a: [1]}")), doc);
+ BSONObj modObj() {
+ return _modObj;
}
- TEST(SimpleMod, PrepareTop) {
- Document doc(fromjson("{a: [1,2]}"));
- Mod mod(fromjson("{$pop: {a: -1}}"));
+private:
+ ModifierPop _mod;
+ BSONObj _modObj;
+};
+
+//
+// Test init values which aren't numbers.
+// These are going to cause a pop from the bottom.
+//
+TEST(Init, StringArg) {
+ BSONObj modObj = fromjson("{$pop: {a: 'hi'}}");
+ ModifierPop mod;
+ ASSERT_OK(mod.init(modObj["$pop"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
+
+TEST(Init, BoolTrueArg) {
+ BSONObj modObj = fromjson("{$pop: {a: true}}");
+ ModifierPop mod;
+ ASSERT_OK(mod.init(modObj["$pop"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
+
+TEST(Init, BoolFalseArg) {
+ BSONObj modObj = fromjson("{$pop: {a: false}}");
+ ModifierPop mod;
+ ASSERT_OK(mod.init(modObj["$pop"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
+
+TEST(MissingField, AllButApply) {
+ Document doc(fromjson("{a: [1,2]}"));
+ Mod mod(fromjson("{$pop: {s: 1}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "s");
+ ASSERT_TRUE(execInfo.noOp);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{$unset: {'s': true}}"), logDoc);
+}
+
+TEST(SimpleMod, PrepareBottom) {
+ Document doc(fromjson("{a: [1,2]}"));
+ Mod mod(fromjson("{$pop: {a: 1}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+}
+
+TEST(SimpleMod, PrepareApplyBottomO) {
+ Document doc(fromjson("{a: [1,2]}"));
+ Mod mod(fromjson("{$pop: {a: 0}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson(("{a: [1]}")), doc);
+}
+
+TEST(SimpleMod, PrepareTop) {
+ Document doc(fromjson("{a: [1,2]}"));
+ Mod mod(fromjson("{$pop: {a: -1}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+}
+
+TEST(SimpleMod, ApplyTopPop) {
+ Document doc(fromjson("{a: [1,2]}"));
+ Mod mod(fromjson("{$pop: {a: -1}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson(("{a: [2]}")), doc);
+}
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+TEST(SimpleMod, ApplyBottomPop) {
+ Document doc(fromjson("{a: [1,2]}"));
+ Mod mod(fromjson("{$pop: {a: 1}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson(("{a: [1]}")), doc);
+}
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
- }
+TEST(SimpleMod, ApplyLogBottomPop) {
+ Document doc(fromjson("{a: [1,2]}"));
+ Mod mod(fromjson("{$pop: {a: 1}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- TEST(SimpleMod, ApplyTopPop) {
- Document doc(fromjson("{a: [1,2]}"));
- Mod mod(fromjson("{$pop: {a: -1}}"));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson(("{a:[1]}")), doc);
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{$set: {a: [1]}}"), logDoc);
+}
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(("{a: [2]}")), doc);
- }
+TEST(EmptyArray, PrepareNoOp) {
+ Document doc(fromjson("{}"));
+ Mod mod(fromjson("{$pop: {a: 1}}"));
- TEST(SimpleMod, ApplyBottomPop) {
- Document doc(fromjson("{a: [1,2]}"));
- Mod mod(fromjson("{$pop: {a: 1}}"));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_TRUE(execInfo.noOp);
+}
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
+TEST(SingleElemArray, ApplyLog) {
+ Document doc(fromjson("{a: [1]}"));
+ Mod mod(fromjson("{$pop: {a: 1}}"));
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(("{a: [1]}")), doc);
- }
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- TEST(SimpleMod, ApplyLogBottomPop) {
- Document doc(fromjson("{a: [1,2]}"));
- Mod mod(fromjson("{$pop: {a: 1}}"));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson(("{a:[]}")), doc);
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{$set: {a: []}}"), logDoc);
+}
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(("{a:[1]}")), doc);
+TEST(ArrayOfArray, ApplyLogPop) {
+ Document doc(fromjson("{a: [[1,2], 1]}"));
+ Mod mod(fromjson("{$pop: {'a.0': 1}}"));
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{$set: {a: [1]}}"), logDoc);
- }
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- TEST(EmptyArray, PrepareNoOp) {
- Document doc(fromjson("{}"));
- Mod mod(fromjson("{$pop: {a: 1}}"));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0");
+ ASSERT_FALSE(execInfo.noOp);
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson(("{a:[[1], 1]}")), doc);
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_TRUE(execInfo.noOp);
- }
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{$set: { 'a.0': [1]}}"), logDoc);
+}
- TEST(SingleElemArray, ApplyLog) {
- Document doc(fromjson("{a: [1]}"));
- Mod mod(fromjson("{$pop: {a: 1}}"));
+TEST(ArrayOfArray, ApplyLogPopOnlyElement) {
+ Document doc(fromjson("{a: [[1], 1]}"));
+ Mod mod(fromjson("{$pop: {'a.0': 1}}"));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0");
+ ASSERT_FALSE(execInfo.noOp);
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(("{a:[]}")), doc);
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson(("{a:[[], 1]}")), doc);
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{$set: {a: []}}"), logDoc);
- }
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{$set: { 'a.0': []}}"), logDoc);
+}
- TEST(ArrayOfArray, ApplyLogPop) {
- Document doc(fromjson("{a: [[1,2], 1]}"));
- Mod mod(fromjson("{$pop: {'a.0': 1}}"));
+TEST(Prepare, MissingPath) {
+ Document doc(fromjson("{ a : [1, 2] }"));
+ Mod mod(fromjson("{ $pop : { b : 1 } }"));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.noOp);
+}
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0");
- ASSERT_FALSE(execInfo.noOp);
+// from SERVER-12846
+TEST(Prepare, MissingArrayElementPath) {
+ Document doc(fromjson("{_id : 1, a : [1, 2]}"));
+ Mod mod(fromjson("{ $pop : { 'a.3' : 1 } }"));
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(("{a:[[1], 1]}")), doc);
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.noOp);
+}
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{$set: { 'a.0': [1]}}"), logDoc);
- }
+TEST(Prepare, FromArrayElementPath) {
+ Document doc(fromjson("{ a : [1, 2] }"));
+ Mod mod(fromjson("{ $pop : { 'a.0' : 1 } }"));
- TEST(ArrayOfArray, ApplyLogPopOnlyElement) {
- Document doc(fromjson("{a: [[1], 1]}"));
- Mod mod(fromjson("{$pop: {'a.0': 1}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(("{a:[[], 1]}")), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{$set: { 'a.0': []}}"), logDoc);
- }
-
- TEST(Prepare, MissingPath) {
- Document doc(fromjson("{ a : [1, 2] }"));
- Mod mod(fromjson("{ $pop : { b : 1 } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
- }
-
- // from SERVER-12846
- TEST(Prepare, MissingArrayElementPath) {
- Document doc(fromjson("{_id : 1, a : [1, 2]}"));
- Mod mod(fromjson("{ $pop : { 'a.3' : 1 } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
- }
-
- TEST(Prepare, FromArrayElementPath) {
- Document doc(fromjson("{ a : [1, 2] }"));
- Mod mod(fromjson("{ $pop : { 'a.0' : 1 } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(mod.prepare(doc.root(), "", &execInfo));
- }
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_NOT_OK(mod.prepare(doc.root(), "", &execInfo));
+}
-} // unnamed namespace
+} // unnamed namespace
diff --git a/src/mongo/db/ops/modifier_pull.cpp b/src/mongo/db/ops/modifier_pull.cpp
index 5fe94e878ea..3b45064afc7 100644
--- a/src/mongo/db/ops/modifier_pull.cpp
+++ b/src/mongo/db/ops/modifier_pull.cpp
@@ -38,265 +38,238 @@
namespace mongo {
- namespace mb = mutablebson;
- namespace str = mongoutils::str;
+namespace mb = mutablebson;
+namespace str = mongoutils::str;
+
+struct ModifierPull::PreparedState {
+ PreparedState(mb::Document& doc)
+ : doc(doc), idxFound(0), elemFound(doc.end()), elementsToRemove(), noOp(false) {}
+
+ // Document that is going to be changed.
+ mb::Document& doc;
+
+ // Index in _fieldRef for which an Element exist in the document.
+ size_t idxFound;
+
+ // Element corresponding to _fieldRef[0.._idxFound].
+ mb::Element elemFound;
+
+ // Values to be removed.
+ std::vector<mb::Element> elementsToRemove;
+
+ // True if this update is a no-op
+ bool noOp;
+};
+
+ModifierPull::ModifierPull()
+ : ModifierInterface(),
+ _fieldRef(),
+ _posDollar(0),
+ _exprElt(),
+ _exprObj(),
+ _matchExpr(),
+ _matcherOnPrimitive(false),
+ _preparedState() {}
+
+ModifierPull::~ModifierPull() {}
+
+Status ModifierPull::init(const BSONElement& modExpr, const Options& opts, bool* positional) {
+ // Perform standard field name and updateable checks.
+ _fieldRef.parse(modExpr.fieldName());
+ Status status = fieldchecker::isUpdatable(_fieldRef);
+ if (!status.isOK()) {
+ return status;
+ }
- struct ModifierPull::PreparedState {
+ // If a $-positional operator was used, get the index in which it occurred
+ // and ensure only one occurrence.
+ size_t foundCount;
+ bool foundDollar = fieldchecker::isPositional(_fieldRef, &_posDollar, &foundCount);
- PreparedState(mb::Document& doc)
- : doc(doc)
- , idxFound(0)
- , elemFound(doc.end())
- , elementsToRemove()
- , noOp(false) {
- }
+ if (positional)
+ *positional = foundDollar;
- // Document that is going to be changed.
- mb::Document& doc;
+ if (foundDollar && foundCount > 1) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "Too many positional (i.e. '$') elements found in path '"
+ << _fieldRef.dottedField() << "'");
+ }
- // Index in _fieldRef for which an Element exist in the document.
- size_t idxFound;
+ _exprElt = modExpr;
- // Element corresponding to _fieldRef[0.._idxFound].
- mb::Element elemFound;
+ // If the element in the mod is actually an object or a regular expression, we need to
+ // build a matcher, instead of just doing an equality comparision.
+ if ((_exprElt.type() == mongo::Object) || (_exprElt.type() == mongo::RegEx)) {
+ if (_exprElt.type() == Object) {
+ _exprObj = _exprElt.embeddedObject();
- // Values to be removed.
- std::vector<mb::Element> elementsToRemove;
+ // If not is not a query operator, then it is a primitive.
+ _matcherOnPrimitive = (_exprObj.firstElement().getGtLtOp() != 0);
- // True if this update is a no-op
- bool noOp;
- };
+ // If the object is primitive then wrap it up into an object.
+ if (_matcherOnPrimitive)
+ _exprObj = BSON("" << _exprObj);
+ } else {
+ // For a regex, we also need to wrap and treat like a primitive.
+ _matcherOnPrimitive = true;
+ _exprObj = _exprElt.wrap("");
+ }
- ModifierPull::ModifierPull()
- : ModifierInterface()
- , _fieldRef()
- , _posDollar(0)
- , _exprElt()
- , _exprObj()
- , _matchExpr()
- , _matcherOnPrimitive(false)
- , _preparedState() {
- }
+ // Build the matcher around the object we built above. Currently, we do not allow
+ // $pull operations to contain $where clauses, so preserving this behaviour.
+ StatusWithMatchExpression parseResult =
+ MatchExpressionParser::parse(_exprObj, MatchExpressionParser::WhereCallback());
+ if (!parseResult.isOK())
+ return parseResult.getStatus();
- ModifierPull::~ModifierPull() {
+ _matchExpr.reset(parseResult.getValue());
}
- Status ModifierPull::init(const BSONElement& modExpr, const Options& opts,
- bool* positional) {
- // Perform standard field name and updateable checks.
- _fieldRef.parse(modExpr.fieldName());
- Status status = fieldchecker::isUpdatable(_fieldRef);
- if (! status.isOK()) {
- return status;
- }
-
- // If a $-positional operator was used, get the index in which it occurred
- // and ensure only one occurrence.
- size_t foundCount;
- bool foundDollar = fieldchecker::isPositional(_fieldRef, &_posDollar, &foundCount);
+ return Status::OK();
+}
- if (positional)
- *positional = foundDollar;
+Status ModifierPull::prepare(mb::Element root, StringData matchedField, ExecInfo* execInfo) {
+ _preparedState.reset(new PreparedState(root.getDocument()));
- if (foundDollar && foundCount > 1) {
+ // If we have a $-positional field, it is time to bind it to an actual field part.
+ if (_posDollar) {
+ if (matchedField.empty()) {
return Status(ErrorCodes::BadValue,
- str::stream() << "Too many positional (i.e. '$') elements found in path '"
- << _fieldRef.dottedField() << "'");
+ str::stream() << "The positional operator did not find the match "
+ "needed from the query. Unexpanded update: "
+ << _fieldRef.dottedField());
}
+ _fieldRef.setPart(_posDollar, matchedField);
+ }
- _exprElt = modExpr;
-
- // If the element in the mod is actually an object or a regular expression, we need to
- // build a matcher, instead of just doing an equality comparision.
- if ((_exprElt.type() == mongo::Object) || (_exprElt.type() == mongo::RegEx)) {
- if (_exprElt.type() == Object) {
- _exprObj = _exprElt.embeddedObject();
-
- // If not is not a query operator, then it is a primitive.
- _matcherOnPrimitive = (_exprObj.firstElement().getGtLtOp() != 0);
-
- // If the object is primitive then wrap it up into an object.
- if (_matcherOnPrimitive)
- _exprObj = BSON( "" << _exprObj );
- }
- else {
- // For a regex, we also need to wrap and treat like a primitive.
- _matcherOnPrimitive = true;
- _exprObj = _exprElt.wrap("");
- }
-
- // Build the matcher around the object we built above. Currently, we do not allow
- // $pull operations to contain $where clauses, so preserving this behaviour.
- StatusWithMatchExpression parseResult =
- MatchExpressionParser::parse(_exprObj,
- MatchExpressionParser::WhereCallback());
- if (!parseResult.isOK())
- return parseResult.getStatus();
+ // Locate the field name in 'root'.
+ Status status = pathsupport::findLongestPrefix(
+ _fieldRef, root, &_preparedState->idxFound, &_preparedState->elemFound);
+
+ // FindLongestPrefix may say the path does not exist at all, which is fine here, or
+ // that the path was not viable or otherwise wrong, in which case, the mod cannot
+ // proceed.
+ if (status.code() == ErrorCodes::NonExistentPath) {
+ _preparedState->elemFound = root.getDocument().end();
+ } else if (!status.isOK()) {
+ return status;
+ }
- _matchExpr.reset(parseResult.getValue());
- }
+ // We register interest in the field name. The driver needs this info to sort out if
+ // there is any conflict among mods.
+ execInfo->fieldRef[0] = &_fieldRef;
+ if (!_preparedState->elemFound.ok() || _preparedState->idxFound < (_fieldRef.numParts() - 1)) {
+ // If no target element exists, then there is nothing to do here.
+ _preparedState->noOp = execInfo->noOp = true;
return Status::OK();
}
- Status ModifierPull::prepare(mb::Element root,
- StringData matchedField,
- ExecInfo* execInfo) {
-
- _preparedState.reset(new PreparedState(root.getDocument()));
-
- // If we have a $-positional field, it is time to bind it to an actual field part.
- if (_posDollar) {
- if (matchedField.empty()) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "The positional operator did not find the match "
- "needed from the query. Unexpanded update: "
- << _fieldRef.dottedField());
- }
- _fieldRef.setPart(_posDollar, matchedField);
- }
-
- // Locate the field name in 'root'.
- Status status = pathsupport::findLongestPrefix(_fieldRef,
- root,
- &_preparedState->idxFound,
- &_preparedState->elemFound);
-
- // FindLongestPrefix may say the path does not exist at all, which is fine here, or
- // that the path was not viable or otherwise wrong, in which case, the mod cannot
- // proceed.
- if (status.code() == ErrorCodes::NonExistentPath) {
- _preparedState->elemFound = root.getDocument().end();
- } else if (!status.isOK()) {
- return status;
- }
-
- // We register interest in the field name. The driver needs this info to sort out if
- // there is any conflict among mods.
- execInfo->fieldRef[0] = &_fieldRef;
-
- if (!_preparedState->elemFound.ok() ||
- _preparedState->idxFound < (_fieldRef.numParts() - 1)) {
- // If no target element exists, then there is nothing to do here.
- _preparedState->noOp = execInfo->noOp = true;
- return Status::OK();
- }
-
- // This operation only applies to arrays
- if (_preparedState->elemFound.getType() != mongo::Array)
- return Status(
- ErrorCodes::BadValue,
- "Cannot apply $pull to a non-array value");
-
- // If the array is empty, there is nothing to pull, so this is a noop.
- if (!_preparedState->elemFound.hasChildren()) {
- _preparedState->noOp = execInfo->noOp = true;
- return Status::OK();
- }
+ // This operation only applies to arrays
+ if (_preparedState->elemFound.getType() != mongo::Array)
+ return Status(ErrorCodes::BadValue, "Cannot apply $pull to a non-array value");
- // Walk the values in the array
- mb::Element cursor = _preparedState->elemFound.leftChild();
- while (cursor.ok()) {
- if (isMatch(cursor))
- _preparedState->elementsToRemove.push_back(cursor);
- cursor = cursor.rightSibling();
- }
+ // If the array is empty, there is nothing to pull, so this is a noop.
+ if (!_preparedState->elemFound.hasChildren()) {
+ _preparedState->noOp = execInfo->noOp = true;
+ return Status::OK();
+ }
- // If we didn't find any elements to add, then this is a no-op, and therefore in place.
- if (_preparedState->elementsToRemove.empty()) {
- _preparedState->noOp = execInfo->noOp = true;
- }
+ // Walk the values in the array
+ mb::Element cursor = _preparedState->elemFound.leftChild();
+ while (cursor.ok()) {
+ if (isMatch(cursor))
+ _preparedState->elementsToRemove.push_back(cursor);
+ cursor = cursor.rightSibling();
+ }
- return Status::OK();
+ // If we didn't find any elements to add, then this is a no-op, and therefore in place.
+ if (_preparedState->elementsToRemove.empty()) {
+ _preparedState->noOp = execInfo->noOp = true;
}
- Status ModifierPull::apply() const {
- dassert(_preparedState->noOp == false);
+ return Status::OK();
+}
- dassert(_preparedState->elemFound.ok() &&
- _preparedState->idxFound == (_fieldRef.numParts() - 1));
+Status ModifierPull::apply() const {
+ dassert(_preparedState->noOp == false);
- std::vector<mb::Element>::const_iterator where = _preparedState->elementsToRemove.begin();
- const std::vector<mb::Element>::const_iterator end = _preparedState->elementsToRemove.end();
- for ( ; where != end; ++where)
- const_cast<mb::Element&>(*where).remove();
+ dassert(_preparedState->elemFound.ok() &&
+ _preparedState->idxFound == (_fieldRef.numParts() - 1));
- return Status::OK();
- }
+ std::vector<mb::Element>::const_iterator where = _preparedState->elementsToRemove.begin();
+ const std::vector<mb::Element>::const_iterator end = _preparedState->elementsToRemove.end();
+ for (; where != end; ++where)
+ const_cast<mb::Element&>(*where).remove();
- Status ModifierPull::log(LogBuilder* logBuilder) const {
+ return Status::OK();
+}
- mb::Document& doc = logBuilder->getDocument();
+Status ModifierPull::log(LogBuilder* logBuilder) const {
+ mb::Document& doc = logBuilder->getDocument();
- if (!_preparedState->elemFound.ok() ||
- _preparedState->idxFound < (_fieldRef.numParts() - 1)) {
+ if (!_preparedState->elemFound.ok() || _preparedState->idxFound < (_fieldRef.numParts() - 1)) {
+ // If we didn't find the element that we wanted to pull from, we log an unset for
+ // that element.
+ return logBuilder->addToUnsets(_fieldRef.dottedField());
- // If we didn't find the element that we wanted to pull from, we log an unset for
- // that element.
- return logBuilder->addToUnsets(_fieldRef.dottedField());
+ } else {
+ // TODO: This is copied more or less identically from $push. As a result, it copies the
+ // behavior in $push that relies on 'apply' having been called unless this is a no-op.
- } else {
+ // TODO We can log just a positional unset in several cases. For now, let's just log
+ // the full resulting array.
- // TODO: This is copied more or less identically from $push. As a result, it copies the
- // behavior in $push that relies on 'apply' having been called unless this is a no-op.
+ // We'd like to create an entry such as {$set: {<fieldname>: [<resulting aray>]}} under
+ // 'logRoot'. We start by creating the {$set: ...} Element.
- // TODO We can log just a positional unset in several cases. For now, let's just log
- // the full resulting array.
+ // Then we create the {<fieldname>:[]} Element, that is, an empty array.
+ mb::Element logElement = doc.makeElementArray(_fieldRef.dottedField());
+ if (!logElement.ok()) {
+ return Status(ErrorCodes::InternalError, "cannot create details for $pull mod");
+ }
- // We'd like to create an entry such as {$set: {<fieldname>: [<resulting aray>]}} under
- // 'logRoot'. We start by creating the {$set: ...} Element.
+ mb::Element curr = _preparedState->elemFound.leftChild();
+ while (curr.ok()) {
+ dassert(curr.hasValue());
- // Then we create the {<fieldname>:[]} Element, that is, an empty array.
- mb::Element logElement = doc.makeElementArray(_fieldRef.dottedField());
- if (!logElement.ok()) {
- return Status(ErrorCodes::InternalError, "cannot create details for $pull mod");
+ // We need to copy each array entry from the resulting document to the log
+ // document.
+ mb::Element currCopy = doc.makeElementWithNewFieldName(StringData(), curr.getValue());
+ if (!currCopy.ok()) {
+ return Status(ErrorCodes::InternalError, "could create copy element");
}
-
- mb::Element curr = _preparedState->elemFound.leftChild();
- while (curr.ok()) {
-
- dassert(curr.hasValue());
-
- // We need to copy each array entry from the resulting document to the log
- // document.
- mb::Element currCopy = doc.makeElementWithNewFieldName(
- StringData(),
- curr.getValue());
- if (!currCopy.ok()) {
- return Status(ErrorCodes::InternalError, "could create copy element");
- }
- Status status = logElement.pushBack(currCopy);
- if (!status.isOK()) {
- return Status(ErrorCodes::BadValue, "could not append entry for $pull log");
- }
- curr = curr.rightSibling();
+ Status status = logElement.pushBack(currCopy);
+ if (!status.isOK()) {
+ return Status(ErrorCodes::BadValue, "could not append entry for $pull log");
}
-
- return logBuilder->addToSets(logElement);
+ curr = curr.rightSibling();
}
- }
- bool ModifierPull::isMatch(mutablebson::ConstElement element) {
+ return logBuilder->addToSets(logElement);
+ }
+}
- // TODO: We are assuming that 'element' hasValue is true. That might be OK if the
- // conflict detection logic will prevent us from ever seeing a deserialized element,
- // but are we sure about that?
+bool ModifierPull::isMatch(mutablebson::ConstElement element) {
+ // TODO: We are assuming that 'element' hasValue is true. That might be OK if the
+ // conflict detection logic will prevent us from ever seeing a deserialized element,
+ // but are we sure about that?
- dassert(element.hasValue());
+ dassert(element.hasValue());
- if (!_matchExpr)
- return (element.compareWithBSONElement(_exprElt, false) == 0);
+ if (!_matchExpr)
+ return (element.compareWithBSONElement(_exprElt, false) == 0);
- if (_matcherOnPrimitive) {
- // TODO: This is kinda slow.
- BSONObj candidate = element.getValue().wrap("");
- return _matchExpr->matchesBSON(candidate);
- }
+ if (_matcherOnPrimitive) {
+ // TODO: This is kinda slow.
+ BSONObj candidate = element.getValue().wrap("");
+ return _matchExpr->matchesBSON(candidate);
+ }
- if (element.getType() != Object)
- return false;
+ if (element.getType() != Object)
+ return false;
- return _matchExpr->matchesBSON(element.getValueObject());
- }
+ return _matchExpr->matchesBSON(element.getValueObject());
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_pull.h b/src/mongo/db/ops/modifier_pull.h
index 4660fd28b62..957750518f0 100644
--- a/src/mongo/db/ops/modifier_pull.h
+++ b/src/mongo/db/ops/modifier_pull.h
@@ -36,52 +36,49 @@
namespace mongo {
- class MatchExpression;
+class MatchExpression;
- class ModifierPull : public ModifierInterface {
- MONGO_DISALLOW_COPYING(ModifierPull);
+class ModifierPull : public ModifierInterface {
+ MONGO_DISALLOW_COPYING(ModifierPull);
- public:
- ModifierPull();
- virtual ~ModifierPull();
+public:
+ ModifierPull();
+ virtual ~ModifierPull();
- /** Evaluates the array items to be removed and the match expression. */
- virtual Status init(const BSONElement& modExpr, const Options& opts,
- bool* positional = NULL);
+ /** Evaluates the array items to be removed and the match expression. */
+ virtual Status init(const BSONElement& modExpr, const Options& opts, bool* positional = NULL);
- /** Decides which portion of the array items will be removed from the provided element */
- virtual Status prepare(mutablebson::Element root,
- StringData matchedField,
- ExecInfo* execInfo);
+ /** Decides which portion of the array items will be removed from the provided element */
+ virtual Status prepare(mutablebson::Element root, StringData matchedField, ExecInfo* execInfo);
- /** Updates the Element used in prepare with the effects of the $pull operation. */
- virtual Status apply() const;
+ /** Updates the Element used in prepare with the effects of the $pull operation. */
+ virtual Status apply() const;
- /** Converts the effects of this $pull into one or more equivalent $unset operations. */
- virtual Status log(LogBuilder* logBuilder) const;
+ /** Converts the effects of this $pull into one or more equivalent $unset operations. */
+ virtual Status log(LogBuilder* logBuilder) const;
- private:
- bool isMatch(mutablebson::ConstElement element);
+private:
+ bool isMatch(mutablebson::ConstElement element);
- // Access to each component of fieldName that's the target of this mod.
- FieldRef _fieldRef;
+ // Access to each component of fieldName that's the target of this mod.
+ FieldRef _fieldRef;
- // 0 or index for $-positional in _fieldRef.
- size_t _posDollar;
+ // 0 or index for $-positional in _fieldRef.
+ size_t _posDollar;
- // If we aren't using a matcher, we just keep modExpr as _exprElt and use that to match
- // with woCompare.
- BSONElement _exprElt;
+ // If we aren't using a matcher, we just keep modExpr as _exprElt and use that to match
+ // with woCompare.
+ BSONElement _exprElt;
- // If we are using a matcher, we need to keep around a BSONObj for it.
- BSONObj _exprObj;
+ // If we are using a matcher, we need to keep around a BSONObj for it.
+ BSONObj _exprObj;
- // If we are using the matcher, this is the match expression we built around _exprObj.
- std::unique_ptr<MatchExpression> _matchExpr;
- bool _matcherOnPrimitive;
+ // If we are using the matcher, this is the match expression we built around _exprObj.
+ std::unique_ptr<MatchExpression> _matchExpr;
+ bool _matcherOnPrimitive;
- struct PreparedState;
- std::unique_ptr<PreparedState> _preparedState;
- };
+ struct PreparedState;
+ std::unique_ptr<PreparedState> _preparedState;
+};
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_pull_all.cpp b/src/mongo/db/ops/modifier_pull_all.cpp
index c8a73abe1e0..287dc4828b4 100644
--- a/src/mongo/db/ops/modifier_pull_all.cpp
+++ b/src/mongo/db/ops/modifier_pull_all.cpp
@@ -38,217 +38,198 @@
namespace mongo {
- using std::vector;
+using std::vector;
- namespace mb = mutablebson;
- namespace str = mongoutils::str;
+namespace mb = mutablebson;
+namespace str = mongoutils::str;
- struct ModifierPullAll::PreparedState {
+struct ModifierPullAll::PreparedState {
+ PreparedState(mutablebson::Document* targetDoc)
+ : doc(*targetDoc),
+ pathFoundIndex(0),
+ pathFoundElement(doc.end()),
+ applyCalled(false),
+ elementsToRemove() {}
- PreparedState(mutablebson::Document* targetDoc)
- : doc(*targetDoc)
- , pathFoundIndex(0)
- , pathFoundElement(doc.end())
- , applyCalled(false)
- , elementsToRemove() {
- }
-
- // Document that is going to be changed.
- mutablebson::Document& doc;
+ // Document that is going to be changed.
+ mutablebson::Document& doc;
- // Index in _fieldRef for which an Element exist in the document.
- size_t pathFoundIndex;
+ // Index in _fieldRef for which an Element exist in the document.
+ size_t pathFoundIndex;
- // Element corresponding to _fieldRef[0.._idxFound].
- mutablebson::Element pathFoundElement;
+ // Element corresponding to _fieldRef[0.._idxFound].
+ mutablebson::Element pathFoundElement;
- bool applyCalled;
+ bool applyCalled;
- // Elements to be removed
- vector<mutablebson::Element> elementsToRemove;
- };
+ // Elements to be removed
+ vector<mutablebson::Element> elementsToRemove;
+};
- namespace {
+namespace {
- struct mutableElementEqualsBSONElement : std::unary_function<BSONElement, bool>
- {
- mutableElementEqualsBSONElement(const mutablebson::Element& elem) : _what(elem) {}
- bool operator()(const BSONElement& elem) const {
- return _what.compareWithBSONElement(elem, false) == 0;
- }
- const mutablebson::Element& _what;
- };
- } // namespace
-
- ModifierPullAll::ModifierPullAll()
- : _fieldRef()
- , _positionalPathIndex(0)
- , _elementsToFind() {
+struct mutableElementEqualsBSONElement : std::unary_function<BSONElement, bool> {
+ mutableElementEqualsBSONElement(const mutablebson::Element& elem) : _what(elem) {}
+ bool operator()(const BSONElement& elem) const {
+ return _what.compareWithBSONElement(elem, false) == 0;
}
+ const mutablebson::Element& _what;
+};
+} // namespace
+
+ModifierPullAll::ModifierPullAll() : _fieldRef(), _positionalPathIndex(0), _elementsToFind() {}
+
+ModifierPullAll::~ModifierPullAll() {}
- ModifierPullAll::~ModifierPullAll() {
+Status ModifierPullAll::init(const BSONElement& modExpr, const Options& opts, bool* positional) {
+ //
+ // field name analysis
+ //
+
+ // Break down the field name into its 'dotted' components (aka parts) and check that
+ // there are no empty parts.
+ _fieldRef.parse(modExpr.fieldName());
+ Status status = fieldchecker::isUpdatable(_fieldRef);
+ if (!status.isOK()) {
+ return status;
}
- Status ModifierPullAll::init(const BSONElement& modExpr, const Options& opts,
- bool* positional) {
+ // If a $-positional operator was used, get the index in which it occurred
+ // and ensure only one occurrence.
+ size_t foundCount;
+ bool foundDollar = fieldchecker::isPositional(_fieldRef, &_positionalPathIndex, &foundCount);
- //
- // field name analysis
- //
+ if (positional)
+ *positional = foundDollar;
- // Break down the field name into its 'dotted' components (aka parts) and check that
- // there are no empty parts.
- _fieldRef.parse(modExpr.fieldName());
- Status status = fieldchecker::isUpdatable(_fieldRef);
- if (! status.isOK()) {
- return status;
- }
+ if (foundDollar && foundCount > 1) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "Too many positional (i.e. '$') elements found in path '"
+ << _fieldRef.dottedField() << "'");
+ }
- // If a $-positional operator was used, get the index in which it occurred
- // and ensure only one occurrence.
- size_t foundCount;
- bool foundDollar = fieldchecker::isPositional(_fieldRef,
- &_positionalPathIndex,
- &foundCount);
+ //
+ // value analysis
+ //
- if (positional)
- *positional = foundDollar;
+ if (modExpr.type() != Array) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "$pullAll requires an array argument but was given a "
+ << typeName(modExpr.type()));
+ }
- if (foundDollar && foundCount > 1) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "Too many positional (i.e. '$') elements found in path '"
- << _fieldRef.dottedField() << "'");
- }
+ // store the stuff to remove later
+ _elementsToFind = modExpr.Array();
+
+ return Status::OK();
+}
- //
- // value analysis
- //
+Status ModifierPullAll::prepare(mutablebson::Element root,
+ StringData matchedField,
+ ExecInfo* execInfo) {
+ _preparedState.reset(new PreparedState(&root.getDocument()));
- if (modExpr.type() != Array) {
+ // If we have a $-positional field, it is time to bind it to an actual field part.
+ if (_positionalPathIndex) {
+ if (matchedField.empty()) {
return Status(ErrorCodes::BadValue,
- str::stream() << "$pullAll requires an array argument but was given a "
- << typeName(modExpr.type()));
+ str::stream() << "The positional operator did not find the match "
+ "needed from the query. Unexpanded update: "
+ << _fieldRef.dottedField());
}
-
- // store the stuff to remove later
- _elementsToFind = modExpr.Array();
-
- return Status::OK();
+ _fieldRef.setPart(_positionalPathIndex, matchedField);
}
- Status ModifierPullAll::prepare(mutablebson::Element root,
- StringData matchedField,
- ExecInfo* execInfo) {
+ // Locate the field name in 'root'. Note that if we don't have the full path in the
+ // doc, there isn't anything to unset, really.
+ Status status = pathsupport::findLongestPrefix(
+ _fieldRef, root, &_preparedState->pathFoundIndex, &_preparedState->pathFoundElement);
+ // Check if we didn't find the full path
+ if (status.isOK()) {
+ const bool destExists = (_preparedState->pathFoundIndex == (_fieldRef.numParts() - 1));
- _preparedState.reset(new PreparedState(&root.getDocument()));
-
- // If we have a $-positional field, it is time to bind it to an actual field part.
- if (_positionalPathIndex) {
- if (matchedField.empty()) {
+ if (!destExists) {
+ execInfo->noOp = true;
+ } else {
+ // If the path exists, we require the target field to be already an
+ // array.
+ if (_preparedState->pathFoundElement.getType() != Array) {
+ mb::Element idElem = mb::findElementNamed(root.leftChild(), "_id");
return Status(ErrorCodes::BadValue,
- str::stream() << "The positional operator did not find the match "
- "needed from the query. Unexpanded update: "
- << _fieldRef.dottedField());
+ str::stream()
+ << "Can only apply $pullAll to an array. " << idElem.toString()
+ << " has the field "
+ << _preparedState->pathFoundElement.getFieldName()
+ << " of non-array type "
+ << typeName(_preparedState->pathFoundElement.getType()));
}
- _fieldRef.setPart(_positionalPathIndex, matchedField);
- }
- // Locate the field name in 'root'. Note that if we don't have the full path in the
- // doc, there isn't anything to unset, really.
- Status status = pathsupport::findLongestPrefix(_fieldRef,
- root,
- &_preparedState->pathFoundIndex,
- &_preparedState->pathFoundElement);
- // Check if we didn't find the full path
- if (status.isOK()) {
- const bool destExists = (_preparedState->pathFoundIndex == (_fieldRef.numParts()-1));
-
- if (!destExists) {
+ // No children, nothing to do -- not an error state
+ if (!_preparedState->pathFoundElement.hasChildren()) {
execInfo->noOp = true;
} else {
- // If the path exists, we require the target field to be already an
- // array.
- if (_preparedState->pathFoundElement.getType() != Array) {
- mb::Element idElem = mb::findElementNamed(root.leftChild(), "_id");
- return Status(
- ErrorCodes::BadValue,
- str::stream() << "Can only apply $pullAll to an array. "
- << idElem.toString()
- << " has the field "
- << _preparedState->pathFoundElement.getFieldName()
- << " of non-array type "
- << typeName(_preparedState->pathFoundElement.getType()));
+ mutablebson::Element elem = _preparedState->pathFoundElement.leftChild();
+ while (elem.ok()) {
+ if (std::find_if(_elementsToFind.begin(),
+ _elementsToFind.end(),
+ mutableElementEqualsBSONElement(elem)) !=
+ _elementsToFind.end()) {
+ _preparedState->elementsToRemove.push_back(elem);
+ }
+ elem = elem.rightSibling();
}
- // No children, nothing to do -- not an error state
- if (!_preparedState->pathFoundElement.hasChildren()) {
+ // Nothing to remove so it is a noOp.
+ if (_preparedState->elementsToRemove.empty())
execInfo->noOp = true;
- } else {
- mutablebson::Element elem = _preparedState->pathFoundElement.leftChild();
- while (elem.ok()) {
- if (std::find_if(_elementsToFind.begin(),
- _elementsToFind.end(),
- mutableElementEqualsBSONElement(elem))
- != _elementsToFind.end()) {
- _preparedState->elementsToRemove.push_back(elem);
- }
- elem = elem.rightSibling();
- }
-
- // Nothing to remove so it is a noOp.
- if (_preparedState->elementsToRemove.empty())
- execInfo->noOp = true;
- }
}
- } else {
- // Let the caller know we can't do anything given the mod, _fieldRef, and doc.
- execInfo->noOp = true;
-
-
- //okay if path not found
- if (status.code() == ErrorCodes::NonExistentPath)
- status = Status::OK();
}
+ } else {
+ // Let the caller know we can't do anything given the mod, _fieldRef, and doc.
+ execInfo->noOp = true;
- // Let the caller know what field we care about
- execInfo->fieldRef[0] = &_fieldRef;
- return status;
+ // okay if path not found
+ if (status.code() == ErrorCodes::NonExistentPath)
+ status = Status::OK();
}
- Status ModifierPullAll::apply() const {
- _preparedState->applyCalled = true;
+ // Let the caller know what field we care about
+ execInfo->fieldRef[0] = &_fieldRef;
- vector<mutablebson::Element>::const_iterator curr =
- _preparedState->elementsToRemove.begin();
- const vector<mutablebson::Element>::const_iterator end =
- _preparedState->elementsToRemove.end();
- for ( ; curr != end; ++curr) {
- const_cast<mutablebson::Element&>(*curr).remove();
- }
- return Status::OK();
- }
+ return status;
+}
- Status ModifierPullAll::log(LogBuilder* logBuilder) const {
- // log document
- mutablebson::Document& doc = logBuilder->getDocument();
- const bool pathExists = _preparedState->pathFoundElement.ok() &&
- (_preparedState->pathFoundIndex == (_fieldRef.numParts() - 1));
-
- if (!pathExists)
- return logBuilder->addToUnsets(_fieldRef.dottedField());
-
- // value for the logElement ("field.path.name": <value>)
- mutablebson::Element logElement = doc.makeElementWithNewFieldName(
- _fieldRef.dottedField(),
- _preparedState->pathFoundElement);
-
- if (!logElement.ok()) {
- return Status(ErrorCodes::InternalError,
- str::stream() << "Could not append entry to $pullAll oplog entry: "
- << "set '" << _fieldRef.dottedField() << "' -> "
- << _preparedState->pathFoundElement.toString() );
- }
- return logBuilder->addToSets(logElement);
+Status ModifierPullAll::apply() const {
+ _preparedState->applyCalled = true;
+
+ vector<mutablebson::Element>::const_iterator curr = _preparedState->elementsToRemove.begin();
+ const vector<mutablebson::Element>::const_iterator end = _preparedState->elementsToRemove.end();
+ for (; curr != end; ++curr) {
+ const_cast<mutablebson::Element&>(*curr).remove();
+ }
+ return Status::OK();
+}
+
+Status ModifierPullAll::log(LogBuilder* logBuilder) const {
+ // log document
+ mutablebson::Document& doc = logBuilder->getDocument();
+ const bool pathExists = _preparedState->pathFoundElement.ok() &&
+ (_preparedState->pathFoundIndex == (_fieldRef.numParts() - 1));
+
+ if (!pathExists)
+ return logBuilder->addToUnsets(_fieldRef.dottedField());
+
+ // value for the logElement ("field.path.name": <value>)
+ mutablebson::Element logElement =
+ doc.makeElementWithNewFieldName(_fieldRef.dottedField(), _preparedState->pathFoundElement);
+
+ if (!logElement.ok()) {
+ return Status(ErrorCodes::InternalError,
+ str::stream() << "Could not append entry to $pullAll oplog entry: "
+ << "set '" << _fieldRef.dottedField() << "' -> "
+ << _preparedState->pathFoundElement.toString());
}
-} // namespace mongo
+ return logBuilder->addToSets(logElement);
+}
+} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_pull_all.h b/src/mongo/db/ops/modifier_pull_all.h
index fef6c7b03f3..9f7efb8ac8d 100644
--- a/src/mongo/db/ops/modifier_pull_all.h
+++ b/src/mongo/db/ops/modifier_pull_all.h
@@ -38,50 +38,44 @@
namespace mongo {
- class LogBuilder;
+class LogBuilder;
- class ModifierPullAll : public ModifierInterface {
- MONGO_DISALLOW_COPYING(ModifierPullAll);
+class ModifierPullAll : public ModifierInterface {
+ MONGO_DISALLOW_COPYING(ModifierPullAll);
- public:
+public:
+ ModifierPullAll();
+ virtual ~ModifierPullAll();
- ModifierPullAll();
- virtual ~ModifierPullAll();
+ /**
+ * The modifier $pullAll takes an array of values to match literally, and remove
+ *
+ * Ex. {$pullAll : {<field> : [<values>]}}
+ * {$pullAll :{ array : [1,2] } } will transform {array: [1,2,3]} -> {array: [3]}
+ */
+ virtual Status init(const BSONElement& modExpr, const Options& opts, bool* positional = NULL);
- /**
- * The modifier $pullAll takes an array of values to match literally, and remove
- *
- * Ex. {$pullAll : {<field> : [<values>]}}
- * {$pullAll :{ array : [1,2] } } will transform {array: [1,2,3]} -> {array: [3]}
- */
- virtual Status init(const BSONElement& modExpr, const Options& opts,
- bool* positional = NULL);
+ virtual Status prepare(mutablebson::Element root, StringData matchedField, ExecInfo* execInfo);
- virtual Status prepare(mutablebson::Element root,
- StringData matchedField,
- ExecInfo* execInfo);
+ virtual Status apply() const;
- virtual Status apply() const;
+ virtual Status log(LogBuilder* logBuilder) const;
- virtual Status log(LogBuilder* logBuilder) const;
+private:
+ // Access to each component of fieldName that's the target of this mod.
+ FieldRef _fieldRef;
- private:
+ // 0 or index for $-positional in _fieldRef.
+ size_t _positionalPathIndex;
- // Access to each component of fieldName that's the target of this mod.
- FieldRef _fieldRef;
+ // The instance of the field in the provided doc.
+ // This data is valid after prepare, for use by log and apply
+ struct PreparedState;
+ std::unique_ptr<PreparedState> _preparedState;
- // 0 or index for $-positional in _fieldRef.
- size_t _positionalPathIndex;
+ // User specified elements to remove
+ std::vector<BSONElement> _elementsToFind;
+};
- // The instance of the field in the provided doc.
- // This data is valid after prepare, for use by log and apply
- struct PreparedState;
- std::unique_ptr<PreparedState> _preparedState;
-
- // User specified elements to remove
- std::vector<BSONElement> _elementsToFind;
-
- };
-
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_pull_all_test.cpp b/src/mongo/db/ops/modifier_pull_all_test.cpp
index 4d689f36ee5..4b22f4d97a8 100644
--- a/src/mongo/db/ops/modifier_pull_all_test.cpp
+++ b/src/mongo/db/ops/modifier_pull_all_test.cpp
@@ -42,209 +42,206 @@
namespace {
- using mongo::BSONObj;
- using mongo::LogBuilder;
- using mongo::ModifierPullAll;
- using mongo::ModifierInterface;
- using mongo::NumberInt;
- using mongo::Status;
- using mongo::StringData;
- using mongo::fromjson;
- using mongo::mutablebson::ConstElement;
- using mongo::mutablebson::Document;
- using mongo::mutablebson::Element;
-
- /** Helper to build and manipulate the mod. */
- class Mod {
- public:
- Mod() : _mod() {}
-
- explicit Mod(BSONObj modObj)
- : _modObj(modObj)
- , _mod() {
- ASSERT_OK(_mod.init(_modObj["$pullAll"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
-
- 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);
- }
-
- ModifierPullAll& mod() { return _mod; }
-
- private:
- BSONObj _modObj;
- ModifierPullAll _mod;
- };
-
- TEST(Init, BadThings) {
- BSONObj modObj;
- ModifierPullAll mod;
-
- modObj = fromjson("{$pullAll: {a:1}}");
- ASSERT_NOT_OK(mod.init(modObj["$pullAll"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
-
- modObj = fromjson("{$pullAll: {a:'test'}}");
- ASSERT_NOT_OK(mod.init(modObj["$pullAll"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
-
- modObj = fromjson("{$pullAll: {a:{}}}");
- ASSERT_NOT_OK(mod.init(modObj["$pullAll"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
-
- modObj = fromjson("{$pullAll: {a:true}}");
- ASSERT_NOT_OK(mod.init(modObj["$pullAll"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
-
- }
-
- TEST(PrepareApply, SimpleNumber) {
- Document doc(fromjson("{ a : [1, 'a', {r:1, b:2}] }"));
- Mod mod(fromjson("{ $pullAll : { a : [1] } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : ['a', {r:1, b:2}] }"), doc);
- }
-
- TEST(PrepareApply, MissingElement) {
- Document doc(fromjson("{ a : [1, 'a', {r:1, b:2}] }"));
- Mod mod(fromjson("{ $pullAll : { a : ['r'] } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [1, 'a', {r:1, b:2}] }"), doc);
- }
-
- TEST(PrepareApply, TwoElements) {
- Document doc(fromjson("{ a : [1, 'a', {r:1, b:2}] }"));
- Mod mod(fromjson("{ $pullAll : { a : [1, 'a'] } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [{r:1, b:2}] }"), doc);
- }
-
- TEST(EmptyResult, RemoveEverythingOutOfOrder) {
- Document doc(fromjson("{ a : [1, 'a', {r:1, b:2}] }"));
- Mod mod(fromjson("{ $pullAll : {a : [ {r:1, b:2}, 1, 'a' ] }}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [] } }"), logDoc);
+using mongo::BSONObj;
+using mongo::LogBuilder;
+using mongo::ModifierPullAll;
+using mongo::ModifierInterface;
+using mongo::NumberInt;
+using mongo::Status;
+using mongo::StringData;
+using mongo::fromjson;
+using mongo::mutablebson::ConstElement;
+using mongo::mutablebson::Document;
+using mongo::mutablebson::Element;
+
+/** Helper to build and manipulate the mod. */
+class Mod {
+public:
+ Mod() : _mod() {}
+
+ explicit Mod(BSONObj modObj) : _modObj(modObj), _mod() {
+ ASSERT_OK(_mod.init(_modObj["$pullAll"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
}
- TEST(EmptyResult, RemoveEverythingInOrder) {
- Document doc(fromjson("{ a : [1, 'a', {r:1, b:2}] }"));
- Mod mod(fromjson("{ $pullAll : { a : [1, 'a', {r:1, b:2} ] } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [] } }"), logDoc);
+ Status prepare(Element root, StringData matchedField, ModifierInterface::ExecInfo* execInfo) {
+ return _mod.prepare(root, matchedField, execInfo);
}
- TEST(EmptyResult, RemoveEverythingAndThenSome) {
- Document doc(fromjson("{ a : [1, 'a', {r:1, b:2}] }"));
- Mod mod(fromjson("{ $pullAll : { a : [2,3,1,'r', {r:1, b:2}, 'a' ] } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [] } }"), logDoc);
+ Status apply() const {
+ return _mod.apply();
}
- TEST(PrepareLog, MissingPullValue) {
- Document doc(fromjson("{ a : [1, 'a', {r:1, b:2}] }"));
- Mod mod(fromjson("{ $pullAll : { a : [2] } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [1, 'a', {r:1, b:2}] } }"), logDoc);
+ Status log(LogBuilder* logBuilder) const {
+ return _mod.log(logBuilder);
}
- TEST(PrepareLog, MissingPath) {
- Document doc(fromjson("{ a : [1, 'a', {r:1, b:2}] }"));
- Mod mod(fromjson("{ $pullAll : { b : [1] } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $unset : { b : true } }"), logDoc);
- }
-
- TEST(Prepare, MissingArrayElementPath) {
- Document doc(fromjson("{ a : [1, 2] }"));
- Mod mod(fromjson("{ $pullAll : { 'a.2' : [1] } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
- }
-
- TEST(Prepare, FromArrayElementPath) {
- Document doc(fromjson("{ a : [1, 2] }"));
- Mod mod(fromjson("{ $pullAll : { 'a.0' : [1] } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ModifierPullAll& mod() {
+ return _mod;
}
-} // namespace
+private:
+ BSONObj _modObj;
+ ModifierPullAll _mod;
+};
+
+TEST(Init, BadThings) {
+ BSONObj modObj;
+ ModifierPullAll mod;
+
+ modObj = fromjson("{$pullAll: {a:1}}");
+ ASSERT_NOT_OK(mod.init(modObj["$pullAll"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+
+ modObj = fromjson("{$pullAll: {a:'test'}}");
+ ASSERT_NOT_OK(mod.init(modObj["$pullAll"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+
+ modObj = fromjson("{$pullAll: {a:{}}}");
+ ASSERT_NOT_OK(mod.init(modObj["$pullAll"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+
+ modObj = fromjson("{$pullAll: {a:true}}");
+ ASSERT_NOT_OK(mod.init(modObj["$pullAll"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
+
+TEST(PrepareApply, SimpleNumber) {
+ Document doc(fromjson("{ a : [1, 'a', {r:1, b:2}] }"));
+ Mod mod(fromjson("{ $pullAll : { a : [1] } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : ['a', {r:1, b:2}] }"), doc);
+}
+
+TEST(PrepareApply, MissingElement) {
+ Document doc(fromjson("{ a : [1, 'a', {r:1, b:2}] }"));
+ Mod mod(fromjson("{ $pullAll : { a : ['r'] } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : [1, 'a', {r:1, b:2}] }"), doc);
+}
+
+TEST(PrepareApply, TwoElements) {
+ Document doc(fromjson("{ a : [1, 'a', {r:1, b:2}] }"));
+ Mod mod(fromjson("{ $pullAll : { a : [1, 'a'] } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : [{r:1, b:2}] }"), doc);
+}
+
+TEST(EmptyResult, RemoveEverythingOutOfOrder) {
+ Document doc(fromjson("{ a : [1, 'a', {r:1, b:2}] }"));
+ Mod mod(fromjson("{ $pullAll : {a : [ {r:1, b:2}, 1, 'a' ] }}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : [] }"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : [] } }"), logDoc);
+}
+
+TEST(EmptyResult, RemoveEverythingInOrder) {
+ Document doc(fromjson("{ a : [1, 'a', {r:1, b:2}] }"));
+ Mod mod(fromjson("{ $pullAll : { a : [1, 'a', {r:1, b:2} ] } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : [] }"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : [] } }"), logDoc);
+}
+
+TEST(EmptyResult, RemoveEverythingAndThenSome) {
+ Document doc(fromjson("{ a : [1, 'a', {r:1, b:2}] }"));
+ Mod mod(fromjson("{ $pullAll : { a : [2,3,1,'r', {r:1, b:2}, 'a' ] } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : [] }"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : [] } }"), logDoc);
+}
+
+TEST(PrepareLog, MissingPullValue) {
+ Document doc(fromjson("{ a : [1, 'a', {r:1, b:2}] }"));
+ Mod mod(fromjson("{ $pullAll : { a : [2] } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.noOp);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : [1, 'a', {r:1, b:2}] } }"), logDoc);
+}
+
+TEST(PrepareLog, MissingPath) {
+ Document doc(fromjson("{ a : [1, 'a', {r:1, b:2}] }"));
+ Mod mod(fromjson("{ $pullAll : { b : [1] } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.noOp);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $unset : { b : true } }"), logDoc);
+}
+
+TEST(Prepare, MissingArrayElementPath) {
+ Document doc(fromjson("{ a : [1, 2] }"));
+ Mod mod(fromjson("{ $pullAll : { 'a.2' : [1] } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.noOp);
+}
+
+TEST(Prepare, FromArrayElementPath) {
+ Document doc(fromjson("{ a : [1, 2] }"));
+ Mod mod(fromjson("{ $pullAll : { 'a.0' : [1] } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_NOT_OK(mod.prepare(doc.root(), "", &execInfo));
+}
+
+} // namespace
diff --git a/src/mongo/db/ops/modifier_pull_test.cpp b/src/mongo/db/ops/modifier_pull_test.cpp
index e0918b24db5..8862ac62575 100644
--- a/src/mongo/db/ops/modifier_pull_test.cpp
+++ b/src/mongo/db/ops/modifier_pull_test.cpp
@@ -40,537 +40,516 @@
namespace {
- using mongo::BSONObj;
- using mongo::LogBuilder;
- using mongo::ModifierPull;
- using mongo::ModifierInterface;
- using mongo::Status;
- using mongo::StringData;
- using mongo::fromjson;
- using mongo::mutablebson::Document;
- using mongo::mutablebson::Element;
-
- /** Helper to build and manipulate a $pull mod. */
- class Mod {
- public:
- Mod() : _mod() {}
-
- explicit Mod(BSONObj modObj)
- : _modObj(modObj)
- , _mod() {
- ASSERT_OK(_mod.init(_modObj["$pull"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
-
- 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);
- }
-
- ModifierPull& mod() {
- return _mod;
- }
-
- private:
- BSONObj _modObj;
- ModifierPull _mod;
- };
-
- TEST(SimpleMod, PrepareOKTargetNotFound) {
- Document doc(fromjson("{}"));
- Mod mod(fromjson("{ $pull : { a : { $lt : 1 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $unset : { a : true } }"), logDoc);
- }
-
- TEST(SimpleMod, PrepareOKTargetFound) {
- Document doc(fromjson("{ a : [ 0, 1, 2, 3 ] }"));
- Mod mod(fromjson("{ $pull : { a : { $lt : 1 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
- }
-
- TEST(SimpleMod, PrepareInvalidTargetString) {
- Document doc(fromjson("{ a : 'foo' }"));
- Mod mod(fromjson("{ $pull : { a : { $lt : 1 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(mod.prepare(doc.root(), "", &execInfo));
+using mongo::BSONObj;
+using mongo::LogBuilder;
+using mongo::ModifierPull;
+using mongo::ModifierInterface;
+using mongo::Status;
+using mongo::StringData;
+using mongo::fromjson;
+using mongo::mutablebson::Document;
+using mongo::mutablebson::Element;
+
+/** Helper to build and manipulate a $pull mod. */
+class Mod {
+public:
+ Mod() : _mod() {}
+
+ explicit Mod(BSONObj modObj) : _modObj(modObj), _mod() {
+ ASSERT_OK(_mod.init(_modObj["$pull"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
}
- TEST(SimpleMod, PrepareInvalidTargetObject) {
- Document doc(fromjson("{ a : { 'foo' : 'bar' } }"));
- Mod mod(fromjson("{ $pull : { a : { $lt : 1 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(mod.prepare(doc.root(), "", &execInfo));
- }
-
- TEST(SimpleMod, PrepareAndLogEmptyDocument) {
- Document doc(fromjson("{}"));
- Mod mod(fromjson("{ $pull : { a : { $lt : 1 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $unset : { a : true } }"), logDoc);
- }
-
- TEST(SimpleMod, PrepareAndLogMissingElementAfterFoundPath) {
- Document doc(fromjson("{ a : { b : { c : {} } } }"));
- Mod mod(fromjson("{ $pull : { 'a.b.c.d' : { $lt : 1 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b.c.d");
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $unset : { 'a.b.c.d' : true } }"), logDoc);
- }
-
- TEST(SimpleMod, PrepareAndLogEmptyArray) {
- Document doc(fromjson("{ a : [] }"));
- Mod mod(fromjson("{ $pull : { a : { $lt : 1 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [] } }"), logDoc);
- }
-
- TEST(SimpleMod, PullMatchesNone) {
- Document doc(fromjson("{ a : [2, 3, 4, 5] }"));
- Mod mod(fromjson("{ $pull : { a : { $lt : 1 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [2, 3, 4, 5] } }"), logDoc);
- }
-
- TEST(SimpleMod, ApplyAndLogPullMatchesOne) {
- Document doc(fromjson("{ a : [0, 1, 2, 3, 4, 5] }"));
- Mod mod(fromjson("{ $pull : { a : { $lt : 1 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [ 1, 2, 3, 4, 5 ] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [1, 2, 3, 4, 5] } }"), logDoc);
- }
-
- TEST(SimpleMod, ApplyAndLogPullMatchesSeveral) {
- Document doc(fromjson("{ a : [0, 1, 0, 2, 0, 3, 0, 4, 0, 5] }"));
- Mod mod(fromjson("{ $pull : { a : { $lt : 1 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [ 1, 2, 3, 4, 5 ] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [1, 2, 3, 4, 5] } }"), logDoc);
+ Status prepare(Element root, StringData matchedField, ModifierInterface::ExecInfo* execInfo) {
+ return _mod.prepare(root, matchedField, execInfo);
}
- TEST(SimpleMod, ApplyAndLogPullMatchesAll) {
- Document doc(fromjson("{ a : [0, -1, -2, -3, -4, -5] }"));
- Mod mod(fromjson("{ $pull : { a : { $lt : 1 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [] } }"), logDoc);
+ Status apply() const {
+ return _mod.apply();
}
- TEST(ComplexMod, ApplyAndLogComplexDocAndMatching1) {
-
- const char* const strings[] = {
- // Document:
- "{ a : { b : [ { x : 1 }, { y : 'y' }, { x : 2 }, { z : 'z' } ] } }",
-
- // Modifier:
- "{ $pull : { 'a.b' : { $or : [ "
- " { 'y' : { $exists : true } }, "
- " { 'z' : { $exists : true } } "
- "] } } }",
-
- // Document result:
- "{ a : { b : [ { x : 1 }, { x : 2 } ] } }",
-
- // Log result:
- "{ $set : { 'a.b' : [ { x : 1 }, { x : 2 } ] } }"
- };
-
- Document doc(fromjson(strings[0]));
- Mod mod(fromjson(strings[1]));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(strings[2]), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson(strings[3]), logDoc);
+ Status log(LogBuilder* logBuilder) const {
+ return _mod.log(logBuilder);
}
- TEST(ComplexMod, ApplyAndLogComplexDocAndMatching2) {
-
- const char* const strings[] = {
- // Document:
- "{ a : { b : [ { x : 1 }, { y : 'y' }, { x : 2 }, { z : 'z' } ] } }",
-
- // Modifier:
- "{ $pull : { 'a.b' : { 'y' : { $exists : true } } } }",
-
- // Document result:
- "{ a : { b : [ { x : 1 }, { x : 2 }, { z : 'z' } ] } }",
-
- // Log result:
- "{ $set : { 'a.b' : [ { x : 1 }, { x : 2 }, { z : 'z' } ] } }"
- };
-
- Document doc(fromjson(strings[0]));
- Mod mod(fromjson(strings[1]));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(strings[2]), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson(strings[3]), logDoc);
+ ModifierPull& mod() {
+ return _mod;
}
- TEST(ComplexMod, ApplyAndLogComplexDocAndMatching3) {
+private:
+ BSONObj _modObj;
+ ModifierPull _mod;
+};
+
+TEST(SimpleMod, PrepareOKTargetNotFound) {
+ Document doc(fromjson("{}"));
+ Mod mod(fromjson("{ $pull : { a : { $lt : 1 } } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_TRUE(execInfo.noOp);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $unset : { a : true } }"), logDoc);
+}
+
+TEST(SimpleMod, PrepareOKTargetFound) {
+ Document doc(fromjson("{ a : [ 0, 1, 2, 3 ] }"));
+ Mod mod(fromjson("{ $pull : { a : { $lt : 1 } } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+}
+
+TEST(SimpleMod, PrepareInvalidTargetString) {
+ Document doc(fromjson("{ a : 'foo' }"));
+ Mod mod(fromjson("{ $pull : { a : { $lt : 1 } } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_NOT_OK(mod.prepare(doc.root(), "", &execInfo));
+}
+
+TEST(SimpleMod, PrepareInvalidTargetObject) {
+ Document doc(fromjson("{ a : { 'foo' : 'bar' } }"));
+ Mod mod(fromjson("{ $pull : { a : { $lt : 1 } } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_NOT_OK(mod.prepare(doc.root(), "", &execInfo));
+}
+
+TEST(SimpleMod, PrepareAndLogEmptyDocument) {
+ Document doc(fromjson("{}"));
+ Mod mod(fromjson("{ $pull : { a : { $lt : 1 } } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_TRUE(execInfo.noOp);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $unset : { a : true } }"), logDoc);
+}
+
+TEST(SimpleMod, PrepareAndLogMissingElementAfterFoundPath) {
+ Document doc(fromjson("{ a : { b : { c : {} } } }"));
+ Mod mod(fromjson("{ $pull : { 'a.b.c.d' : { $lt : 1 } } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b.c.d");
+ ASSERT_TRUE(execInfo.noOp);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $unset : { 'a.b.c.d' : true } }"), logDoc);
+}
+
+TEST(SimpleMod, PrepareAndLogEmptyArray) {
+ Document doc(fromjson("{ a : [] }"));
+ Mod mod(fromjson("{ $pull : { a : { $lt : 1 } } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_TRUE(execInfo.noOp);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : [] } }"), logDoc);
+}
+
+TEST(SimpleMod, PullMatchesNone) {
+ Document doc(fromjson("{ a : [2, 3, 4, 5] }"));
+ Mod mod(fromjson("{ $pull : { a : { $lt : 1 } } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_TRUE(execInfo.noOp);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : [2, 3, 4, 5] } }"), logDoc);
+}
+
+TEST(SimpleMod, ApplyAndLogPullMatchesOne) {
+ Document doc(fromjson("{ a : [0, 1, 2, 3, 4, 5] }"));
+ Mod mod(fromjson("{ $pull : { a : { $lt : 1 } } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : [ 1, 2, 3, 4, 5 ] }"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : [1, 2, 3, 4, 5] } }"), logDoc);
+}
+
+TEST(SimpleMod, ApplyAndLogPullMatchesSeveral) {
+ Document doc(fromjson("{ a : [0, 1, 0, 2, 0, 3, 0, 4, 0, 5] }"));
+ Mod mod(fromjson("{ $pull : { a : { $lt : 1 } } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : [ 1, 2, 3, 4, 5 ] }"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : [1, 2, 3, 4, 5] } }"), logDoc);
+}
+
+TEST(SimpleMod, ApplyAndLogPullMatchesAll) {
+ Document doc(fromjson("{ a : [0, -1, -2, -3, -4, -5] }"));
+ Mod mod(fromjson("{ $pull : { a : { $lt : 1 } } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ a : [] }"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{ $set : { a : [] } }"), logDoc);
+}
+
+TEST(ComplexMod, ApplyAndLogComplexDocAndMatching1) {
+ const char* const strings[] = {
+ // Document:
+ "{ a : { b : [ { x : 1 }, { y : 'y' }, { x : 2 }, { z : 'z' } ] } }",
+
+ // Modifier:
+ "{ $pull : { 'a.b' : { $or : [ "
+ " { 'y' : { $exists : true } }, "
+ " { 'z' : { $exists : true } } "
+ "] } } }",
+
+ // Document result:
+ "{ a : { b : [ { x : 1 }, { x : 2 } ] } }",
+
+ // Log result:
+ "{ $set : { 'a.b' : [ { x : 1 }, { x : 2 } ] } }"};
+
+ Document doc(fromjson(strings[0]));
+ Mod mod(fromjson(strings[1]));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson(strings[2]), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson(strings[3]), logDoc);
+}
+
+TEST(ComplexMod, ApplyAndLogComplexDocAndMatching2) {
+ const char* const strings[] = {
+ // Document:
+ "{ a : { b : [ { x : 1 }, { y : 'y' }, { x : 2 }, { z : 'z' } ] } }",
+
+ // Modifier:
+ "{ $pull : { 'a.b' : { 'y' : { $exists : true } } } }",
+
+ // Document result:
+ "{ a : { b : [ { x : 1 }, { x : 2 }, { z : 'z' } ] } }",
+
+ // Log result:
+ "{ $set : { 'a.b' : [ { x : 1 }, { x : 2 }, { z : 'z' } ] } }"};
+
+ Document doc(fromjson(strings[0]));
+ Mod mod(fromjson(strings[1]));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson(strings[2]), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson(strings[3]), logDoc);
+}
+
+TEST(ComplexMod, ApplyAndLogComplexDocAndMatching3) {
+ const char* const strings[] = {
+ // Document:
+ "{ a : { b : [ { x : 1 }, { y : 'y' }, { x : 2 }, { z : 'z' } ] } }",
+
+ // Modifier:
+ "{ $pull : { 'a.b' : { $in : [ { x : 1 }, { y : 'y' } ] } } }",
- const char* const strings[] = {
- // Document:
- "{ a : { b : [ { x : 1 }, { y : 'y' }, { x : 2 }, { z : 'z' } ] } }",
+ // Document result:
+ "{ a : { b : [ { x : 2 }, { z : 'z' } ] } }",
- // Modifier:
- "{ $pull : { 'a.b' : { $in : [ { x : 1 }, { y : 'y' } ] } } }",
+ // Log result:
+ "{ $set : { 'a.b' : [ { x : 2 }, { z : 'z' } ] } }"};
- // Document result:
- "{ a : { b : [ { x : 2 }, { z : 'z' } ] } }",
+ Document doc(fromjson(strings[0]));
+ Mod mod(fromjson(strings[1]));
- // Log result:
- "{ $set : { 'a.b' : [ { x : 2 }, { z : 'z' } ] } }"
- };
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
+ ASSERT_FALSE(execInfo.noOp);
- Document doc(fromjson(strings[0]));
- Mod mod(fromjson(strings[1]));
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson(strings[2]), doc);
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
- ASSERT_FALSE(execInfo.noOp);
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson(strings[3]), logDoc);
+}
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(strings[2]), doc);
+TEST(ValueMod, ApplyAndLogScalarValueMod) {
+ const char* const strings[] = {// Document:
+ "{ a : [1, 2, 1, 2, 1, 2] }",
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson(strings[3]), logDoc);
- }
+ // Modifier:
+ "{ $pull : { a : 1 } }",
- TEST(ValueMod, ApplyAndLogScalarValueMod) {
+ // Document result:
+ "{ a : [ 2, 2, 2] }",
- const char* const strings[] = {
- // Document:
- "{ a : [1, 2, 1, 2, 1, 2] }",
+ // Log result:
+ "{ $set : { a : [ 2, 2, 2 ] } }"};
- // Modifier:
- "{ $pull : { a : 1 } }",
+ Document doc(fromjson(strings[0]));
+ Mod mod(fromjson(strings[1]));
- // Document result:
- "{ a : [ 2, 2, 2] }",
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
- // Log result:
- "{ $set : { a : [ 2, 2, 2 ] } }"
- };
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson(strings[2]), doc);
- Document doc(fromjson(strings[0]));
- Mod mod(fromjson(strings[1]));
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson(strings[3]), logDoc);
+}
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
+TEST(ValueMod, ApplyAndLogObjectValueMod) {
+ const char* const strings[] = {// Document:
+ "{ a : [ { x : 1 }, { y : 2 }, { x : 1 }, { y : 2 } ] }",
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(strings[2]), doc);
+ // Modifier:
+ "{ $pull : { a : { y : 2 } } }",
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson(strings[3]), logDoc);
- }
+ // Document result:
+ "{ a : [ { x : 1 }, { x : 1 }] }",
- TEST(ValueMod, ApplyAndLogObjectValueMod) {
+ // Log result:
+ "{ $set : { a : [ { x : 1 }, { x : 1 } ] } }"};
- const char* const strings[] = {
- // Document:
- "{ a : [ { x : 1 }, { y : 2 }, { x : 1 }, { y : 2 } ] }",
+ Document doc(fromjson(strings[0]));
+ Mod mod(fromjson(strings[1]));
- // Modifier:
- "{ $pull : { a : { y : 2 } } }",
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
- // Document result:
- "{ a : [ { x : 1 }, { x : 1 }] }",
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson(strings[2]), doc);
- // Log result:
- "{ $set : { a : [ { x : 1 }, { x : 1 } ] } }"
- };
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson(strings[3]), logDoc);
+}
- Document doc(fromjson(strings[0]));
- Mod mod(fromjson(strings[1]));
+TEST(DocumentationTests, Example1) {
+ const char* const strings[] = {
+ // Document:
+ "{ flags: ['vme', 'de', 'pse', 'tsc', 'msr', 'pae', 'mce' ] }",
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
+ // Modifier:
+ "{ $pull: { flags: 'msr' } }",
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(strings[2]), doc);
+ // Document result:
+ "{ flags: ['vme', 'de', 'pse', 'tsc', 'pae', 'mce' ] }",
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson(strings[3]), logDoc);
- }
+ // Log result:
+ "{ $set : { flags: ['vme', 'de', 'pse', 'tsc', 'pae', 'mce' ] } }"};
- TEST(DocumentationTests, Example1) {
- const char* const strings[] = {
- // Document:
- "{ flags: ['vme', 'de', 'pse', 'tsc', 'msr', 'pae', 'mce' ] }",
+ Document doc(fromjson(strings[0]));
+ Mod mod(fromjson(strings[1]));
- // Modifier:
- "{ $pull: { flags: 'msr' } }",
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "flags");
+ ASSERT_FALSE(execInfo.noOp);
- // Document result:
- "{ flags: ['vme', 'de', 'pse', 'tsc', 'pae', 'mce' ] }",
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson(strings[2]), doc);
- // Log result:
- "{ $set : { flags: ['vme', 'de', 'pse', 'tsc', 'pae', 'mce' ] } }"
- };
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson(strings[3]), logDoc);
+}
- Document doc(fromjson(strings[0]));
- Mod mod(fromjson(strings[1]));
+TEST(DocumentationTests, Example2a) {
+ const char* const strings[] = {// Document:
+ "{ votes: [ 3, 5, 6, 7, 7, 8 ] }",
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "flags");
- ASSERT_FALSE(execInfo.noOp);
+ // Modifier:
+ "{ $pull: { votes: 7 } }",
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(strings[2]), doc);
+ // Document result:
+ "{ votes: [ 3, 5, 6, 8 ] }",
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson(strings[3]), logDoc);
- }
+ // Log result:
+ "{ $set : { votes: [ 3, 5, 6, 8 ] } }"};
- TEST(DocumentationTests, Example2a) {
- const char* const strings[] = {
- // Document:
- "{ votes: [ 3, 5, 6, 7, 7, 8 ] }",
+ Document doc(fromjson(strings[0]));
+ Mod mod(fromjson(strings[1]));
- // Modifier:
- "{ $pull: { votes: 7 } }",
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "votes");
+ ASSERT_FALSE(execInfo.noOp);
- // Document result:
- "{ votes: [ 3, 5, 6, 8 ] }",
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson(strings[2]), doc);
- // Log result:
- "{ $set : { votes: [ 3, 5, 6, 8 ] } }"
- };
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson(strings[3]), logDoc);
+}
- Document doc(fromjson(strings[0]));
- Mod mod(fromjson(strings[1]));
+TEST(DocumentationTests, Example2b) {
+ const char* const strings[] = {// Document:
+ "{ votes: [ 3, 5, 6, 7, 7, 8 ] }",
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "votes");
- ASSERT_FALSE(execInfo.noOp);
+ // Modifier:
+ "{ $pull: { votes: { $gt: 6 } } }",
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(strings[2]), doc);
+ // Document result:
+ "{ votes: [ 3, 5, 6 ] }",
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson(strings[3]), logDoc);
- }
+ // Log result:
+ "{ $set : { votes: [ 3, 5, 6 ] } }"};
- TEST(DocumentationTests, Example2b) {
- const char* const strings[] = {
- // Document:
- "{ votes: [ 3, 5, 6, 7, 7, 8 ] }",
+ Document doc(fromjson(strings[0]));
+ Mod mod(fromjson(strings[1]));
- // Modifier:
- "{ $pull: { votes: { $gt: 6 } } }",
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "votes");
+ ASSERT_FALSE(execInfo.noOp);
- // Document result:
- "{ votes: [ 3, 5, 6 ] }",
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson(strings[2]), doc);
- // Log result:
- "{ $set : { votes: [ 3, 5, 6 ] } }"
- };
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson(strings[3]), logDoc);
+}
- Document doc(fromjson(strings[0]));
- Mod mod(fromjson(strings[1]));
+TEST(MatchingEdgeCases, NonObjectShortCircuit) {
+ const char* const strings[] = {
+ "{ a: [ { x: 1 }, 2 ] }",
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "votes");
- ASSERT_FALSE(execInfo.noOp);
+ "{ $pull: { a: { x: 1 } } }",
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(strings[2]), doc);
+ "{ a: [ 2 ] }",
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson(strings[3]), logDoc);
- }
-
- TEST(MatchingEdgeCases, NonObjectShortCircuit) {
- const char* const strings[] = {
- "{ a: [ { x: 1 }, 2 ] }",
-
- "{ $pull: { a: { x: 1 } } }",
-
- "{ a: [ 2 ] }",
-
- "{ $set : { a: [ 2 ] } }",
- };
+ "{ $set : { a: [ 2 ] } }",
+ };
- Document doc(fromjson(strings[0]));
- Mod mod(fromjson(strings[1]));
+ Document doc(fromjson(strings[0]));
+ Mod mod(fromjson(strings[1]));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(strings[2]), doc);
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson(strings[2]), doc);
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson(strings[3]), logDoc);
- }
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson(strings[3]), logDoc);
+}
- TEST(MatchingRegressions, SERVER_3988) {
- const char* const strings[] = {
- "{ x: 1, y: [ 2, 3, 4, 'abc', 'xyz' ] }",
+TEST(MatchingRegressions, SERVER_3988) {
+ const char* const strings[] = {
+ "{ x: 1, y: [ 2, 3, 4, 'abc', 'xyz' ] }",
- "{ $pull: { y: /yz/ } }",
+ "{ $pull: { y: /yz/ } }",
- "{ x: 1, y: [ 2, 3, 4, 'abc' ] }",
+ "{ x: 1, y: [ 2, 3, 4, 'abc' ] }",
- "{ $set : { y: [ 2, 3, 4, 'abc' ] } }",
- };
+ "{ $set : { y: [ 2, 3, 4, 'abc' ] } }",
+ };
- Document doc(fromjson(strings[0]));
- Mod mod(fromjson(strings[1]));
+ Document doc(fromjson(strings[0]));
+ Mod mod(fromjson(strings[1]));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "y");
- ASSERT_FALSE(execInfo.noOp);
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "y");
+ ASSERT_FALSE(execInfo.noOp);
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(strings[2]), doc);
+ ASSERT_OK(mod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson(strings[2]), doc);
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson(strings[3]), logDoc);
- }
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod.log(&logBuilder));
+ ASSERT_EQUALS(fromjson(strings[3]), logDoc);
+}
-} // namespace
+} // namespace
diff --git a/src/mongo/db/ops/modifier_push.cpp b/src/mongo/db/ops/modifier_push.cpp
index 70a227fddbd..636a9f4450a 100644
--- a/src/mongo/db/ops/modifier_push.cpp
+++ b/src/mongo/db/ops/modifier_push.cpp
@@ -44,220 +44,196 @@
namespace mongo {
- using std::abs;
- using std::numeric_limits;
+using std::abs;
+using std::numeric_limits;
- namespace mb = mutablebson;
- namespace str = mongoutils::str;
+namespace mb = mutablebson;
+namespace str = mongoutils::str;
- namespace {
+namespace {
- const char kEach[] = "$each";
- const char kSlice[] = "$slice";
- const char kSort[] = "$sort";
- const char kPosition[] = "$position";
+const char kEach[] = "$each";
+const char kSlice[] = "$slice";
+const char kSort[] = "$sort";
+const char kPosition[] = "$position";
- bool isPatternElement(const BSONElement& pattern) {
- if (!pattern.isNumber()) {
- return false;
- }
+bool isPatternElement(const BSONElement& pattern) {
+ if (!pattern.isNumber()) {
+ return false;
+ }
- // Patterns can be only 1 or -1.
- double val = pattern.Number();
- if (val != 1 && val != -1) {
- return false;
- }
+ // Patterns can be only 1 or -1.
+ double val = pattern.Number();
+ if (val != 1 && val != -1) {
+ return false;
+ }
- return true;
- }
+ return true;
+}
- bool inEachMode(const BSONElement& modExpr) {
- if (modExpr.type() != Object) {
- return false;
- }
- BSONObj obj = modExpr.embeddedObject();
- if (obj[kEach].type() == EOO) {
- return false;
- }
- return true;
+bool inEachMode(const BSONElement& modExpr) {
+ if (modExpr.type() != Object) {
+ return false;
+ }
+ BSONObj obj = modExpr.embeddedObject();
+ if (obj[kEach].type() == EOO) {
+ return false;
+ }
+ return true;
+}
+
+Status parseEachMode(ModifierPush::ModifierPushMode pushMode,
+ const BSONElement& modExpr,
+ BSONElement* eachElem,
+ BSONElement* sliceElem,
+ BSONElement* sortElem,
+ BSONElement* positionElem) {
+ Status status = Status::OK();
+
+ // If in $pushAll mode, all we need is the array.
+ if (pushMode == ModifierPush::PUSH_ALL) {
+ if (modExpr.type() != Array) {
+ return Status(ErrorCodes::BadValue, "$pushAll requires an array");
}
+ *eachElem = modExpr;
+ return Status::OK();
+ }
- Status parseEachMode(ModifierPush::ModifierPushMode pushMode,
- const BSONElement& modExpr,
- BSONElement* eachElem,
- BSONElement* sliceElem,
- BSONElement* sortElem,
- BSONElement* positionElem) {
-
- Status status = Status::OK();
-
- // If in $pushAll mode, all we need is the array.
- if (pushMode == ModifierPush::PUSH_ALL) {
- if (modExpr.type() != Array) {
- return Status(ErrorCodes::BadValue, "$pushAll requires an array");
- }
- *eachElem = modExpr;
- return Status::OK();
- }
-
- // The $each clause must be an array.
- *eachElem = modExpr.embeddedObject()[kEach];
- if (eachElem->type() != Array) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "The argument to $each in $push must be"
- " an array but it was of type "
- << typeName(eachElem->type()));
- }
-
- // There must be only one $each clause.
- bool seenEach = false;
- BSONObjIterator itMod(modExpr.embeddedObject());
- while (itMod.more()) {
- BSONElement modElem = itMod.next();
- if (mongoutils::str::equals(modElem.fieldName(), kEach)) {
- if (seenEach) {
- return Status(ErrorCodes::BadValue,
- "Only one $each clause is supported.");
- }
- seenEach = true;
- }
- }
+ // The $each clause must be an array.
+ *eachElem = modExpr.embeddedObject()[kEach];
+ if (eachElem->type() != Array) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "The argument to $each in $push must be"
+ " an array but it was of type "
+ << typeName(eachElem->type()));
+ }
- // Slice, sort, position are optional and may be present in any order.
- bool seenSlice = false;
- bool seenSort = false;
- bool seenPosition = false;
- BSONObjIterator itPush(modExpr.embeddedObject());
- while (itPush.more()) {
- BSONElement elem = itPush.next();
- if (mongoutils::str::equals(elem.fieldName(), kSlice)) {
- if (seenSlice) {
- return Status(ErrorCodes::BadValue,
- "Only one $slice clause is supported.");
- }
- *sliceElem = elem;
- seenSlice = true;
- }
- else if (mongoutils::str::equals(elem.fieldName(), kSort)) {
- if (seenSort) {
- return Status(ErrorCodes::BadValue,
- "Only one $sort clause is supported.");
- }
- *sortElem = elem;
- seenSort = true;
- }
- else if (mongoutils::str::equals(elem.fieldName(), kPosition)) {
- if (seenPosition) {
- return Status(ErrorCodes::BadValue,
- "Only one $position clause is supported.");
- }
- *positionElem = elem;
- seenPosition = true;
- }
- else if (!mongoutils::str::equals(elem.fieldName(), kEach)) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "Unrecognized clause in $push: "
- << elem.fieldNameStringData());
- }
+ // There must be only one $each clause.
+ bool seenEach = false;
+ BSONObjIterator itMod(modExpr.embeddedObject());
+ while (itMod.more()) {
+ BSONElement modElem = itMod.next();
+ if (mongoutils::str::equals(modElem.fieldName(), kEach)) {
+ if (seenEach) {
+ return Status(ErrorCodes::BadValue, "Only one $each clause is supported.");
}
-
- return Status::OK();
+ seenEach = true;
}
+ }
- } // unnamed namespace
-
- struct ModifierPush::PreparedState {
-
- PreparedState(mutablebson::Document* targetDoc)
- : doc(*targetDoc)
- , idxFound(0)
- , elemFound(doc.end())
- , arrayPreModSize(0) {
+ // Slice, sort, position are optional and may be present in any order.
+ bool seenSlice = false;
+ bool seenSort = false;
+ bool seenPosition = false;
+ BSONObjIterator itPush(modExpr.embeddedObject());
+ while (itPush.more()) {
+ BSONElement elem = itPush.next();
+ if (mongoutils::str::equals(elem.fieldName(), kSlice)) {
+ if (seenSlice) {
+ return Status(ErrorCodes::BadValue, "Only one $slice clause is supported.");
+ }
+ *sliceElem = elem;
+ seenSlice = true;
+ } else if (mongoutils::str::equals(elem.fieldName(), kSort)) {
+ if (seenSort) {
+ return Status(ErrorCodes::BadValue, "Only one $sort clause is supported.");
+ }
+ *sortElem = elem;
+ seenSort = true;
+ } else if (mongoutils::str::equals(elem.fieldName(), kPosition)) {
+ if (seenPosition) {
+ return Status(ErrorCodes::BadValue, "Only one $position clause is supported.");
+ }
+ *positionElem = elem;
+ seenPosition = true;
+ } else if (!mongoutils::str::equals(elem.fieldName(), kEach)) {
+ return Status(ErrorCodes::BadValue,
+ str::stream()
+ << "Unrecognized clause in $push: " << elem.fieldNameStringData());
}
+ }
- // Document that is going to be changed.
- mutablebson::Document& doc;
+ return Status::OK();
+}
- // Index in _fieldRef for which an Element exist in the document.
- size_t idxFound;
+} // unnamed namespace
- // Element corresponding to _fieldRef[0.._idxFound].
- mutablebson::Element elemFound;
+struct ModifierPush::PreparedState {
+ PreparedState(mutablebson::Document* targetDoc)
+ : doc(*targetDoc), idxFound(0), elemFound(doc.end()), arrayPreModSize(0) {}
- size_t arrayPreModSize;
+ // Document that is going to be changed.
+ mutablebson::Document& doc;
- };
+ // Index in _fieldRef for which an Element exist in the document.
+ size_t idxFound;
- ModifierPush::ModifierPush(ModifierPush::ModifierPushMode pushMode)
- : _fieldRef()
- , _posDollar(0)
- , _eachMode(false)
- , _eachElem()
- , _slicePresent(false)
- , _slice(0)
- , _sortPresent(false)
- , _startPosition(std::numeric_limits<std::size_t>::max())
- , _sort()
- , _pushMode(pushMode)
- , _val() {
- }
+ // Element corresponding to _fieldRef[0.._idxFound].
+ mutablebson::Element elemFound;
- ModifierPush::~ModifierPush() {
- }
+ size_t arrayPreModSize;
+};
- Status ModifierPush::init(const BSONElement& modExpr, const Options& opts,
- bool* positional) {
+ModifierPush::ModifierPush(ModifierPush::ModifierPushMode pushMode)
+ : _fieldRef(),
+ _posDollar(0),
+ _eachMode(false),
+ _eachElem(),
+ _slicePresent(false),
+ _slice(0),
+ _sortPresent(false),
+ _startPosition(std::numeric_limits<std::size_t>::max()),
+ _sort(),
+ _pushMode(pushMode),
+ _val() {}
- //
- // field name analysis
- //
+ModifierPush::~ModifierPush() {}
- // Break down the field name into its 'dotted' components (aka parts) and check that
- // the field is fit for updates.
- _fieldRef.parse(modExpr.fieldName());
- Status status = fieldchecker::isUpdatable(_fieldRef);
- if (! status.isOK()) {
- return status;
- }
+Status ModifierPush::init(const BSONElement& modExpr, const Options& opts, bool* positional) {
+ //
+ // field name analysis
+ //
- // If a $-positional operator was used, get the index in which it occurred
- // and ensure only one occurrence.
- size_t foundCount;
- bool foundDollar = fieldchecker::isPositional(_fieldRef, &_posDollar, &foundCount);
+ // Break down the field name into its 'dotted' components (aka parts) and check that
+ // the field is fit for updates.
+ _fieldRef.parse(modExpr.fieldName());
+ Status status = fieldchecker::isUpdatable(_fieldRef);
+ if (!status.isOK()) {
+ return status;
+ }
- if (positional)
- *positional = foundDollar;
+ // If a $-positional operator was used, get the index in which it occurred
+ // and ensure only one occurrence.
+ size_t foundCount;
+ bool foundDollar = fieldchecker::isPositional(_fieldRef, &_posDollar, &foundCount);
- if (foundDollar && foundCount > 1) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "Too many positional (i.e. '$') elements found in path '"
- << _fieldRef.dottedField() << "'");
- }
+ if (positional)
+ *positional = foundDollar;
- //
- // value analysis
- //
+ if (foundDollar && foundCount > 1) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "Too many positional (i.e. '$') elements found in path '"
+ << _fieldRef.dottedField() << "'");
+ }
- // Are the target push values safe to store?
- BSONElement sliceElem;
- BSONElement sortElem;
- BSONElement positionElem;
- switch (modExpr.type()) {
+ //
+ // value analysis
+ //
+ // Are the target push values safe to store?
+ BSONElement sliceElem;
+ BSONElement sortElem;
+ BSONElement positionElem;
+ switch (modExpr.type()) {
case Array:
if (_pushMode == PUSH_ALL) {
_eachMode = true;
- Status status = parseEachMode(PUSH_ALL,
- modExpr,
- &_eachElem,
- &sliceElem,
- &sortElem,
- &positionElem);
+ Status status = parseEachMode(
+ PUSH_ALL, modExpr, &_eachElem, &sliceElem, &sortElem, &positionElem);
if (!status.isOK()) {
return status;
}
- }
- else {
+ } else {
_val = modExpr;
}
break;
@@ -266,24 +242,19 @@ namespace mongo {
if (_pushMode == PUSH_ALL) {
return Status(ErrorCodes::BadValue,
str::stream() << "$pushAll requires an array of values "
- "but was given an embedded document.");
+ "but was given an embedded document.");
}
// If any known clause ($each, $slice, or $sort) is present, we'd assume
// we're using the $each variation of push and would parse accodingly.
_eachMode = inEachMode(modExpr);
if (_eachMode) {
- Status status = parseEachMode(PUSH_NORMAL,
- modExpr,
- &_eachElem,
- &sliceElem,
- &sortElem,
- &positionElem);
+ Status status = parseEachMode(
+ PUSH_NORMAL, modExpr, &_eachElem, &sliceElem, &sortElem, &positionElem);
if (!status.isOK()) {
return status;
}
- }
- else {
+ } else {
_val = modExpr;
}
break;
@@ -292,435 +263,400 @@ namespace mongo {
if (_pushMode == PUSH_ALL) {
return Status(ErrorCodes::BadValue,
str::stream() << "$pushAll requires an array of values "
- "but was given an "
- << typeName(modExpr.type()));
+ "but was given an " << typeName(modExpr.type()));
}
_val = modExpr;
break;
+ }
+
+ // Is slice present and correct?
+ if (sliceElem.type() != EOO) {
+ if (_pushMode == PUSH_ALL) {
+ return Status(ErrorCodes::BadValue, "cannot use $slice in $pushAll");
}
- // Is slice present and correct?
- if (sliceElem.type() != EOO) {
- if (_pushMode == PUSH_ALL) {
- return Status(ErrorCodes::BadValue, "cannot use $slice in $pushAll");
- }
+ if (!sliceElem.isNumber()) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "The value for $slice must "
+ "be a numeric value not a "
+ << typeName(sliceElem.type()));
+ }
- if (!sliceElem.isNumber()) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "The value for $slice must "
- "be a numeric value not a "
- << typeName(sliceElem.type()));
- }
+ // TODO: Cleanup and unify numbers wrt getting int32/64 bson values (from doubles)
- // TODO: Cleanup and unify numbers wrt getting int32/64 bson values (from doubles)
+ // If the value of slice is not fraction, even if it's a double, we allow it. The
+ // reason here is that the shell will use doubles by default unless told otherwise.
+ const double doubleVal = sliceElem.numberDouble();
+ if (doubleVal - static_cast<int64_t>(doubleVal) != 0) {
+ return Status(ErrorCodes::BadValue, "The $slice value in $push cannot be fractional");
+ }
- // If the value of slice is not fraction, even if it's a double, we allow it. The
- // reason here is that the shell will use doubles by default unless told otherwise.
- const double doubleVal = sliceElem.numberDouble();
- if (doubleVal - static_cast<int64_t>(doubleVal) != 0) {
- return Status(ErrorCodes::BadValue,
- "The $slice value in $push cannot be fractional");
- }
+ _slice = sliceElem.numberLong();
+ _slicePresent = true;
+ }
- _slice = sliceElem.numberLong();
- _slicePresent = true;
+ // Is position present and correct?
+ if (positionElem.type() != EOO) {
+ if (_pushMode == PUSH_ALL) {
+ return Status(ErrorCodes::BadValue, "cannot use $position in $pushAll");
}
- // Is position present and correct?
- if (positionElem.type() != EOO) {
- if (_pushMode == PUSH_ALL) {
- return Status(ErrorCodes::BadValue, "cannot use $position in $pushAll");
- }
+ if (!positionElem.isNumber()) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "The value for $position must "
+ "be a positive numeric value not a "
+ << typeName(positionElem.type()));
+ }
- if (!positionElem.isNumber()) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "The value for $position must "
- "be a positive numeric value not a "
- << typeName(positionElem.type()));
- }
+ // TODO: Cleanup and unify numbers wrt getting int32/64 bson values (from doubles)
- // TODO: Cleanup and unify numbers wrt getting int32/64 bson values (from doubles)
+ // If the value of position is not fraction, even if it's a double, we allow it. The
+ // reason here is that the shell will use doubles by default unless told otherwise.
+ const double doubleVal = positionElem.numberDouble();
+ if (doubleVal - static_cast<int64_t>(doubleVal) != 0) {
+ return Status(ErrorCodes::BadValue,
+ "The $position value in $push cannot be fractional");
+ }
- // If the value of position is not fraction, even if it's a double, we allow it. The
- // reason here is that the shell will use doubles by default unless told otherwise.
- const double doubleVal = positionElem.numberDouble();
- if (doubleVal - static_cast<int64_t>(doubleVal) != 0) {
- return Status(ErrorCodes::BadValue,
- "The $position value in $push cannot be fractional");
- }
+ if (static_cast<double>(numeric_limits<int64_t>::max()) < doubleVal) {
+ return Status(ErrorCodes::BadValue,
+ "The $position value in $push is too large a number.");
+ }
- if (static_cast<double>(numeric_limits<int64_t>::max()) < doubleVal) {
- return Status(ErrorCodes::BadValue,
- "The $position value in $push is too large a number.");
- }
+ if (static_cast<double>(numeric_limits<int64_t>::min()) > doubleVal) {
+ return Status(ErrorCodes::BadValue,
+ "The $position value in $push is too small a number.");
+ }
- if (static_cast<double>(numeric_limits<int64_t>::min()) > doubleVal) {
- return Status(ErrorCodes::BadValue,
- "The $position value in $push is too small a number.");
- }
+ const int64_t tempVal = positionElem.numberLong();
+ if (tempVal < 0)
+ return Status(ErrorCodes::BadValue, "The $position value in $push must be positive.");
- const int64_t tempVal = positionElem.numberLong();
- if (tempVal < 0)
- return Status(ErrorCodes::BadValue,
- "The $position value in $push must be positive.");
+ _startPosition = size_t(tempVal);
+ }
- _startPosition = size_t(tempVal);
+ // Is sort present and correct?
+ if (sortElem.type() != EOO) {
+ if (_pushMode == PUSH_ALL) {
+ return Status(ErrorCodes::BadValue, "cannot use $sort in $pushAll");
}
- // Is sort present and correct?
- if (sortElem.type() != EOO) {
- if (_pushMode == PUSH_ALL) {
- return Status(ErrorCodes::BadValue,
- "cannot use $sort in $pushAll");
- }
+ if (sortElem.type() != Object && !sortElem.isNumber()) {
+ return Status(ErrorCodes::BadValue,
+ "The $sort is invalid: use 1/-1 to sort the whole element, "
+ "or {field:1/-1} to sort embedded fields");
+ }
- if (sortElem.type() != Object && !sortElem.isNumber()) {
+ if (sortElem.isABSONObj()) {
+ BSONObj sortObj = sortElem.embeddedObject();
+ if (sortObj.isEmpty()) {
return Status(ErrorCodes::BadValue,
- "The $sort is invalid: use 1/-1 to sort the whole element, "
- "or {field:1/-1} to sort embedded fields");
+ "The $sort pattern is empty when it should be a set of fields.");
}
- if (sortElem.isABSONObj()) {
- BSONObj sortObj = sortElem.embeddedObject();
- if (sortObj.isEmpty()) {
+ // Check if the sort pattern is sound.
+ BSONObjIterator sortIter(sortObj);
+ while (sortIter.more()) {
+ BSONElement sortPatternElem = sortIter.next();
+
+ // We require either <field>: 1 or -1 for asc and desc.
+ if (!isPatternElement(sortPatternElem)) {
return Status(ErrorCodes::BadValue,
- "The $sort pattern is empty when it should be a set of fields.");
+ "The $sort element value must be either 1 or -1");
}
- // Check if the sort pattern is sound.
- BSONObjIterator sortIter(sortObj);
- while (sortIter.more()) {
-
- BSONElement sortPatternElem = sortIter.next();
-
- // We require either <field>: 1 or -1 for asc and desc.
- if (!isPatternElement(sortPatternElem)) {
- return Status(ErrorCodes::BadValue,
- "The $sort element value must be either 1 or -1");
- }
+ // All fields parts must be valid.
+ FieldRef sortField(sortPatternElem.fieldName());
+ if (sortField.numParts() == 0) {
+ return Status(ErrorCodes::BadValue, "The $sort field cannot be empty");
+ }
- // All fields parts must be valid.
- FieldRef sortField(sortPatternElem.fieldName());
- if (sortField.numParts() == 0) {
+ for (size_t i = 0; i < sortField.numParts(); i++) {
+ if (sortField.getPart(i).size() == 0) {
return Status(ErrorCodes::BadValue,
- "The $sort field cannot be empty");
- }
-
- for (size_t i = 0; i < sortField.numParts(); i++) {
- if (sortField.getPart(i).size() == 0) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "The $sort field is a dotted field "
- "but has an empty part: "
- << sortField.dottedField());
- }
+ str::stream()
+ << "The $sort field is a dotted field "
+ "but has an empty part: " << sortField.dottedField());
}
}
-
- _sort = PatternElementCmp(sortElem.embeddedObject());
}
- else {
- // Ensure the sortElem number is valid.
- if (!isPatternElement(sortElem)) {
- return Status(ErrorCodes::BadValue,
- "The $sort element value must be either 1 or -1");
- }
- _sort = PatternElementCmp(BSON("" << sortElem.number()));
+ _sort = PatternElementCmp(sortElem.embeddedObject());
+ } else {
+ // Ensure the sortElem number is valid.
+ if (!isPatternElement(sortElem)) {
+ return Status(ErrorCodes::BadValue,
+ "The $sort element value must be either 1 or -1");
}
- _sortPresent = true;
+ _sort = PatternElementCmp(BSON("" << sortElem.number()));
}
- return Status::OK();
+ _sortPresent = true;
}
- Status ModifierPush::prepare(mutablebson::Element root,
- StringData matchedField,
- ExecInfo* execInfo) {
+ return Status::OK();
+}
- _preparedState.reset(new PreparedState(&root.getDocument()));
+Status ModifierPush::prepare(mutablebson::Element root,
+ StringData matchedField,
+ ExecInfo* execInfo) {
+ _preparedState.reset(new PreparedState(&root.getDocument()));
- // If we have a $-positional field, it is time to bind it to an actual field part.
- if (_posDollar) {
- if (matchedField.empty()) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "The positional operator did not find the match "
- "needed from the query. Unexpanded update: "
- << _fieldRef.dottedField());
- }
- _fieldRef.setPart(_posDollar, matchedField);
+ // If we have a $-positional field, it is time to bind it to an actual field part.
+ if (_posDollar) {
+ if (matchedField.empty()) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "The positional operator did not find the match "
+ "needed from the query. Unexpanded update: "
+ << _fieldRef.dottedField());
+ }
+ _fieldRef.setPart(_posDollar, matchedField);
+ }
+
+ // Locate the field name in 'root'. Note that we may not have all the parts in the path
+ // in the doc -- which is fine. Our goal now is merely to reason about whether this mod
+ // apply is a noOp or whether is can be in place. The remainin path, if missing, will
+ // be created during the apply.
+ Status status = pathsupport::findLongestPrefix(
+ _fieldRef, root, &_preparedState->idxFound, &_preparedState->elemFound);
+
+ // FindLongestPrefix may say the path does not exist at all, which is fine here, or
+ // that the path was not viable or otherwise wrong, in which case, the mod cannot
+ // proceed.
+ if (status.code() == ErrorCodes::NonExistentPath) {
+ _preparedState->elemFound = root.getDocument().end();
+
+ } else if (status.isOK()) {
+ const bool destExists = (_preparedState->idxFound == (_fieldRef.numParts() - 1));
+ // If the path exists, we require the target field to be already an
+ // array.
+ if (destExists && _preparedState->elemFound.getType() != Array) {
+ mb::Element idElem = mb::findFirstChildNamed(root, "_id");
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "The field '" << _fieldRef.dottedField() << "'"
+ << " must be an array but is of type "
+ << typeName(_preparedState->elemFound.getType())
+ << " in document {" << idElem.toString() << "}");
+ }
+ } else {
+ return status;
+ }
+
+ // We register interest in the field name. The driver needs this info to sort out if
+ // there is any conflict among mods.
+ execInfo->fieldRef[0] = &_fieldRef;
+
+ return Status::OK();
+}
+
+namespace {
+Status pushFirstElement(mb::Element& arrayElem,
+ const size_t arraySize,
+ const size_t pos,
+ mb::Element& elem) {
+ // Empty array or pushing to the front
+ if (arraySize == 0 || pos == 0) {
+ return arrayElem.pushFront(elem);
+ } else {
+ // Push position is at the end, or beyond
+ if (pos >= arraySize) {
+ return arrayElem.pushBack(elem);
}
- // Locate the field name in 'root'. Note that we may not have all the parts in the path
- // in the doc -- which is fine. Our goal now is merely to reason about whether this mod
- // apply is a noOp or whether is can be in place. The remainin path, if missing, will
- // be created during the apply.
- Status status = pathsupport::findLongestPrefix(_fieldRef,
- root,
- &_preparedState->idxFound,
- &_preparedState->elemFound);
-
- // FindLongestPrefix may say the path does not exist at all, which is fine here, or
- // that the path was not viable or otherwise wrong, in which case, the mod cannot
- // proceed.
- if (status.code() == ErrorCodes::NonExistentPath) {
- _preparedState->elemFound = root.getDocument().end();
+ const size_t appendPos = pos - 1;
+ mutablebson::Element fromElem = getNthChild(arrayElem, appendPos);
+ // This should not be possible since the checks above should
+ // cover us but error just in case
+ if (!fromElem.ok()) {
+ return Status(ErrorCodes::InvalidLength,
+ str::stream() << "The specified position (" << appendPos << "/" << pos
+ << ") is invalid based on the length ( " << arraySize
+ << ") of the array");
}
- else if (status.isOK()) {
- const bool destExists = (_preparedState->idxFound == (_fieldRef.numParts()-1));
- // If the path exists, we require the target field to be already an
- // array.
- if (destExists && _preparedState->elemFound.getType() != Array) {
- mb::Element idElem = mb::findFirstChildNamed(root, "_id");
- return Status(ErrorCodes::BadValue,
- str::stream() << "The field '" << _fieldRef.dottedField() << "'"
- << " must be an array but is of type "
- << typeName(_preparedState->elemFound.getType())
- << " in document {" << idElem.toString() << "}");
- }
+ return fromElem.addSiblingRight(elem);
+ }
+}
+} // unamed namespace
+
+Status ModifierPush::apply() const {
+ Status status = Status::OK();
+
+ //
+ // Applying a $push with an $clause has the following steps
+ // 1. Create the doc array we'll push into, if it is not there
+ // 2. Add the items in the $each array (or the simple $push) to the doc array
+ // 3. Sort the resulting array according to $sort clause, if present
+ // 4. Trim the resulting array according the $slice clasue, if present
+ //
+ // TODO There are _lots_ of optimization opportunities that we'll consider once the
+ // test coverage is adequate.
+ //
+
+ // 1. If the array field is not there, create it as an array and attach it to the
+ // document.
+ if (!_preparedState->elemFound.ok() || _preparedState->idxFound < (_fieldRef.numParts() - 1)) {
+ // Creates the array element
+ mutablebson::Document& doc = _preparedState->doc;
+ StringData lastPart = _fieldRef.getPart(_fieldRef.numParts() - 1);
+ mutablebson::Element baseArray = doc.makeElementArray(lastPart);
+ if (!baseArray.ok()) {
+ return Status(ErrorCodes::InternalError, "can't create new base array");
}
- else {
- return status;
+
+ // Now, we can be in two cases here, as far as attaching the element being set
+ // goes: (a) none of the parts in the element's path exist, or (b) some parts of
+ // the path exist but not all.
+ if (!_preparedState->elemFound.ok()) {
+ _preparedState->elemFound = doc.root();
+ _preparedState->idxFound = 0;
+ } else {
+ _preparedState->idxFound++;
}
- // We register interest in the field name. The driver needs this info to sort out if
- // there is any conflict among mods.
- execInfo->fieldRef[0] = &_fieldRef;
+ // createPathAt() will complete the path and attach 'elemToSet' at the end of it.
+ status = pathsupport::createPathAt(
+ _fieldRef, _preparedState->idxFound, _preparedState->elemFound, baseArray);
+ if (!status.isOK()) {
+ return status;
+ }
- return Status::OK();
+ // Point to the base array just created. The subsequent code expects it to exist
+ // already.
+ _preparedState->elemFound = baseArray;
}
- namespace {
- Status pushFirstElement(mb::Element& arrayElem,
- const size_t arraySize,
- const size_t pos,
- mb::Element& elem) {
+ // This is the count of the array before we change it, or 0 if missing from the doc.
+ _preparedState->arrayPreModSize = countChildren(_preparedState->elemFound);
+ // 2. Add new elements to the array either by going over the $each array or by
+ // appending the (old style $push) element.
+ if (_eachMode || _pushMode == PUSH_ALL) {
+ BSONObjIterator itEach(_eachElem.embeddedObject());
- // Empty array or pushing to the front
- if (arraySize == 0 || pos == 0) {
- return arrayElem.pushFront(elem);
- }
- else {
+ // When adding more than one element we keep track of the previous one
+ // so we can add right siblings to it.
+ mutablebson::Element prevElem = _preparedState->doc.end();
- // Push position is at the end, or beyond
- if (pos >= arraySize) {
- return arrayElem.pushBack(elem);
- }
+ // The first element is special below
+ bool first = true;
- const size_t appendPos = pos - 1;
- mutablebson::Element fromElem = getNthChild(arrayElem, appendPos);
-
- // This should not be possible since the checks above should
- // cover us but error just in case
- if (!fromElem.ok()){
- return Status(ErrorCodes::InvalidLength,
- str::stream() << "The specified position (" << appendPos << "/"
- << pos
- << ") is invalid based on the length ( "
- << arraySize
- << ") of the array");
- }
+ while (itEach.more()) {
+ BSONElement eachItem = itEach.next();
+ mutablebson::Element elem =
+ _preparedState->doc.makeElementWithNewFieldName(StringData(), eachItem);
- return fromElem.addSiblingRight(elem);
- }
- }
- } //unamed namespace
-
- Status ModifierPush::apply() const {
-
- Status status = Status::OK();
-
- //
- // Applying a $push with an $clause has the following steps
- // 1. Create the doc array we'll push into, if it is not there
- // 2. Add the items in the $each array (or the simple $push) to the doc array
- // 3. Sort the resulting array according to $sort clause, if present
- // 4. Trim the resulting array according the $slice clasue, if present
- //
- // TODO There are _lots_ of optimization opportunities that we'll consider once the
- // test coverage is adequate.
- //
-
- // 1. If the array field is not there, create it as an array and attach it to the
- // document.
- if (!_preparedState->elemFound.ok() ||
- _preparedState->idxFound < (_fieldRef.numParts()-1)) {
-
- // Creates the array element
- mutablebson::Document& doc = _preparedState->doc;
- StringData lastPart = _fieldRef.getPart(_fieldRef.numParts()-1);
- mutablebson::Element baseArray = doc.makeElementArray(lastPart);
- if (!baseArray.ok()) {
- return Status(ErrorCodes::InternalError, "can't create new base array");
+ if (first) {
+ status = pushFirstElement(_preparedState->elemFound,
+ _preparedState->arrayPreModSize,
+ _startPosition,
+ elem);
+ } else {
+ status = prevElem.addSiblingRight(elem);
}
- // Now, we can be in two cases here, as far as attaching the element being set
- // goes: (a) none of the parts in the element's path exist, or (b) some parts of
- // the path exist but not all.
- if (!_preparedState->elemFound.ok()) {
- _preparedState->elemFound = doc.root();
- _preparedState->idxFound = 0;
- }
- else {
- _preparedState->idxFound++;
- }
-
- // createPathAt() will complete the path and attach 'elemToSet' at the end of it.
- status = pathsupport::createPathAt(_fieldRef,
- _preparedState->idxFound,
- _preparedState->elemFound,
- baseArray);
if (!status.isOK()) {
return status;
}
- // Point to the base array just created. The subsequent code expects it to exist
- // already.
- _preparedState->elemFound = baseArray;
-
- }
-
- // This is the count of the array before we change it, or 0 if missing from the doc.
- _preparedState->arrayPreModSize = countChildren(_preparedState->elemFound);
-
- // 2. Add new elements to the array either by going over the $each array or by
- // appending the (old style $push) element.
- if (_eachMode || _pushMode == PUSH_ALL) {
- BSONObjIterator itEach(_eachElem.embeddedObject());
-
- // When adding more than one element we keep track of the previous one
- // so we can add right siblings to it.
- mutablebson::Element prevElem = _preparedState->doc.end();
-
- // The first element is special below
- bool first = true;
-
- while (itEach.more()) {
- BSONElement eachItem = itEach.next();
- mutablebson::Element elem =
- _preparedState->doc.makeElementWithNewFieldName(StringData(), eachItem);
-
- if (first) {
- status = pushFirstElement(_preparedState->elemFound,
- _preparedState->arrayPreModSize,
- _startPosition,
- elem); }
- else {
- status = prevElem.addSiblingRight(elem);
- }
-
- if (!status.isOK()) {
- return status;
- }
-
- // For the next iteration the previous element will be the left sibling
- prevElem = elem;
- first = false;
- }
+ // For the next iteration the previous element will be the left sibling
+ prevElem = elem;
+ first = false;
}
- else {
- mutablebson::Element elem =
- _preparedState->doc.makeElementWithNewFieldName(StringData(), _val);
- if (!elem.ok()) {
- return Status(ErrorCodes::InternalError, "can't wrap element being $push-ed");
- }
- return pushFirstElement(_preparedState->elemFound,
- _preparedState->arrayPreModSize,
- _startPosition,
- elem);
- }
-
- // 3. Sort the resulting array, if $sort was requested.
- if (_sortPresent) {
- sortChildren(_preparedState->elemFound, _sort);
+ } else {
+ mutablebson::Element elem =
+ _preparedState->doc.makeElementWithNewFieldName(StringData(), _val);
+ if (!elem.ok()) {
+ return Status(ErrorCodes::InternalError, "can't wrap element being $push-ed");
}
+ return pushFirstElement(
+ _preparedState->elemFound, _preparedState->arrayPreModSize, _startPosition, elem);
+ }
- // 4. Trim the resulting array according to $slice, if present.
- if (_slicePresent) {
+ // 3. Sort the resulting array, if $sort was requested.
+ if (_sortPresent) {
+ sortChildren(_preparedState->elemFound, _sort);
+ }
- // Slice 0 means to remove all
- if (_slice == 0) {
- while(_preparedState->elemFound.ok() &&
- _preparedState->elemFound.rightChild().ok()) {
- _preparedState->elemFound.rightChild().remove();
- }
+ // 4. Trim the resulting array according to $slice, if present.
+ if (_slicePresent) {
+ // Slice 0 means to remove all
+ if (_slice == 0) {
+ while (_preparedState->elemFound.ok() && _preparedState->elemFound.rightChild().ok()) {
+ _preparedState->elemFound.rightChild().remove();
}
+ }
- const int64_t numChildren = mutablebson::countChildren(_preparedState->elemFound);
- int64_t countRemoved = std::max(static_cast<int64_t>(0), numChildren - abs(_slice));
+ const int64_t numChildren = mutablebson::countChildren(_preparedState->elemFound);
+ int64_t countRemoved = std::max(static_cast<int64_t>(0), numChildren - abs(_slice));
- // If _slice is negative, remove from the bottom, otherwise from the top
- const bool removeFromEnd = (_slice > 0);
+ // If _slice is negative, remove from the bottom, otherwise from the top
+ const bool removeFromEnd = (_slice > 0);
- // Either start at right or left depending if we are taking from top or bottom
- mutablebson::Element curr = removeFromEnd ?
- _preparedState->elemFound.rightChild() :
- _preparedState->elemFound.leftChild();
- while (curr.ok() && countRemoved > 0) {
- mutablebson::Element toRemove = curr;
- // Either go right or left depending if we are taking from top or bottom
- curr = removeFromEnd ? curr.leftSibling() : curr.rightSibling();
+ // Either start at right or left depending if we are taking from top or bottom
+ mutablebson::Element curr = removeFromEnd ? _preparedState->elemFound.rightChild()
+ : _preparedState->elemFound.leftChild();
+ while (curr.ok() && countRemoved > 0) {
+ mutablebson::Element toRemove = curr;
+ // Either go right or left depending if we are taking from top or bottom
+ curr = removeFromEnd ? curr.leftSibling() : curr.rightSibling();
- status = toRemove.remove();
- if (!status.isOK()) {
- return status;
- }
- countRemoved--;
+ status = toRemove.remove();
+ if (!status.isOK()) {
+ return status;
}
+ countRemoved--;
}
-
- return status;
}
- Status ModifierPush::log(LogBuilder* logBuilder) const {
+ return status;
+}
- // The start position to use for positional (ordinal) updates to the array
- // (We will increment as we append elements to the oplog entry so can't be const)
- size_t position = _preparedState->arrayPreModSize;
+Status ModifierPush::log(LogBuilder* logBuilder) const {
+ // The start position to use for positional (ordinal) updates to the array
+ // (We will increment as we append elements to the oplog entry so can't be const)
+ size_t position = _preparedState->arrayPreModSize;
- // NOTE: Idempotence Requirement
- // In the case that the document does't have an array or it is empty we need to make sure
- // that the first time the field gets filled with items that it is a full set of the array.
+ // NOTE: Idempotence Requirement
+ // In the case that the document does't have an array or it is empty we need to make sure
+ // that the first time the field gets filled with items that it is a full set of the array.
- // If we sorted, sliced, or added the first items to the array, make a full array copy.
- const bool doFullCopy = _slicePresent || _sortPresent
- || (position == 0) // first element in new/empty array
- || (_startPosition < _preparedState->arrayPreModSize); // add in middle
+ // If we sorted, sliced, or added the first items to the array, make a full array copy.
+ const bool doFullCopy = _slicePresent || _sortPresent ||
+ (position == 0) // first element in new/empty array
+ || (_startPosition < _preparedState->arrayPreModSize); // add in middle
- if (doFullCopy) {
- return logBuilder->addToSetsWithNewFieldName(_fieldRef.dottedField(),
- _preparedState->elemFound);
- }
- else {
- // Set only the positional elements appended
- if (_eachMode || _pushMode == PUSH_ALL) {
- // For each input element log it as a posisional $set
- BSONObjIterator itEach(_eachElem.embeddedObject());
- while (itEach.more()) {
- BSONElement eachItem = itEach.next();
- // value for the logElement ("field.path.name.N": <value>)
- const std::string positionalName =
- mongoutils::str::stream() << _fieldRef.dottedField() << "." << position++;
-
- Status s = logBuilder->addToSetsWithNewFieldName(positionalName, eachItem);
- if (!s.isOK())
- return s;
- }
+ if (doFullCopy) {
+ return logBuilder->addToSetsWithNewFieldName(_fieldRef.dottedField(),
+ _preparedState->elemFound);
+ } else {
+ // Set only the positional elements appended
+ if (_eachMode || _pushMode == PUSH_ALL) {
+ // For each input element log it as a posisional $set
+ BSONObjIterator itEach(_eachElem.embeddedObject());
+ while (itEach.more()) {
+ BSONElement eachItem = itEach.next();
+ // value for the logElement ("field.path.name.N": <value>)
+ const std::string positionalName = mongoutils::str::stream()
+ << _fieldRef.dottedField() << "." << position++;
- return Status::OK();
+ Status s = logBuilder->addToSetsWithNewFieldName(positionalName, eachItem);
+ if (!s.isOK())
+ return s;
}
- else {
- // single value for the logElement ("field.path.name.N": <value>)
- const std::string positionalName =
- mongoutils::str::stream() << _fieldRef.dottedField() << "." << position++;
- return logBuilder->addToSetsWithNewFieldName(positionalName, _val);
- }
+ return Status::OK();
+ } else {
+ // single value for the logElement ("field.path.name.N": <value>)
+ const std::string positionalName = mongoutils::str::stream() << _fieldRef.dottedField()
+ << "." << position++;
+
+ return logBuilder->addToSetsWithNewFieldName(positionalName, _val);
}
}
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_push.h b/src/mongo/db/ops/modifier_push.h
index 708c6431d97..df9c4b65db9 100644
--- a/src/mongo/db/ops/modifier_push.h
+++ b/src/mongo/db/ops/modifier_push.h
@@ -39,97 +39,91 @@
namespace mongo {
- class LogBuilder;
-
- class ModifierPush : public ModifierInterface {
- MONGO_DISALLOW_COPYING(ModifierPush);
-
- public:
-
- enum ModifierPushMode { PUSH_NORMAL, PUSH_ALL };
- explicit ModifierPush(ModifierPushMode mode= PUSH_NORMAL);
-
- //
- // Modifier interface implementation
- //
-
- virtual ~ModifierPush();
-
- /**
- * A 'modExpr' here is a BSONElement {<fieldname>: <each clause>, <slice clause>, <sort
- * clause>} coming from a $push mod such as {$set: {x: $each[{a:1}], $slice:3,
- * $sort{b:1}}}. init() extracts and validates the field name and the clauses. It
- * returns OK if successful or a status describing the error.
- *
- * There are currently a few restrictions concerning the clauses (but all can be
- * lifted):
- * + $slice can be negative only (ie, slicing from the recent end)
- * + $sort requires $slice to be present
- * + $sort can only sort objects (as opposed to basic types), so it only takes
- * object as patterns
- * + Because of the previous, $sort requires that the array being pushed to be made
- * of objects
- */
- virtual Status init(const BSONElement& modExpr, const Options& opts,
- bool* positional = NULL);
-
- /**
- * Locates the array to be pushed into in the 'root', if it exists, and fills in
- * execInfo accordingly. Returns true if $push would succeed in 'root', otherwise
- * return a status describing the error.
- *
- * Note that a $push is never in-place. The cost of checking if it is a no-op makes it
- * so that we don't do such check either. As such, execInfo is always filled with
- * 'false' for those two options.
- */
- virtual Status prepare(mutablebson::Element root,
- StringData matchedField,
- ExecInfo* execInfo);
-
- /**
- * Pushes the array into the prepared position and "sort/slice"s the resulting array
- * according to that call's instructions.
- */
- virtual Status apply() const;
-
- /**
- * $push currently logs the entire resulting array as a $set.
- *
- * TODO Log a positional $set in the array, whenever possible.
- */
- virtual Status log(LogBuilder* logBuilder) const;
-
- private:
-
- // Access to each component of fieldName that's the target of this mod.
- FieldRef _fieldRef;
-
- // 0 or index for $-positional in _fieldRef.
- size_t _posDollar;
-
- // Clauses for the $push that are filled when the $each variation of the command is used.
- bool _eachMode;
- BSONElement _eachElem;
- bool _slicePresent;
- int64_t _slice;
- bool _sortPresent;
- size_t _startPosition;
-
- PatternElementCmp _sort;
-
- // Whether this mod is supposed to be parsed as a $pushAll.
- const ModifierPushMode _pushMode;
-
- // Simple (old style) push value when the $each variation of the command is not
- // used. The _eachMode flag would be off if we're this mode.
- BSONElement _val;
-
- // The instance of the field in the provided doc. This state is valid after a
- // prepare() was issued and until a log() is issued. The document this mod is
- // being prepared against must be live throughout all the calls.
- struct PreparedState;
- std::unique_ptr<PreparedState> _preparedState;
-
- };
-
-} // namespace mongo
+class LogBuilder;
+
+class ModifierPush : public ModifierInterface {
+ MONGO_DISALLOW_COPYING(ModifierPush);
+
+public:
+ enum ModifierPushMode { PUSH_NORMAL, PUSH_ALL };
+ explicit ModifierPush(ModifierPushMode mode = PUSH_NORMAL);
+
+ //
+ // Modifier interface implementation
+ //
+
+ virtual ~ModifierPush();
+
+ /**
+ * A 'modExpr' here is a BSONElement {<fieldname>: <each clause>, <slice clause>, <sort
+ * clause>} coming from a $push mod such as {$set: {x: $each[{a:1}], $slice:3,
+ * $sort{b:1}}}. init() extracts and validates the field name and the clauses. It
+ * returns OK if successful or a status describing the error.
+ *
+ * There are currently a few restrictions concerning the clauses (but all can be
+ * lifted):
+ * + $slice can be negative only (ie, slicing from the recent end)
+ * + $sort requires $slice to be present
+ * + $sort can only sort objects (as opposed to basic types), so it only takes
+ * object as patterns
+ * + Because of the previous, $sort requires that the array being pushed to be made
+ * of objects
+ */
+ virtual Status init(const BSONElement& modExpr, const Options& opts, bool* positional = NULL);
+
+ /**
+ * Locates the array to be pushed into in the 'root', if it exists, and fills in
+ * execInfo accordingly. Returns true if $push would succeed in 'root', otherwise
+ * return a status describing the error.
+ *
+ * Note that a $push is never in-place. The cost of checking if it is a no-op makes it
+ * so that we don't do such check either. As such, execInfo is always filled with
+ * 'false' for those two options.
+ */
+ virtual Status prepare(mutablebson::Element root, StringData matchedField, ExecInfo* execInfo);
+
+ /**
+ * Pushes the array into the prepared position and "sort/slice"s the resulting array
+ * according to that call's instructions.
+ */
+ virtual Status apply() const;
+
+ /**
+ * $push currently logs the entire resulting array as a $set.
+ *
+ * TODO Log a positional $set in the array, whenever possible.
+ */
+ virtual Status log(LogBuilder* logBuilder) const;
+
+private:
+ // Access to each component of fieldName that's the target of this mod.
+ FieldRef _fieldRef;
+
+ // 0 or index for $-positional in _fieldRef.
+ size_t _posDollar;
+
+ // Clauses for the $push that are filled when the $each variation of the command is used.
+ bool _eachMode;
+ BSONElement _eachElem;
+ bool _slicePresent;
+ int64_t _slice;
+ bool _sortPresent;
+ size_t _startPosition;
+
+ PatternElementCmp _sort;
+
+ // Whether this mod is supposed to be parsed as a $pushAll.
+ const ModifierPushMode _pushMode;
+
+ // Simple (old style) push value when the $each variation of the command is not
+ // used. The _eachMode flag would be off if we're this mode.
+ BSONElement _val;
+
+ // The instance of the field in the provided doc. This state is valid after a
+ // prepare() was issued and until a log() is issued. The document this mod is
+ // being prepared against must be live throughout all the calls.
+ struct PreparedState;
+ std::unique_ptr<PreparedState> _preparedState;
+};
+
+} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_push_sorter.h b/src/mongo/db/ops/modifier_push_sorter.h
index 930ab539d57..c942f4e5da3 100644
--- a/src/mongo/db/ops/modifier_push_sorter.h
+++ b/src/mongo/db/ops/modifier_push_sorter.h
@@ -34,44 +34,37 @@
namespace mongo {
- // Extracts the value for 'pattern' for both 'lhs' and 'rhs' and return true if 'lhs' <
- // 'rhs'. We expect that both 'lhs' and 'rhs' be key patterns.
- struct PatternElementCmp {
- BSONObj sortPattern;
- bool useWholeValue;
+// Extracts the value for 'pattern' for both 'lhs' and 'rhs' and return true if 'lhs' <
+// 'rhs'. We expect that both 'lhs' and 'rhs' be key patterns.
+struct PatternElementCmp {
+ BSONObj sortPattern;
+ bool useWholeValue;
- PatternElementCmp()
- : sortPattern(BSONObj())
- , useWholeValue(true) {}
+ PatternElementCmp() : sortPattern(BSONObj()), useWholeValue(true) {}
- PatternElementCmp(const BSONObj& pattern)
- : sortPattern(pattern)
- , useWholeValue(pattern.hasField("")){
- }
+ PatternElementCmp(const BSONObj& pattern)
+ : sortPattern(pattern), useWholeValue(pattern.hasField("")) {}
- bool operator()(const mutablebson::Element& lhs, const mutablebson::Element& rhs) const {
- if (useWholeValue) {
- const int comparedValue = lhs.compareWithElement( rhs, false );
+ bool operator()(const mutablebson::Element& lhs, const mutablebson::Element& rhs) const {
+ if (useWholeValue) {
+ const int comparedValue = lhs.compareWithElement(rhs, false);
- const bool reversed = (sortPattern.firstElement().number() < 0 );
+ const bool reversed = (sortPattern.firstElement().number() < 0);
- return (reversed ? comparedValue > 0 : comparedValue < 0);
- }
- else {
- //TODO: Push on to mutable in the future, and to support non-contiguous Elements.
- BSONObj lhsObj = lhs.getType() == Object ?
- lhs.getValueObject() :
- lhs.getValue().wrap("");
- BSONObj rhsObj = rhs.getType() == Object ?
- rhs.getValueObject() :
- rhs.getValue().wrap("");
+ return (reversed ? comparedValue > 0 : comparedValue < 0);
+ } else {
+ // TODO: Push on to mutable in the future, and to support non-contiguous Elements.
+ BSONObj lhsObj =
+ lhs.getType() == Object ? lhs.getValueObject() : lhs.getValue().wrap("");
+ BSONObj rhsObj =
+ rhs.getType() == Object ? rhs.getValueObject() : rhs.getValue().wrap("");
- BSONObj lhsKey = lhsObj.extractFields(sortPattern, true);
- BSONObj rhsKey = rhsObj.extractFields(sortPattern, true);
+ BSONObj lhsKey = lhsObj.extractFields(sortPattern, true);
+ BSONObj rhsKey = rhsObj.extractFields(sortPattern, true);
- return lhsKey.woCompare(rhsKey, sortPattern) < 0;
- }
+ return lhsKey.woCompare(rhsKey, sortPattern) < 0;
}
- };
+ }
+};
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_push_sorter_test.cpp b/src/mongo/db/ops/modifier_push_sorter_test.cpp
index 4df1c4dfadc..d60eea1e942 100644
--- a/src/mongo/db/ops/modifier_push_sorter_test.cpp
+++ b/src/mongo/db/ops/modifier_push_sorter_test.cpp
@@ -37,143 +37,143 @@
namespace {
- using mongo::BSONObj;
- using mongo::fromjson;
- using mongo::PatternElementCmp;
- using mongo::mutablebson::Document;
- using mongo::mutablebson::Element;
- using mongo::mutablebson::sortChildren;
-
- class ObjectArray : public mongo::unittest::Test {
- public:
- ObjectArray() : _doc(), _size(0) {}
-
- virtual void setUp() {
- Element arr = _doc.makeElementArray("x");
- ASSERT_TRUE(arr.ok());
- ASSERT_OK(_doc.root().pushBack(arr));
- }
-
- void addObj(BSONObj obj) {
- ASSERT_LESS_THAN_OR_EQUALS(_size, 3u);
- _objs[_size] = obj;
- _size++;
-
- ASSERT_OK(_doc.root()["x"].appendObject(mongo::StringData(), obj));
- }
-
- BSONObj getOrigObj(size_t i) {
- return _objs[i];
- }
-
- BSONObj getSortedObj(size_t i) {
- return getArray()[i].getValueObject();
- }
-
- Element getArray() {
- return _doc.root()["x"];
- }
-
- private:
- Document _doc;
- BSONObj _objs[3];
- size_t _size;
- };
-
- TEST_F(ObjectArray, NormalOrder) {
- addObj(fromjson("{b:1, a:1}"));
- addObj(fromjson("{a:3, b:2}"));
- addObj(fromjson("{b:3, a:2}"));
-
- sortChildren(getArray(), PatternElementCmp(fromjson("{'a':1,'b':1}")));
-
- ASSERT_EQUALS(getOrigObj(0), getSortedObj(0));
- ASSERT_EQUALS(getOrigObj(1), getSortedObj(2));
- ASSERT_EQUALS(getOrigObj(2), getSortedObj(1));
+using mongo::BSONObj;
+using mongo::fromjson;
+using mongo::PatternElementCmp;
+using mongo::mutablebson::Document;
+using mongo::mutablebson::Element;
+using mongo::mutablebson::sortChildren;
+
+class ObjectArray : public mongo::unittest::Test {
+public:
+ ObjectArray() : _doc(), _size(0) {}
+
+ virtual void setUp() {
+ Element arr = _doc.makeElementArray("x");
+ ASSERT_TRUE(arr.ok());
+ ASSERT_OK(_doc.root().pushBack(arr));
}
- TEST_F(ObjectArray, MixedOrder) {
- addObj(fromjson("{b:1, a:1}"));
- addObj(fromjson("{a:3, b:2}"));
- addObj(fromjson("{b:3, a:2}"));
+ void addObj(BSONObj obj) {
+ ASSERT_LESS_THAN_OR_EQUALS(_size, 3u);
+ _objs[_size] = obj;
+ _size++;
- sortChildren(getArray(), PatternElementCmp(fromjson("{b:1,a:-1}")));
-
- ASSERT_EQUALS(getOrigObj(0), getSortedObj(0));
- ASSERT_EQUALS(getOrigObj(1), getSortedObj(1));
- ASSERT_EQUALS(getOrigObj(2), getSortedObj(2));
+ ASSERT_OK(_doc.root()["x"].appendObject(mongo::StringData(), obj));
}
- TEST_F(ObjectArray, ExtraFields) {
- addObj(fromjson("{b:1, c:2, a:1}"));
- addObj(fromjson("{c:1, a:3, b:2}"));
- addObj(fromjson("{b:3, a:2}"));
+ BSONObj getOrigObj(size_t i) {
+ return _objs[i];
+ }
- sortChildren(getArray(), PatternElementCmp(fromjson("{a:1,b:1}")));
+ BSONObj getSortedObj(size_t i) {
+ return getArray()[i].getValueObject();
+ }
- ASSERT_EQUALS(getOrigObj(0), getSortedObj(0));
- ASSERT_EQUALS(getOrigObj(1), getSortedObj(2));
- ASSERT_EQUALS(getOrigObj(2), getSortedObj(1));
+ Element getArray() {
+ return _doc.root()["x"];
}
- TEST_F(ObjectArray, MissingFields) {
- addObj(fromjson("{a:2, b:2}"));
- addObj(fromjson("{a:1}"));
- addObj(fromjson("{a:3, b:3, c:3}"));
+private:
+ Document _doc;
+ BSONObj _objs[3];
+ size_t _size;
+};
- sortChildren(getArray(), PatternElementCmp(fromjson("{b:1,c:1}")));
+TEST_F(ObjectArray, NormalOrder) {
+ addObj(fromjson("{b:1, a:1}"));
+ addObj(fromjson("{a:3, b:2}"));
+ addObj(fromjson("{b:3, a:2}"));
- ASSERT_EQUALS(getOrigObj(0), getSortedObj(1));
- ASSERT_EQUALS(getOrigObj(1), getSortedObj(0));
- ASSERT_EQUALS(getOrigObj(2), getSortedObj(2));
- }
+ sortChildren(getArray(), PatternElementCmp(fromjson("{'a':1,'b':1}")));
- TEST_F(ObjectArray, NestedFields) {
- addObj(fromjson("{a:{b:{c:2, d:0}}}"));
- addObj(fromjson("{a:{b:{c:1, d:2}}}"));
- addObj(fromjson("{a:{b:{c:3, d:1}}}"));
+ ASSERT_EQUALS(getOrigObj(0), getSortedObj(0));
+ ASSERT_EQUALS(getOrigObj(1), getSortedObj(2));
+ ASSERT_EQUALS(getOrigObj(2), getSortedObj(1));
+}
- sortChildren(getArray(), PatternElementCmp(fromjson("{'a.b':1}")));
+TEST_F(ObjectArray, MixedOrder) {
+ addObj(fromjson("{b:1, a:1}"));
+ addObj(fromjson("{a:3, b:2}"));
+ addObj(fromjson("{b:3, a:2}"));
- ASSERT_EQUALS(getOrigObj(0), getSortedObj(1));
- ASSERT_EQUALS(getOrigObj(1), getSortedObj(0));
- ASSERT_EQUALS(getOrigObj(2), getSortedObj(2));
- }
+ sortChildren(getArray(), PatternElementCmp(fromjson("{b:1,a:-1}")));
- TEST_F(ObjectArray, SimpleNestedFields) {
- addObj(fromjson("{a:{b: -1}}"));
- addObj(fromjson("{a:{b: -100}}"));
- addObj(fromjson("{a:{b: 34}}"));
+ ASSERT_EQUALS(getOrigObj(0), getSortedObj(0));
+ ASSERT_EQUALS(getOrigObj(1), getSortedObj(1));
+ ASSERT_EQUALS(getOrigObj(2), getSortedObj(2));
+}
- sortChildren(getArray(), PatternElementCmp(fromjson("{'a.b':1}")));
+TEST_F(ObjectArray, ExtraFields) {
+ addObj(fromjson("{b:1, c:2, a:1}"));
+ addObj(fromjson("{c:1, a:3, b:2}"));
+ addObj(fromjson("{b:3, a:2}"));
- ASSERT_EQUALS(getOrigObj(0), getSortedObj(1));
- ASSERT_EQUALS(getOrigObj(1), getSortedObj(0));
- ASSERT_EQUALS(getOrigObj(2), getSortedObj(2));
- }
+ sortChildren(getArray(), PatternElementCmp(fromjson("{a:1,b:1}")));
- TEST_F(ObjectArray, NestedInnerObjectDescending) {
- addObj(fromjson("{a:{b:{c:2, d:0}}}"));
- addObj(fromjson("{a:{b:{c:1, d:2}}}"));
- addObj(fromjson("{a:{b:{c:3, d:1}}}"));
+ ASSERT_EQUALS(getOrigObj(0), getSortedObj(0));
+ ASSERT_EQUALS(getOrigObj(1), getSortedObj(2));
+ ASSERT_EQUALS(getOrigObj(2), getSortedObj(1));
+}
- sortChildren(getArray(), PatternElementCmp(fromjson("{'a.b.d':-1}")));
+TEST_F(ObjectArray, MissingFields) {
+ addObj(fromjson("{a:2, b:2}"));
+ addObj(fromjson("{a:1}"));
+ addObj(fromjson("{a:3, b:3, c:3}"));
- ASSERT_EQUALS(getOrigObj(0), getSortedObj(2));
- ASSERT_EQUALS(getOrigObj(1), getSortedObj(0));
- ASSERT_EQUALS(getOrigObj(2), getSortedObj(1));
- }
+ sortChildren(getArray(), PatternElementCmp(fromjson("{b:1,c:1}")));
- TEST_F(ObjectArray, NestedInnerObjectAscending) {
- addObj(fromjson("{a:{b:{c:2, d:0}}}"));
- addObj(fromjson("{a:{b:{c:1, d:2}}}"));
- addObj(fromjson("{a:{b:{c:3, d:1}}}"));
+ ASSERT_EQUALS(getOrigObj(0), getSortedObj(1));
+ ASSERT_EQUALS(getOrigObj(1), getSortedObj(0));
+ ASSERT_EQUALS(getOrigObj(2), getSortedObj(2));
+}
- sortChildren(getArray(), PatternElementCmp(fromjson("{'a.b.d':1}")));
+TEST_F(ObjectArray, NestedFields) {
+ addObj(fromjson("{a:{b:{c:2, d:0}}}"));
+ addObj(fromjson("{a:{b:{c:1, d:2}}}"));
+ addObj(fromjson("{a:{b:{c:3, d:1}}}"));
- ASSERT_EQUALS(getOrigObj(0), getSortedObj(0));
- ASSERT_EQUALS(getOrigObj(2), getSortedObj(1));
- ASSERT_EQUALS(getOrigObj(1), getSortedObj(2));
- }
+ sortChildren(getArray(), PatternElementCmp(fromjson("{'a.b':1}")));
+
+ ASSERT_EQUALS(getOrigObj(0), getSortedObj(1));
+ ASSERT_EQUALS(getOrigObj(1), getSortedObj(0));
+ ASSERT_EQUALS(getOrigObj(2), getSortedObj(2));
+}
+
+TEST_F(ObjectArray, SimpleNestedFields) {
+ addObj(fromjson("{a:{b: -1}}"));
+ addObj(fromjson("{a:{b: -100}}"));
+ addObj(fromjson("{a:{b: 34}}"));
+
+ sortChildren(getArray(), PatternElementCmp(fromjson("{'a.b':1}")));
+
+ ASSERT_EQUALS(getOrigObj(0), getSortedObj(1));
+ ASSERT_EQUALS(getOrigObj(1), getSortedObj(0));
+ ASSERT_EQUALS(getOrigObj(2), getSortedObj(2));
+}
+
+TEST_F(ObjectArray, NestedInnerObjectDescending) {
+ addObj(fromjson("{a:{b:{c:2, d:0}}}"));
+ addObj(fromjson("{a:{b:{c:1, d:2}}}"));
+ addObj(fromjson("{a:{b:{c:3, d:1}}}"));
+
+ sortChildren(getArray(), PatternElementCmp(fromjson("{'a.b.d':-1}")));
+
+ ASSERT_EQUALS(getOrigObj(0), getSortedObj(2));
+ ASSERT_EQUALS(getOrigObj(1), getSortedObj(0));
+ ASSERT_EQUALS(getOrigObj(2), getSortedObj(1));
+}
+
+TEST_F(ObjectArray, NestedInnerObjectAscending) {
+ addObj(fromjson("{a:{b:{c:2, d:0}}}"));
+ addObj(fromjson("{a:{b:{c:1, d:2}}}"));
+ addObj(fromjson("{a:{b:{c:3, d:1}}}"));
+
+ sortChildren(getArray(), PatternElementCmp(fromjson("{'a.b.d':1}")));
+
+ ASSERT_EQUALS(getOrigObj(0), getSortedObj(0));
+ ASSERT_EQUALS(getOrigObj(2), getSortedObj(1));
+ ASSERT_EQUALS(getOrigObj(1), getSortedObj(2));
+}
-} // unnamed namespace
+} // unnamed namespace
diff --git a/src/mongo/db/ops/modifier_push_test.cpp b/src/mongo/db/ops/modifier_push_test.cpp
index 884566d48ac..275e1a4945e 100644
--- a/src/mongo/db/ops/modifier_push_test.cpp
+++ b/src/mongo/db/ops/modifier_push_test.cpp
@@ -47,1409 +47,1394 @@
namespace {
- using mongo::BSONObj;
- using mongo::BSONObjBuilder;
- using mongo::BSONArrayBuilder;
- using mongo::fromjson;
- using mongo::LogBuilder;
- using mongo::ModifierInterface;
- using mongo::ModifierPush;
- using mongo::NumberInt;
- using mongo::Ordering;
- using mongo::Status;
- using mongo::StringData;
- using mongo::mutablebson::ConstElement;
- using mongo::mutablebson::countChildren;
- using mongo::mutablebson::Document;
- using mongo::mutablebson::Element;
- using std::sort;
- using std::vector;
-
- void combineVec(const vector<int>& origVec,
- const vector<int>& modVec,
- int32_t slice,
- vector<int>* combined) {
-
- using namespace std;
- combined->clear();
-
- // Slice 0 means the result is empty
- if (slice == 0)
- return;
-
- // Combine both vectors
- *combined = origVec;
- combined->insert(combined->end(), modVec.begin(), modVec.end());
-
- // Remove sliced items
- bool removeFromFront = (slice < 0);
-
- // if abs(slice) is larger than the size, nothing to do.
- if (abs(slice) >= int32_t(combined->size()))
- return;
-
- if (removeFromFront) {
- // Slice is negative.
- int32_t removeCount = combined->size() + slice;
- combined->erase(combined->begin(), combined->begin() + removeCount);
- }
- else {
- combined->resize(std::min(combined->size(), size_t(slice)));
- }
- }
-
- /**
- * Comparator between two BSONObjects that takes in consideration only the keys and
- * direction described in the sort pattern.
- */
- struct ProjectKeyCmp {
- BSONObj sortPattern;
- bool useWholeValue;
-
- ProjectKeyCmp(BSONObj pattern) : sortPattern(pattern) {
- useWholeValue = pattern.hasField("");
- }
-
- int operator()(const BSONObj& left, const BSONObj& right) const {
- int ret = 0;
- if (useWholeValue) {
- ret = left.woCompare( right, Ordering::make(sortPattern), false );
- } else {
- BSONObj lhsKey = left.extractFields(sortPattern, true);
- BSONObj rhsKey = right.extractFields(sortPattern, true);
- ret = lhsKey.woCompare(rhsKey, sortPattern);
- }
- return ret < 0;
- }
- };
-
- void combineAndSortVec(const vector<BSONObj>& origVec,
- const vector<BSONObj>& modVec,
- int32_t slice,
- BSONObj sortOrder,
- vector<BSONObj>* combined) {
-
- combined->clear();
+using mongo::BSONObj;
+using mongo::BSONObjBuilder;
+using mongo::BSONArrayBuilder;
+using mongo::fromjson;
+using mongo::LogBuilder;
+using mongo::ModifierInterface;
+using mongo::ModifierPush;
+using mongo::NumberInt;
+using mongo::Ordering;
+using mongo::Status;
+using mongo::StringData;
+using mongo::mutablebson::ConstElement;
+using mongo::mutablebson::countChildren;
+using mongo::mutablebson::Document;
+using mongo::mutablebson::Element;
+using std::sort;
+using std::vector;
+
+void combineVec(const vector<int>& origVec,
+ const vector<int>& modVec,
+ int32_t slice,
+ vector<int>* combined) {
+ using namespace std;
+ combined->clear();
+
+ // Slice 0 means the result is empty
+ if (slice == 0)
+ return;
+
+ // Combine both vectors
+ *combined = origVec;
+ combined->insert(combined->end(), modVec.begin(), modVec.end());
+
+ // Remove sliced items
+ bool removeFromFront = (slice < 0);
+
+ // if abs(slice) is larger than the size, nothing to do.
+ if (abs(slice) >= int32_t(combined->size()))
+ return;
+
+ if (removeFromFront) {
+ // Slice is negative.
+ int32_t removeCount = combined->size() + slice;
+ combined->erase(combined->begin(), combined->begin() + removeCount);
+ } else {
+ combined->resize(std::min(combined->size(), size_t(slice)));
+ }
+}
- // Slice 0 means the result is empty
- if (slice == 0)
- return;
-
- *combined = origVec;
- combined->insert(combined->end(), modVec.begin(), modVec.end());
-
- sort(combined->begin(), combined->end(), ProjectKeyCmp(sortOrder));
-
- // Remove sliced items
- bool removeFromFront = (slice < 0);
-
- // if abs(slice) is larger than the size, nothing to do.
- if (abs(slice) >= int32_t(combined->size()))
- return;
-
- if (removeFromFront) {
- // Slice is negative.
- int32_t removeCount = combined->size() + slice;
- combined->erase(combined->begin(), combined->begin() + removeCount);
- }
- else {
- combined->resize(std::min(combined->size(), size_t(slice)));
+/**
+ * Comparator between two BSONObjects that takes in consideration only the keys and
+ * direction described in the sort pattern.
+ */
+struct ProjectKeyCmp {
+ BSONObj sortPattern;
+ bool useWholeValue;
+
+ ProjectKeyCmp(BSONObj pattern) : sortPattern(pattern) {
+ useWholeValue = pattern.hasField("");
+ }
+
+ int operator()(const BSONObj& left, const BSONObj& right) const {
+ int ret = 0;
+ if (useWholeValue) {
+ ret = left.woCompare(right, Ordering::make(sortPattern), false);
+ } else {
+ BSONObj lhsKey = left.extractFields(sortPattern, true);
+ BSONObj rhsKey = right.extractFields(sortPattern, true);
+ ret = lhsKey.woCompare(rhsKey, sortPattern);
}
- }
-
- //
- // Init testing (module field checking, which is done in 'fieldchecker'
- //
-
- TEST(Init, SimplePush) {
- BSONObj modObj = fromjson("{$push: {x: 0}}");
- ModifierPush mod;
- ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
+ return ret < 0;
+ }
+};
+
+void combineAndSortVec(const vector<BSONObj>& origVec,
+ const vector<BSONObj>& modVec,
+ int32_t slice,
+ BSONObj sortOrder,
+ vector<BSONObj>* combined) {
+ combined->clear();
+
+ // Slice 0 means the result is empty
+ if (slice == 0)
+ return;
+
+ *combined = origVec;
+ combined->insert(combined->end(), modVec.begin(), modVec.end());
+
+ sort(combined->begin(), combined->end(), ProjectKeyCmp(sortOrder));
+
+ // Remove sliced items
+ bool removeFromFront = (slice < 0);
+
+ // if abs(slice) is larger than the size, nothing to do.
+ if (abs(slice) >= int32_t(combined->size()))
+ return;
+
+ if (removeFromFront) {
+ // Slice is negative.
+ int32_t removeCount = combined->size() + slice;
+ combined->erase(combined->begin(), combined->begin() + removeCount);
+ } else {
+ combined->resize(std::min(combined->size(), size_t(slice)));
+ }
+}
+
+//
+// Init testing (module field checking, which is done in 'fieldchecker'
+//
+
+TEST(Init, SimplePush) {
+ BSONObj modObj = fromjson("{$push: {x: 0}}");
+ ModifierPush mod;
+ ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
+
+//
+// If present, is the $each clause valid?
+//
+
+TEST(Init, PushEachNormal) {
+ BSONObj modObj = fromjson("{$push: {x: {$each: [1, 2]}}}");
+ ModifierPush mod;
+ ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
+
+TEST(Init, PushEachMixed) {
+ BSONObj modObj = fromjson("{$push: {x: {$each: [1, {a: 2}]}}}");
+ ModifierPush mod;
+ ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
+
+TEST(Init, PushEachObject) {
+ // $each must be an array
+ BSONObj modObj = fromjson("{$push: {x: {$each: {'0': 1}}}}");
+ ModifierPush mod;
+ ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
ModifierInterface::Options::normal()));
- }
+}
- //
- // If present, is the $each clause valid?
- //
-
- TEST(Init, PushEachNormal) {
- BSONObj modObj = fromjson("{$push: {x: {$each: [1, 2]}}}");
- ModifierPush mod;
- ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
+TEST(Init, PushEachSimpleType) {
+ // $each must be an array.
+ BSONObj modObj = fromjson("{$push: {x: {$each: 1}}}");
+ ModifierPush mod;
+ ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
ModifierInterface::Options::normal()));
- }
-
- TEST(Init, PushEachMixed) {
- BSONObj modObj = fromjson("{$push: {x: {$each: [1, {a: 2}]}}}");
- ModifierPush mod;
- ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
+}
+
+TEST(Init, PushEachEmpty) {
+ BSONObj modObj = fromjson("{$push: {x: {$each: []}}}");
+ ModifierPush mod;
+ ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
+
+TEST(Init, PushEachInvalidType) {
+ // $each must be an array.
+ BSONObj modObj = fromjson("{$push: {x: {$each: {b: 1}}}}");
+ ModifierPush mod;
+ ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
ModifierInterface::Options::normal()));
- }
-
- TEST(Init, PushEachObject) {
- // $each must be an array
- BSONObj modObj = fromjson("{$push: {x: {$each: {'0': 1}}}}");
- ModifierPush mod;
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
-
- TEST(Init, PushEachSimpleType) {
- // $each must be an array.
- BSONObj modObj = fromjson("{$push: {x: {$each: 1}}}");
- ModifierPush mod;
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
-
- TEST(Init, PushEachEmpty) {
- BSONObj modObj = fromjson("{$push: {x: {$each: []}}}");
- ModifierPush mod;
- ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
+}
+
+//
+// If present, is the $slice clause valid?
+//
+
+TEST(Init, PushEachWithSliceBottom) {
+ BSONObj modObj = fromjson("{$push: {x: {$each: [1, 2], $slice: -3}}}");
+ ModifierPush mod;
+ ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
+
+TEST(Init, PushEachWithSliceTop) {
+ BSONObj modObj = fromjson("{$push: {x: {$each: [1, 2], $slice: 3}}}");
+ ModifierPush mod;
+ ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
+
+TEST(Init, PushEachWithInvalidSliceObject) {
+ BSONObj modObj = fromjson("{$push: {x: {$each: [1, 2], $slice: {a: 1}}}}");
+ ModifierPush mod;
+ ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
ModifierInterface::Options::normal()));
- }
-
- TEST(Init, PushEachInvalidType) {
- // $each must be an array.
- BSONObj modObj = fromjson("{$push: {x: {$each: {b: 1}}}}");
- ModifierPush mod;
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
+}
- //
- // If present, is the $slice clause valid?
- //
-
- TEST(Init, PushEachWithSliceBottom) {
- BSONObj modObj = fromjson("{$push: {x: {$each: [1, 2], $slice: -3}}}");
- ModifierPush mod;
- ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
+TEST(Init, PushEachWithInvalidSliceDouble) {
+ BSONObj modObj = fromjson("{$push: {x: {$each: [1, 2], $slice: -2.1}}}");
+ ModifierPush mod;
+ ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
ModifierInterface::Options::normal()));
- }
-
- TEST(Init, PushEachWithSliceTop) {
- BSONObj modObj = fromjson("{$push: {x: {$each: [1, 2], $slice: 3}}}");
- ModifierPush mod;
- ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
-
- TEST(Init, PushEachWithInvalidSliceObject) {
- BSONObj modObj = fromjson("{$push: {x: {$each: [1, 2], $slice: {a: 1}}}}");
- ModifierPush mod;
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
-
- TEST(Init, PushEachWithInvalidSliceDouble) {
- BSONObj modObj = fromjson("{$push: {x: {$each: [1, 2], $slice: -2.1}}}");
- ModifierPush mod;
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
-
- TEST(Init, PushEachWithValidSliceDouble) {
- BSONObj modObj = fromjson("{$push: {x: {$each: [1, 2], $slice: -2.0}}}");
- ModifierPush mod;
- ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
+}
+
+TEST(Init, PushEachWithValidSliceDouble) {
+ BSONObj modObj = fromjson("{$push: {x: {$each: [1, 2], $slice: -2.0}}}");
+ ModifierPush mod;
+ ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
+
+TEST(Init, PushEachWithUnsupportedFullSlice) {
+ BSONObj modObj = fromjson("{$push: {x: {$each: [1, 2], $slice: [1,2]}}}");
+ ModifierPush mod;
+ ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
ModifierInterface::Options::normal()));
- }
+}
- TEST(Init, PushEachWithUnsupportedFullSlice) {
- BSONObj modObj = fromjson("{$push: {x: {$each: [1, 2], $slice: [1,2]}}}");
- ModifierPush mod;
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
-
- TEST(Init, PushEachWithWrongTypeSlice) {
- BSONObj modObj = fromjson("{$push: {x: {$each: [1, 2], $slice: '-1'}}}");
- ModifierPush mod;
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
-
- //
- // If present, is the sort $sort clause valid?
- //
-
- TEST(Init, PushEachWithObjectSort) {
- const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort: {a:1}}}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
+TEST(Init, PushEachWithWrongTypeSlice) {
+ BSONObj modObj = fromjson("{$push: {x: {$each: [1, 2], $slice: '-1'}}}");
+ ModifierPush mod;
+ ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
ModifierInterface::Options::normal()));
- }
-
- TEST(Init, PushEachWithNumbericSort) {
- const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort:1 }}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
-
- TEST(Init, PushEachWithInvalidSortType) {
- const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort: [{a:1}]}}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
-
- TEST(Init, PushEachDuplicateSortPattern) {
- const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort: [{a:1,a:1}]}}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
-
- TEST(Init, PushEachWithInvalidSortValue) {
- const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort: {a:100}}}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
+}
+
+//
+// If present, is the sort $sort clause valid?
+//
+
+TEST(Init, PushEachWithObjectSort) {
+ const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort: {a:1}}}}";
+ BSONObj modObj = fromjson(c);
+ ModifierPush mod;
+ ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
+
+TEST(Init, PushEachWithNumbericSort) {
+ const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort:1 }}}";
+ BSONObj modObj = fromjson(c);
+ ModifierPush mod;
+ ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
+
+TEST(Init, PushEachWithInvalidSortType) {
+ const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort: [{a:1}]}}}";
+ BSONObj modObj = fromjson(c);
+ ModifierPush mod;
+ ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
- TEST(Init, PushEachWithEmptySortField) {
- const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort: {'':1}}}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
+TEST(Init, PushEachDuplicateSortPattern) {
+ const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort: [{a:1,a:1}]}}}";
+ BSONObj modObj = fromjson(c);
+ ModifierPush mod;
+ ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
- TEST(Init, PushEachWithEmptyDottedSortField) {
- const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort: {'.':1}}}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
+TEST(Init, PushEachWithInvalidSortValue) {
+ const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort: {a:100}}}}";
+ BSONObj modObj = fromjson(c);
+ ModifierPush mod;
+ ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
- TEST(Init, PushEachWithMissingSortFieldSuffix) {
- const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort: {'a.':1}}}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
+TEST(Init, PushEachWithEmptySortField) {
+ const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort: {'':1}}}}";
+ BSONObj modObj = fromjson(c);
+ ModifierPush mod;
+ ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
- TEST(Init, PushEachWithMissingSortFieldPreffix) {
- const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort: {'.b':1}}}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
+TEST(Init, PushEachWithEmptyDottedSortField) {
+ const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort: {'.':1}}}}";
+ BSONObj modObj = fromjson(c);
+ ModifierPush mod;
+ ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
- TEST(Init, PushEachWithMissingSortFieldMiddle) {
- const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort: {'a..b':1}}}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
+TEST(Init, PushEachWithMissingSortFieldSuffix) {
+ const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort: {'a.':1}}}}";
+ BSONObj modObj = fromjson(c);
+ ModifierPush mod;
+ ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
- TEST(Init, PushEachWithEmptySort) {
- const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort:{} }}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
+TEST(Init, PushEachWithMissingSortFieldPreffix) {
+ const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort: {'.b':1}}}}";
+ BSONObj modObj = fromjson(c);
+ ModifierPush mod;
+ ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
- //
- // If in $pushAll semantics, do we check the array and that nothing else is there?
- //
+TEST(Init, PushEachWithMissingSortFieldMiddle) {
+ const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort: {'a..b':1}}}}";
+ BSONObj modObj = fromjson(c);
+ ModifierPush mod;
+ ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
- TEST(Init, PushAllSimple) {
- BSONObj modObj = fromjson("{$pushAll: {x: [0]}}");
- ModifierPush mod(ModifierPush::PUSH_ALL);
- ASSERT_OK(mod.init(modObj["$pushAll"].embeddedObject().firstElement(),
+TEST(Init, PushEachWithEmptySort) {
+ const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort:{} }}}";
+ BSONObj modObj = fromjson(c);
+ ModifierPush mod;
+ ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
ModifierInterface::Options::normal()));
- }
+}
+
+//
+// If in $pushAll semantics, do we check the array and that nothing else is there?
+//
+
+TEST(Init, PushAllSimple) {
+ BSONObj modObj = fromjson("{$pushAll: {x: [0]}}");
+ ModifierPush mod(ModifierPush::PUSH_ALL);
+ ASSERT_OK(mod.init(modObj["$pushAll"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
+
+TEST(Init, PushAllMultiple) {
+ BSONObj modObj = fromjson("{$pushAll: {x: [1,2,3]}}");
+ ModifierPush mod(ModifierPush::PUSH_ALL);
+ ASSERT_OK(mod.init(modObj["$pushAll"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
+
+TEST(Init, PushAllObject) {
+ BSONObj modObj = fromjson("{$pushAll: {x: [{a:1},{a:2}]}}");
+ ModifierPush mod(ModifierPush::PUSH_ALL);
+ ASSERT_OK(mod.init(modObj["$pushAll"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
+
+TEST(Init, PushAllMixed) {
+ BSONObj modObj = fromjson("{$pushAll: {x: [1,{a:2}]}}");
+ ModifierPush mod(ModifierPush::PUSH_ALL);
+ ASSERT_OK(mod.init(modObj["$pushAll"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
+
+TEST(Init, PushAllWrongType) {
+ BSONObj modObj = fromjson("{$pushAll: {x: 1}}");
+ ModifierPush mod(ModifierPush::PUSH_ALL);
+ ASSERT_NOT_OK(mod.init(modObj["$pushAll"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
- TEST(Init, PushAllMultiple) {
- BSONObj modObj = fromjson("{$pushAll: {x: [1,2,3]}}");
- ModifierPush mod(ModifierPush::PUSH_ALL);
- ASSERT_OK(mod.init(modObj["$pushAll"].embeddedObject().firstElement(),
+TEST(Init, PushAllNotArray) {
+ BSONObj modObj = fromjson("{$pushAll: {x: {a:1}}}");
+ ModifierPush mod(ModifierPush::PUSH_ALL);
+ ASSERT_NOT_OK(mod.init(modObj["$pushAll"].embeddedObject().firstElement(),
ModifierInterface::Options::normal()));
- }
+}
+
+//
+// Are all clauses present? Is anything extroneous? Is anything duplicated?
+//
+
+TEST(Init, PushEachWithSortMissingSlice) {
+ const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $sort:{a:1}}}}";
+ BSONObj modObj = fromjson(c);
+ ModifierPush mod;
+ ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
+
+TEST(Init, PushEachInvalidClause) {
+ const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $xxx: -1, $sort:{a:1}}}}";
+ BSONObj modObj = fromjson(c);
+ ModifierPush mod;
+ ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
- TEST(Init, PushAllObject) {
- BSONObj modObj = fromjson("{$pushAll: {x: [{a:1},{a:2}]}}");
- ModifierPush mod(ModifierPush::PUSH_ALL);
- ASSERT_OK(mod.init(modObj["$pushAll"].embeddedObject().firstElement(),
+TEST(Init, PushEachExtraField) {
+ const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort: {a:1}, b: 1}}}";
+ BSONObj modObj = fromjson(c);
+ ModifierPush mod;
+ ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
ModifierInterface::Options::normal()));
- }
+}
- TEST(Init, PushAllMixed) {
- BSONObj modObj = fromjson("{$pushAll: {x: [1,{a:2}]}}");
- ModifierPush mod(ModifierPush::PUSH_ALL);
- ASSERT_OK(mod.init(modObj["$pushAll"].embeddedObject().firstElement(),
+TEST(Init, PushEachDuplicateSortClause) {
+ const char* c = "{$push: {x:{$each:[{a:1},{a:2}], $slice:-2.0, $sort:{a:1}, $sort:{a:1}}}}";
+ BSONObj modObj = fromjson(c);
+ ModifierPush mod;
+ ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
ModifierInterface::Options::normal()));
- }
+}
- TEST(Init, PushAllWrongType) {
- BSONObj modObj = fromjson("{$pushAll: {x: 1}}");
- ModifierPush mod(ModifierPush::PUSH_ALL);
- ASSERT_NOT_OK(mod.init(modObj["$pushAll"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
+TEST(Init, PushEachDuplicateSliceClause) {
+ const char* c = "{$push: {x: {$each:[{a:1},{a:2}], $slice:-2.0, $slice:-2, $sort:{a:1}}}}";
+ BSONObj modObj = fromjson(c);
+ ModifierPush mod;
+ ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
- TEST(Init, PushAllNotArray) {
- BSONObj modObj = fromjson("{$pushAll: {x: {a:1}}}");
- ModifierPush mod(ModifierPush::PUSH_ALL);
- ASSERT_NOT_OK(mod.init(modObj["$pushAll"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
+TEST(Init, PushEachDuplicateEachClause) {
+ const char* c = "{$push: {x: {$each:[{a:1}], $each:[{a:2}], $slice:-3, $sort:{a:1}}}}";
+ BSONObj modObj = fromjson(c);
+ ModifierPush mod;
+ ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
- //
- // Are all clauses present? Is anything extroneous? Is anything duplicated?
- //
+TEST(Init, PushEachWithSliceFirst) {
+ const char* c = "{$push: {x: {$slice: -2.0, $each: [{a:1},{a:2}], $sort: {a:1}}}}";
+ BSONObj modObj = fromjson(c);
+ ModifierPush mod;
+ ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
- TEST(Init, PushEachWithSortMissingSlice) {
- const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $sort:{a:1}}}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
+TEST(Init, PushEachWithSortFirst) {
+ const char* c = "{$push: {x: {$sort: {a:1}, $slice: -2.0, $each: [{a:1},{a:2}]}}}";
+ BSONObj modObj = fromjson(c);
+ ModifierPush mod;
+ ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
- TEST(Init, PushEachInvalidClause) {
- const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $xxx: -1, $sort:{a:1}}}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
+//
+// Simple mod
+//
- TEST(Init, PushEachExtraField) {
- const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort: {a:1}, b: 1}}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
+/** Helper to build and manipulate a $push or a $pushAll mod. */
+class Mod {
+public:
+ Mod() : _mod() {}
- TEST(Init, PushEachDuplicateSortClause) {
- const char* c = "{$push: {x:{$each:[{a:1},{a:2}], $slice:-2.0, $sort:{a:1}, $sort:{a:1}}}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
+ explicit Mod(BSONObj modObj)
+ : _mod(mongoutils::str::equals(modObj.firstElement().fieldName(), "$pushAll")
+ ? ModifierPush::PUSH_ALL
+ : ModifierPush::PUSH_NORMAL) {
+ _modObj = modObj;
+ StringData modName = modObj.firstElement().fieldName();
+ ASSERT_OK(_mod.init(_modObj[modName].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
}
- TEST(Init, PushEachDuplicateSliceClause) {
- const char* c = "{$push: {x: {$each:[{a:1},{a:2}], $slice:-2.0, $slice:-2, $sort:{a:1}}}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
+ Status prepare(Element root, StringData matchedField, ModifierInterface::ExecInfo* execInfo) {
+ return _mod.prepare(root, matchedField, execInfo);
}
- TEST(Init, PushEachDuplicateEachClause) {
- const char* c = "{$push: {x: {$each:[{a:1}], $each:[{a:2}], $slice:-3, $sort:{a:1}}}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
+ Status apply() const {
+ return _mod.apply();
}
- TEST(Init, PushEachWithSliceFirst) {
- const char* c = "{$push: {x: {$slice: -2.0, $each: [{a:1},{a:2}], $sort: {a:1}}}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
+ Status log(LogBuilder* logBuilder) const {
+ return _mod.log(logBuilder);
}
- TEST(Init, PushEachWithSortFirst) {
- const char* c = "{$push: {x: {$sort: {a:1}, $slice: -2.0, $each: [{a:1},{a:2}]}}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
+ ModifierPush& mod() {
+ return _mod;
}
- //
- // Simple mod
- //
-
- /** Helper to build and manipulate a $push or a $pushAll mod. */
- class Mod {
- public:
- Mod() : _mod() {}
-
- explicit Mod(BSONObj modObj)
- : _mod(mongoutils::str::equals(modObj.firstElement().fieldName(), "$pushAll") ?
- ModifierPush::PUSH_ALL : ModifierPush::PUSH_NORMAL) {
- _modObj = modObj;
- StringData modName = modObj.firstElement().fieldName();
- ASSERT_OK(_mod.init(_modObj[modName].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
-
- Status prepare(Element root,
- StringData matchedField,
- ModifierInterface::ExecInfo* execInfo) {
- return _mod.prepare(root, matchedField, execInfo);
- }
-
- Status apply() const {
- return _mod.apply();
- }
+private:
+ ModifierPush _mod;
+ BSONObj _modObj;
+};
- Status log(LogBuilder* logBuilder) const {
- return _mod.log(logBuilder);
- }
+TEST(SimpleMod, PrepareNonArray) {
+ Document doc(fromjson("{a: 1}"));
+ Mod pushMod(fromjson("{$push: {a: 1}}"));
- ModifierPush& mod() { return _mod; }
+ ModifierInterface::ExecInfo dummy;
+ ASSERT_NOT_OK(pushMod.prepare(doc.root(), "", &dummy));
+}
- private:
- ModifierPush _mod;
- BSONObj _modObj;
- };
+TEST(SimpleMod, PrepareApplyEmpty) {
+ Document doc(fromjson("{a: []}"));
+ Mod pushMod(fromjson("{$push: {a: 1}}"));
- TEST(SimpleMod, PrepareNonArray) {
- Document doc(fromjson("{a: 1}"));
- Mod pushMod(fromjson("{$push: {a: 1}}"));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
- ModifierInterface::ExecInfo dummy;
- ASSERT_NOT_OK(pushMod.prepare(doc.root(), "", &dummy));
- }
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
- TEST(SimpleMod, PrepareApplyEmpty) {
- Document doc(fromjson("{a: []}"));
- Mod pushMod(fromjson("{$push: {a: 1}}"));
+ ASSERT_OK(pushMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: [1]}"), doc);
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(pushMod.log(&logBuilder));
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(fromjson("{$set: {a: [1]}}"), logDoc);
+}
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
+TEST(SimpleMod, PrepareApplyInexistent) {
+ Document doc(fromjson("{}"));
+ Mod pushMod(fromjson("{$push: {a: 1}}"));
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [1]}"), doc);
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {a: [1]}}"), logDoc);
- }
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
- TEST(SimpleMod, PrepareApplyInexistent) {
- Document doc(fromjson("{}"));
- Mod pushMod(fromjson("{$push: {a: 1}}"));
+ ASSERT_OK(pushMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: [1]}"), doc);
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(pushMod.log(&logBuilder));
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(fromjson("{$set: {a: [1]}}"), logDoc);
+}
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
+TEST(SimpleMod, PrepareApplyNormal) {
+ Document doc(fromjson("{a: [0]}"));
+ Mod pushMod(fromjson("{$push: {a: 1}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [1]}"), doc);
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {a: [1]}}"), logDoc);
- }
+ ASSERT_OK(pushMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: [0,1]}"), doc);
- TEST(SimpleMod, PrepareApplyNormal) {
- Document doc(fromjson("{a: [0]}"));
- Mod pushMod(fromjson("{$push: {a: 1}}"));
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(pushMod.log(&logBuilder));
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(fromjson("{$set: {'a.1':1}}"), logDoc);
+}
+
+//
+// Simple object mod
+//
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
+TEST(SimpleObjMod, PrepareNonArray) {
+ Document doc(fromjson("{a: 1}"));
+ Mod pushMod(fromjson("{$push: {a: {b: 1}}}"));
+
+ ModifierInterface::ExecInfo dummy;
+ ASSERT_NOT_OK(pushMod.prepare(doc.root(), "", &dummy));
+}
+
+TEST(SimpleObjMod, PrepareApplyEmpty) {
+ Document doc(fromjson("{a: []}"));
+ Mod pushMod(fromjson("{$push: {a: {b: 1}}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(pushMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: [{b:1}]}"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(pushMod.log(&logBuilder));
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(fromjson("{$set: {a: [{b:1}]}}"), logDoc);
+}
+
+TEST(SimpleObjMod, PrepareApplyInexistent) {
+ Document doc(fromjson("{}"));
+ Mod pushMod(fromjson("{$push: {a: {b: 1}}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(pushMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: [{b:1}]}"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(pushMod.log(&logBuilder));
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(fromjson("{$set: {a: [{b:1}]}}"), logDoc);
+}
+
+TEST(SimpleObjMod, PrepareApplyNormal) {
+ Document doc(fromjson("{a: [{b:0}]}"));
+ Mod pushMod(fromjson("{$push: {a: {b: 1}}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(pushMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: [{b:0},{b:1}]}"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(pushMod.log(&logBuilder));
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(fromjson("{$set: {'a.1':{b:1}}}"), logDoc);
+}
+
+TEST(SimpleObjMod, PrepareApplyDotted) {
+ Document doc(fromjson(
+ "{ _id : 1 , "
+ " question : 'a', "
+ " choices : { "
+ " first : { choice : 'b' }, "
+ " second : { choice : 'c' } }"
+ "}"));
+ Mod pushMod(fromjson("{$push: {'choices.first.votes': 1}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "choices.first.votes");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(pushMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson(
+ "{ _id : 1 , "
+ " question : 'a', "
+ " choices : { "
+ " first : { choice : 'b', votes: [1]}, "
+ " second : { choice : 'c' } }"
+ "}"),
+ doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(pushMod.log(&logBuilder));
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(fromjson("{$set: {'choices.first.votes':[1]}}"), logDoc);
+}
+
+
+//
+// $pushAll Variation
+//
+
+TEST(PushAll, PrepareApplyEmpty) {
+ Document doc(fromjson("{a: []}"));
+ Mod pushMod(fromjson("{$pushAll: {a: [1]}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(pushMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(doc, fromjson("{a: [1]}"));
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(pushMod.log(&logBuilder));
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(logDoc, fromjson("{$set: {a: [1]}}"));
+}
+
+TEST(PushAll, PrepareApplyInexistent) {
+ Document doc(fromjson("{}"));
+ Mod pushMod(fromjson("{$pushAll: {a: [1]}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(pushMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(doc, fromjson("{a: [1]}"));
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(pushMod.log(&logBuilder));
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(logDoc, fromjson("{$set: {a: [1]}}"));
+}
+
+TEST(PushAll, PrepareApplyNormal) {
+ Document doc(fromjson("{a: [0]}"));
+ Mod pushMod(fromjson("{$pushAll: {a: [1,2]}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(pushMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(doc, fromjson("{a: [0,1,2]}"));
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(pushMod.log(&logBuilder));
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(logDoc, fromjson("{$set: {'a.1': 1, 'a.2':2}}"));
+}
+
+//
+// Simple $each mod
+//
+
+TEST(SimpleEachMod, PrepareNonArray) {
+ Document doc(fromjson("{a: 1}"));
+ Mod pushMod(fromjson("{$push: {a: {$each: [1]}}}"));
+
+ ModifierInterface::ExecInfo dummy;
+ ASSERT_NOT_OK(pushMod.prepare(doc.root(), "", &dummy));
+}
+
+TEST(SimpleEachMod, PrepareApplyEmpty) {
+ Document doc(fromjson("{a: []}"));
+ Mod pushMod(fromjson("{$push: {a: {$each: [1]}}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(pushMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: [1]}"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(pushMod.log(&logBuilder));
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(fromjson("{$set: {a: [1]}}"), logDoc);
+}
+
+TEST(SimpleEachMod, PrepareApplyInexistent) {
+ Document doc(fromjson("{}"));
+ Mod pushMod(fromjson("{$push: {a: {$each: [1]}}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(pushMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: [1]}"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(pushMod.log(&logBuilder));
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(fromjson("{$set: {a: [1]}}"), logDoc);
+}
+
+TEST(SimpleEachMod, PrepareApplyInexistentMultiple) {
+ Document doc(fromjson("{}"));
+ Mod pushMod(fromjson("{$push: {a: {$each: [1, 2]}}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(pushMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: [1, 2]}"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(pushMod.log(&logBuilder));
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(fromjson("{$set: {a: [1, 2]}}"), logDoc);
+}
+
+TEST(SimpleEachMod, PrepareApplyNormal) {
+ Document doc(fromjson("{a: [0]}"));
+ Mod pushMod(fromjson("{$push: {a: {$each: [1]}}}"));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(pushMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: [0,1]}"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(pushMod.log(&logBuilder));
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(fromjson("{$set: {'a.1': 1}}"), logDoc);
+}
+
+TEST(SimpleEachMod, PrepareApplyNormalMultiple) {
+ Document doc(fromjson("{a: [0]}"));
+ Mod pushMod(fromjson("{$push: {a: {$each: [1,2]}}}"));
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [0,1]}"), doc);
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {'a.1':1}}"), logDoc);
- }
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(pushMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: [0,1,2]}"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(pushMod.log(&logBuilder));
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(fromjson("{$set: {'a.1': 1, 'a.2':2}}"), logDoc);
+}
- //
- // Simple object mod
- //
+/**
+ * Slice variants
+ */
+TEST(SlicePushEach, TopOne) {
+ Document doc(fromjson("{a: [3]}"));
+ Mod pushMod(fromjson("{$push: {a: {$each: [2, -1], $slice:1}}}"));
- TEST(SimpleObjMod, PrepareNonArray) {
- Document doc(fromjson("{a: 1}"));
- Mod pushMod(fromjson("{$push: {a: {b: 1}}}"));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
- ModifierInterface::ExecInfo dummy;
- ASSERT_NOT_OK(pushMod.prepare(doc.root(), "", &dummy));
- }
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
- TEST(SimpleObjMod, PrepareApplyEmpty) {
- Document doc(fromjson("{a: []}"));
- Mod pushMod(fromjson("{$push: {a: {b: 1}}}"));
+ ASSERT_OK(pushMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: [3]}"), doc);
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(pushMod.log(&logBuilder));
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(fromjson("{$set: {a: [3]}}"), logDoc);
+}
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
+/**
+ * Sort for scalar (whole) array elements
+ */
+TEST(SortPushEach, NumberSort) {
+ Document doc(fromjson("{a: [3]}"));
+ Mod pushMod(fromjson("{$push: {a: {$each: [2, -1], $sort:1}}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(pushMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: [-1,2,3]}"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(pushMod.log(&logBuilder));
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(fromjson("{$set: {a: [-1, 2, 3]}}"), logDoc);
+}
+
+TEST(SortPushEach, NumberSortReverse) {
+ Document doc(fromjson("{a: [3]}"));
+ Mod pushMod(fromjson("{$push: {a: {$each: [4, -1], $sort:-1}}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(pushMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: [4,3,-1]}"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(pushMod.log(&logBuilder));
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(fromjson("{$set: {a: [4,3,-1]}}"), logDoc);
+}
+
+TEST(SortPushEach, MixedSortWhole) {
+ Document doc(fromjson("{a: [3, 't', {b:1}, {a:1}]}"));
+ Mod pushMod(fromjson("{$push: {a: {$each: [4, -1], $sort:1}}}"));
+ const BSONObj expectedObj = fromjson("{a: [-1,3,4,'t', {a:1}, {b:1}]}");
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(pushMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(expectedObj, doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(pushMod.log(&logBuilder));
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(BSON("$set" << expectedObj), logDoc);
+}
+
+TEST(SortPushEach, MixedSortWholeReverse) {
+ Document doc(fromjson("{a: [3, 't', {b:1}, {a:1}]}"));
+ Mod pushMod(fromjson("{$push: {a: {$each: [4, -1], $sort:-1}}}"));
+ const BSONObj expectedObj = fromjson("{a: [{b:1}, {a:1}, 't', 4, 3, -1]}");
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(pushMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(expectedObj, doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(pushMod.log(&logBuilder));
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(BSON("$set" << expectedObj), logDoc);
+}
+
+TEST(SortPushEach, MixedSortEmbeddedField) {
+ Document doc(fromjson("{a: [3, 't', {b:1}, {a:1}]}"));
+ Mod pushMod(fromjson("{$push: {a: {$each: [4, -1], $sort:{a:1}}}}"));
+ const BSONObj expectedObj = fromjson("{a: [3, 't', {b: 1}, 4, -1, {a: 1}]}");
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(pushMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(expectedObj, doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(pushMod.log(&logBuilder));
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(BSON("$set" << expectedObj), logDoc);
+}
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [{b:1}]}"), doc);
+/**
+ * This fixture supports building $push mods with parameterized $each arrays and $slices.
+ * It always assume that the array being operated on is called 'a'. To build a mod, one
+ * issues a set*Mod() call.
+ *
+ * The setSimpleMod() call will build a $each array of numbers. The setObjectMod() call
+ * will build a $each array with object. Both these calls take the slice as a parameter as
+ * well.
+ *
+ * Here's a typical test case flow:
+ * + Determine what the original document's 'a' array would contain
+ * + Ditto for the $push's $each arrray
+ * + Loop over slice value
+ * + Apply the $push with current slice value to the doc
+ * + Use the fixture/helpers to combine and slice the mod's and original's 'a'
+ * array
+ * + Build a document with the above and check against the one generated by the mod apply
+ */
+class SlicedMod : public mongo::unittest::Test {
+public:
+ SlicedMod() : _mod() {}
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {a: [{b:1}]}}"), logDoc);
+ virtual void setUp() {
+ // no op; set all state using the setMod() call
}
- TEST(SimpleObjMod, PrepareApplyInexistent) {
- Document doc(fromjson("{}"));
- Mod pushMod(fromjson("{$push: {a: {b: 1}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
+ /** Sets up the mod to be {$push: {a: {$each: [<eachArray>], $slice: <slice>}}} */
+ void setSimpleMod(int32_t slice, const vector<int>& eachArray) {
+ BSONArrayBuilder arrBuilder;
+ for (vector<int>::const_iterator it = eachArray.begin(); it != eachArray.end(); ++it) {
+ arrBuilder.append(*it);
+ }
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [{b:1}]}"), doc);
+ _modObj =
+ BSON("$push" << BSON("a" << BSON("$each" << arrBuilder.arr() << "$slice" << slice)));
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {a: [{b:1}]}}"), logDoc);
+ ASSERT_OK(_mod.init(_modObj["$push"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
}
- TEST(SimpleObjMod, PrepareApplyNormal) {
- Document doc(fromjson("{a: [{b:0}]}"));
- Mod pushMod(fromjson("{$push: {a: {b: 1}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
+ /** Sets up the mod to be {$push: {a: {$each:[<Obj>,...], $slice:<slice>, $sort:<Obj>}}} */
+ void setSortMod(int32_t slice, const vector<BSONObj>& eachArray, BSONObj sort) {
+ BSONArrayBuilder arrBuilder;
+ for (vector<BSONObj>::const_iterator it = eachArray.begin(); it != eachArray.end(); ++it) {
+ arrBuilder.append(*it);
+ }
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [{b:0},{b:1}]}"), doc);
+ _modObj = BSON("$push" << BSON("a" << BSON("$each" << arrBuilder.arr() << "$slice" << slice
+ << "$sort" << sort)));
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {'a.1':{b:1}}}"), logDoc);
+ ASSERT_OK(_mod.init(_modObj["$push"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
}
- TEST(SimpleObjMod, PrepareApplyDotted) {
- Document doc(fromjson("{ _id : 1 , "
- " question : 'a', "
- " choices : { "
- " first : { choice : 'b' }, "
- " second : { choice : 'c' } }"
- "}"));
- Mod pushMod(fromjson("{$push: {'choices.first.votes': 1}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
+ /** Returns an object {a: [<'vec's content>]} */
+ BSONObj getObjectUsing(const vector<int>& vec) {
+ BSONArrayBuilder arrBuilder;
+ for (vector<int>::const_iterator it = vec.begin(); it != vec.end(); ++it) {
+ arrBuilder.append(*it);
+ }
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "choices.first.votes");
- ASSERT_FALSE(execInfo.noOp);
+ BSONObjBuilder builder;
+ builder.appendArray("a", arrBuilder.obj());
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson( "{ _id : 1 , "
- " question : 'a', "
- " choices : { "
- " first : { choice : 'b', votes: [1]}, "
- " second : { choice : 'c' } }"
- "}"),
- doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {'choices.first.votes':[1]}}"), logDoc);
+ return builder.obj();
}
+ /** Returns an object {a: [<'vec's content>]} */
+ BSONObj getObjectUsing(const vector<BSONObj>& vec) {
+ BSONArrayBuilder arrBuilder;
+ for (vector<BSONObj>::const_iterator it = vec.begin(); it != vec.end(); ++it) {
+ arrBuilder.append(*it);
+ }
- //
- // $pushAll Variation
- //
-
- TEST(PushAll, PrepareApplyEmpty) {
- Document doc(fromjson("{a: []}"));
- Mod pushMod(fromjson("{$pushAll: {a: [1]}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(doc, fromjson("{a: [1]}"));
+ BSONObjBuilder builder;
+ builder.appendArray("a", arrBuilder.obj());
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(logDoc, fromjson("{$set: {a: [1]}}"));
+ return builder.obj();
}
- TEST(PushAll, PrepareApplyInexistent) {
- Document doc(fromjson("{}"));
- Mod pushMod(fromjson("{$pushAll: {a: [1]}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(doc, fromjson("{a: [1]}"));
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(logDoc, fromjson("{$set: {a: [1]}}"));
+ ModifierPush& mod() {
+ return _mod;
}
- TEST(PushAll, PrepareApplyNormal) {
- Document doc(fromjson("{a: [0]}"));
- Mod pushMod(fromjson("{$pushAll: {a: [1,2]}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(doc, fromjson("{a: [0,1,2]}"));
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(logDoc, fromjson("{$set: {'a.1': 1, 'a.2':2}}"));
+ BSONObj modObj() {
+ return _modObj;
}
- //
- // Simple $each mod
- //
+private:
+ ModifierPush _mod;
+ BSONObj _modObj;
+ vector<int> _eachArray;
+};
- TEST(SimpleEachMod, PrepareNonArray) {
- Document doc(fromjson("{a: 1}"));
- Mod pushMod(fromjson("{$push: {a: {$each: [1]}}}"));
+TEST_F(SlicedMod, SimpleArrayFromEmpty) {
+ // We'll simulate the original document having {a: []} and the mod being
+ // {$push: {a: {$each: [1], $slice: <-2..0>}}}
+ vector<int> docArray;
+ vector<int> eachArray;
+ eachArray.push_back(1);
- ModifierInterface::ExecInfo dummy;
- ASSERT_NOT_OK(pushMod.prepare(doc.root(), "", &dummy));
- }
-
- TEST(SimpleEachMod, PrepareApplyEmpty) {
- Document doc(fromjson("{a: []}"));
- Mod pushMod(fromjson("{$push: {a: {$each: [1]}}}"));
+ for (int32_t slice = -2; slice <= 0; slice++) {
+ setSimpleMod(slice, eachArray);
+ Document doc(getObjectUsing(docArray /* {a: []} */));
ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_OK(mod().prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
ASSERT_FALSE(execInfo.noOp);
- ASSERT_OK(pushMod.apply());
+ ASSERT_OK(mod().apply());
ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [1]}"), doc);
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {a: [1]}}"), logDoc);
+ vector<int> combinedVec;
+ combineVec(docArray, /* a: [] */
+ eachArray, /* a: [1] */
+ slice,
+ &combinedVec);
+ ASSERT_EQUALS(getObjectUsing(combinedVec), doc);
}
+}
- TEST(SimpleEachMod, PrepareApplyInexistent) {
- Document doc(fromjson("{}"));
- Mod pushMod(fromjson("{$push: {a: {$each: [1]}}}"));
+TEST_F(SlicedMod, SimpleArrayFromExisting) {
+ // We'll simulate the original document having {a: [2,3]} and the mod being
+ // {$push: {a: {$each: [1], $slice: <-4..0>}}}
+ vector<int> docArray;
+ docArray.push_back(2);
+ docArray.push_back(3);
+ vector<int> eachArray;
+ eachArray.push_back(1);
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [1]}"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {a: [1]}}"), logDoc);
- }
-
- TEST(SimpleEachMod, PrepareApplyInexistentMultiple) {
- Document doc(fromjson("{}"));
- Mod pushMod(fromjson("{$push: {a: {$each: [1, 2]}}}"));
+ for (int32_t slice = -4; slice <= 0; slice++) {
+ setSimpleMod(slice, eachArray);
+ Document doc(getObjectUsing(docArray /* {a: [2, 3]} */));
ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_OK(mod().prepare(doc.root(), "", &execInfo));
ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
ASSERT_FALSE(execInfo.noOp);
- ASSERT_OK(pushMod.apply());
+ ASSERT_OK(mod().apply());
ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [1, 2]}"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {a: [1, 2]}}"), logDoc);
- }
-
- TEST(SimpleEachMod, PrepareApplyNormal) {
- Document doc(fromjson("{a: [0]}"));
- Mod pushMod(fromjson("{$push: {a: {$each: [1]}}}"));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
+ vector<int> combinedVec;
+ combineVec(docArray, /* a: [2, 3] */
+ eachArray, /* a: [1] */
+ slice,
+ &combinedVec);
+ ASSERT_EQUALS(getObjectUsing(combinedVec), doc);
+ }
+}
+
+TEST_F(SlicedMod, ObjectArrayFromEmpty) {
+ // We'll simulate the original document having {a: []} and the mod being
+ // {$push: {a: {$each: [{a:2,b:1}], $slice: <-4..0>}, $sort: {a:-1/1,b:-1/1}}
+ vector<BSONObj> docArray;
+ vector<BSONObj> eachArray;
+ eachArray.push_back(fromjson("{a:2,b:1}"));
+ eachArray.push_back(fromjson("{a:1,b:2}"));
+
+ for (int32_t aOrB = 0; aOrB < 2; aOrB++) {
+ for (int32_t sortA = 0; sortA < 2; sortA++) {
+ for (int32_t sortB = 0; sortB < 2; sortB++) {
+ for (int32_t slice = -3; slice <= 3; slice++) {
+ BSONObj sortOrder;
+ if (aOrB == 0) {
+ sortOrder = BSON("a" << (sortA ? 1 : -1) << "b" << (sortB ? 1 : -1));
+ } else {
+ sortOrder = BSON("b" << (sortB ? 1 : -1) << "a" << (sortA ? 1 : -1));
+ }
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
+ setSortMod(slice, eachArray, sortOrder);
+ Document doc(getObjectUsing(docArray /* {a: []} */));
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [0,1]}"), doc);
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod().prepare(doc.root(), "", &execInfo));
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {'a.1': 1}}"), logDoc);
- }
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
- TEST(SimpleEachMod, PrepareApplyNormalMultiple) {
- Document doc(fromjson("{a: [0]}"));
- Mod pushMod(fromjson("{$push: {a: {$each: [1,2]}}}"));
+ ASSERT_OK(mod().apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
+ vector<BSONObj> combinedVec;
+ combineAndSortVec(docArray, /* a: [] */
+ eachArray, /* a: [{a:2,b:1},{a:1,b:2}] */
+ slice,
+ sortOrder,
+ &combinedVec);
+ ASSERT_EQUALS(getObjectUsing(combinedVec), doc);
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [0,1,2]}"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {'a.1': 1, 'a.2':2}}"), logDoc);
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod().log(&logBuilder));
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(BSON("$set" << getObjectUsing(combinedVec)), logDoc);
+ }
+ }
+ }
}
+}
+
+TEST_F(SlicedMod, ObjectArrayFromExisting) {
+ // We'll simulate the original document having {a: [{a:2,b:3},{a:3,:b1}]} and the mod being
+ // {$push: {a: {$each: [{a:2,b:1}], $slice: <-4..0>}, $sort: {a:-1/1,b:-1/1}}
+ vector<BSONObj> docArray;
+ docArray.push_back(fromjson("{a:2,b:3}"));
+ docArray.push_back(fromjson("{a:3,b:1}"));
+ vector<BSONObj> eachArray;
+ eachArray.push_back(fromjson("{a:2,b:1}"));
+
+ for (int32_t aOrB = 0; aOrB < 2; aOrB++) {
+ for (int32_t sortA = 0; sortA < 2; sortA++) {
+ for (int32_t sortB = 0; sortB < 2; sortB++) {
+ for (int32_t slice = -4; slice <= 4; slice++) {
+ BSONObj sortOrder;
+ if (aOrB == 0) {
+ sortOrder = BSON("a" << (sortA ? 1 : -1) << "b" << (sortB ? 1 : -1));
+ } else {
+ sortOrder = BSON("b" << (sortB ? 1 : -1) << "a" << (sortA ? 1 : -1));
+ }
- /**
- * Slice variants
- */
- TEST(SlicePushEach, TopOne) {
- Document doc(fromjson("{a: [3]}"));
- Mod pushMod(fromjson("{$push: {a: {$each: [2, -1], $slice:1}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [3]}"), doc);
+ setSortMod(slice, eachArray, sortOrder);
+ Document doc(getObjectUsing(docArray /* {a: [{a:2,b:b},{a:3,:b1}]} */));
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {a: [3]}}"), logDoc);
- }
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(mod().prepare(doc.root(), "", &execInfo));
- /**
- * Sort for scalar (whole) array elements
- */
- TEST(SortPushEach, NumberSort) {
- Document doc(fromjson("{a: [3]}"));
- Mod pushMod(fromjson("{$push: {a: {$each: [2, -1], $sort:1}}}"));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_OK(mod().apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
+ vector<BSONObj> combinedVec;
+ combineAndSortVec(docArray, /* a: [{a:2,b:3},{a:3,:b1}] */
+ eachArray, /* a: [{a:2,b:1}] */
+ slice,
+ sortOrder,
+ &combinedVec);
+ ASSERT_EQUALS(getObjectUsing(combinedVec), doc);
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [-1,2,3]}"), doc);
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(mod().log(&logBuilder));
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(BSON("$set" << getObjectUsing(combinedVec)), logDoc);
+ }
+ }
+ }
+ }
+}
+
+// Push to position tests
+
+TEST(ToPosition, BadInputs) {
+ const char* const bad[] = {
+ "{$push: {a: { $each: [1], $position:-1}}}",
+ "{$push: {a: { $each: [1], $position:'s'}}}",
+ "{$push: {a: { $each: [1], $position:{}}}}",
+ "{$push: {a: { $each: [1], $position:[0]}}}",
+ "{$push: {a: { $each: [1], $position:1.1211212}}}",
+ "{$push: {a: { $each: [1], $position:3.000000000001}}}",
+ "{$push: {a: { $each: [1], $position:1.2}}}",
+ "{$push: {a: { $each: [1], $position:-1.2}}}",
+ "{$push: {a: { $each: [1], $position:9.0e19}}}",
+ "{$push: {a: { $each: [1], $position:-9.0e19}}}",
+ NULL,
+ };
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {a: [-1, 2, 3]}}"), logDoc);
+ int i = 0;
+ while (bad[i] != NULL) {
+ ModifierPush pushMod(ModifierPush::PUSH_NORMAL);
+ BSONObj modObj = fromjson(bad[i]);
+ ASSERT_NOT_OK(pushMod.init(modObj.firstElement().embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+ i++;
}
+}
- TEST(SortPushEach, NumberSortReverse) {
- Document doc(fromjson("{a: [3]}"));
- Mod pushMod(fromjson("{$push: {a: {$each: [4, -1], $sort:-1}}}"));
+TEST(ToPosition, GoodInputs) {
+ {
+ Document doc(fromjson("{a: []}"));
+ Mod pushMod(fromjson("{$push: {a: { $each: [1], $position: NumberLong(1)}}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [4,3,-1]}"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {a: [4,3,-1]}}"), logDoc);
}
-
- TEST(SortPushEach, MixedSortWhole) {
- Document doc(fromjson("{a: [3, 't', {b:1}, {a:1}]}"));
- Mod pushMod(fromjson("{$push: {a: {$each: [4, -1], $sort:1}}}"));
- const BSONObj expectedObj = fromjson("{a: [-1,3,4,'t', {a:1}, {b:1}]}");
+ {
+ Document doc(fromjson("{a: []}"));
+ Mod pushMod(fromjson("{$push: {a: { $each: [1], $position:NumberInt(100)}}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(expectedObj, doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(BSON("$set" << expectedObj), logDoc);
}
-
- TEST(SortPushEach, MixedSortWholeReverse) {
- Document doc(fromjson("{a: [3, 't', {b:1}, {a:1}]}"));
- Mod pushMod(fromjson("{$push: {a: {$each: [4, -1], $sort:-1}}}"));
- const BSONObj expectedObj = fromjson("{a: [{b:1}, {a:1}, 't', 4, 3, -1]}");
+ {
+ Document doc(fromjson("{a: []}"));
+ Mod pushMod(fromjson("{$push: {a: { $each: [1], $position:1.0}}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(expectedObj, doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(BSON("$set" << expectedObj), logDoc);
}
-
- TEST(SortPushEach, MixedSortEmbeddedField) {
- Document doc(fromjson("{a: [3, 't', {b:1}, {a:1}]}"));
- Mod pushMod(fromjson("{$push: {a: {$each: [4, -1], $sort:{a:1}}}}"));
- const BSONObj expectedObj = fromjson("{a: [3, 't', {b: 1}, 4, -1, {a: 1}]}");
+ {
+ Document doc(fromjson("{a: []}"));
+ Mod pushMod(fromjson("{$push: {a: { $each: [1], $position:1000000}}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(expectedObj, doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(BSON("$set" << expectedObj), logDoc);
- }
-
- /**
- * This fixture supports building $push mods with parameterized $each arrays and $slices.
- * It always assume that the array being operated on is called 'a'. To build a mod, one
- * issues a set*Mod() call.
- *
- * The setSimpleMod() call will build a $each array of numbers. The setObjectMod() call
- * will build a $each array with object. Both these calls take the slice as a parameter as
- * well.
- *
- * Here's a typical test case flow:
- * + Determine what the original document's 'a' array would contain
- * + Ditto for the $push's $each arrray
- * + Loop over slice value
- * + Apply the $push with current slice value to the doc
- * + Use the fixture/helpers to combine and slice the mod's and original's 'a'
- * array
- * + Build a document with the above and check against the one generated by the mod apply
- */
- class SlicedMod : public mongo::unittest::Test {
- public:
- SlicedMod() : _mod() {}
-
- virtual void setUp() {
- // no op; set all state using the setMod() call
- }
-
- /** Sets up the mod to be {$push: {a: {$each: [<eachArray>], $slice: <slice>}}} */
- void setSimpleMod(int32_t slice, const vector<int>& eachArray) {
-
- BSONArrayBuilder arrBuilder;
- for (vector<int>::const_iterator it = eachArray.begin(); it != eachArray.end(); ++it) {
- arrBuilder.append(*it);
- }
-
- _modObj = BSON("$push" <<
- BSON("a"
- << BSON("$each" << arrBuilder.arr() <<
- "$slice" << slice)));
-
- ASSERT_OK(_mod.init(_modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
-
- /** Sets up the mod to be {$push: {a: {$each:[<Obj>,...], $slice:<slice>, $sort:<Obj>}}} */
- void setSortMod(int32_t slice, const vector<BSONObj>& eachArray, BSONObj sort) {
-
- BSONArrayBuilder arrBuilder;
- for (vector<BSONObj>::const_iterator it = eachArray.begin();
- it != eachArray.end();
- ++it) {
- arrBuilder.append(*it);
- }
-
- _modObj = BSON("$push" <<
- BSON("a"
- << BSON("$each" << arrBuilder.arr() <<
- "$slice" << slice <<
- "$sort" << sort)));
-
- ASSERT_OK(_mod.init(_modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
-
- /** Returns an object {a: [<'vec's content>]} */
- BSONObj getObjectUsing(const vector<int>& vec) {
-
- BSONArrayBuilder arrBuilder;
- for (vector<int>::const_iterator it = vec.begin(); it != vec.end(); ++it) {
- arrBuilder.append(*it);
- }
-
- BSONObjBuilder builder;
- builder.appendArray("a", arrBuilder.obj());
-
- return builder.obj();
- }
-
- /** Returns an object {a: [<'vec's content>]} */
- BSONObj getObjectUsing(const vector<BSONObj>& vec) {
-
- BSONArrayBuilder arrBuilder;
- for (vector<BSONObj>::const_iterator it = vec.begin(); it != vec.end(); ++it) {
- arrBuilder.append(*it);
- }
-
- BSONObjBuilder builder;
- builder.appendArray("a", arrBuilder.obj());
-
- return builder.obj();
- }
-
- ModifierPush& mod() { return _mod; }
-
- BSONObj modObj() { return _modObj; }
-
- private:
- ModifierPush _mod;
- BSONObj _modObj;
- vector<int> _eachArray;
- };
-
- TEST_F(SlicedMod, SimpleArrayFromEmpty) {
- // We'll simulate the original document having {a: []} and the mod being
- // {$push: {a: {$each: [1], $slice: <-2..0>}}}
- vector<int> docArray;
- vector<int> eachArray;
- eachArray.push_back(1);
-
- for (int32_t slice = -2; slice <= 0; slice++) {
- setSimpleMod(slice, eachArray);
- Document doc(getObjectUsing(docArray /* {a: []} */));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod().prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod().apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
-
- vector<int> combinedVec;
- combineVec(docArray, /* a: [] */
- eachArray, /* a: [1] */
- slice,
- &combinedVec);
- ASSERT_EQUALS(getObjectUsing(combinedVec), doc);
- }
- }
-
- TEST_F(SlicedMod, SimpleArrayFromExisting) {
- // We'll simulate the original document having {a: [2,3]} and the mod being
- // {$push: {a: {$each: [1], $slice: <-4..0>}}}
- vector<int> docArray;
- docArray.push_back(2);
- docArray.push_back(3);
- vector<int> eachArray;
- eachArray.push_back(1);
-
- for (int32_t slice = -4; slice <= 0; slice++) {
- setSimpleMod(slice, eachArray);
- Document doc(getObjectUsing(docArray /* {a: [2, 3]} */));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod().prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod().apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
-
- vector<int> combinedVec;
- combineVec(docArray, /* a: [2, 3] */
- eachArray, /* a: [1] */
- slice,
- &combinedVec);
- ASSERT_EQUALS(getObjectUsing(combinedVec), doc);
- }
- }
-
- TEST_F(SlicedMod, ObjectArrayFromEmpty) {
- // We'll simulate the original document having {a: []} and the mod being
- // {$push: {a: {$each: [{a:2,b:1}], $slice: <-4..0>}, $sort: {a:-1/1,b:-1/1}}
- vector<BSONObj> docArray;
- vector<BSONObj> eachArray;
- eachArray.push_back(fromjson("{a:2,b:1}"));
- eachArray.push_back(fromjson("{a:1,b:2}"));
-
- for (int32_t aOrB = 0; aOrB < 2 ; aOrB++) {
- for (int32_t sortA = 0; sortA < 2; sortA++) {
- for (int32_t sortB = 0; sortB < 2; sortB++) {
- for (int32_t slice = -3; slice <= 3; slice++) {
-
- BSONObj sortOrder;
- if (aOrB == 0) {
- sortOrder = BSON("a" << (sortA ? 1 : -1) << "b" << (sortB ? 1 : -1));
- }
- else {
- sortOrder = BSON("b" << (sortB ? 1 : -1) << "a" << (sortA ? 1 : -1));
- }
-
- setSortMod(slice, eachArray, sortOrder);
- Document doc(getObjectUsing(docArray /* {a: []} */));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod().prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod().apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
-
- vector<BSONObj> combinedVec;
- combineAndSortVec(docArray, /* a: [] */
- eachArray, /* a: [{a:2,b:1},{a:1,b:2}] */
- slice,
- sortOrder,
- &combinedVec);
- ASSERT_EQUALS(getObjectUsing(combinedVec), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod().log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(BSON("$set" << getObjectUsing(combinedVec)), logDoc);
-
- }
- }
- }
- }
- }
-
- TEST_F(SlicedMod, ObjectArrayFromExisting) {
- // We'll simulate the original document having {a: [{a:2,b:3},{a:3,:b1}]} and the mod being
- // {$push: {a: {$each: [{a:2,b:1}], $slice: <-4..0>}, $sort: {a:-1/1,b:-1/1}}
- vector<BSONObj> docArray;
- docArray.push_back(fromjson("{a:2,b:3}"));
- docArray.push_back(fromjson("{a:3,b:1}"));
- vector<BSONObj> eachArray;
- eachArray.push_back(fromjson("{a:2,b:1}"));
-
- for (int32_t aOrB = 0; aOrB < 2 ; aOrB++) {
- for (int32_t sortA = 0; sortA < 2; sortA++) {
- for (int32_t sortB = 0; sortB < 2; sortB++) {
- for (int32_t slice = -4; slice <= 4; slice++) {
-
- BSONObj sortOrder;
- if (aOrB == 0) {
- sortOrder = BSON("a" << (sortA ? 1 : -1) << "b" << (sortB ? 1 : -1));
- }
- else {
- sortOrder = BSON("b" << (sortB ? 1 : -1) << "a" << (sortA ? 1 : -1));
- }
-
- setSortMod(slice, eachArray, sortOrder);
- Document doc(getObjectUsing(docArray /* {a: [{a:2,b:b},{a:3,:b1}]} */));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod().prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod().apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
-
- vector<BSONObj> combinedVec;
- combineAndSortVec(docArray, /* a: [{a:2,b:3},{a:3,:b1}] */
- eachArray, /* a: [{a:2,b:1}] */
- slice,
- sortOrder,
- &combinedVec);
- ASSERT_EQUALS(getObjectUsing(combinedVec), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod().log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(BSON("$set" << getObjectUsing(combinedVec)), logDoc);
-
- }
- }
- }
- }
- }
-
- // Push to position tests
-
- TEST(ToPosition, BadInputs) {
- const char* const bad[] = {
- "{$push: {a: { $each: [1], $position:-1}}}",
- "{$push: {a: { $each: [1], $position:'s'}}}",
- "{$push: {a: { $each: [1], $position:{}}}}",
- "{$push: {a: { $each: [1], $position:[0]}}}",
- "{$push: {a: { $each: [1], $position:1.1211212}}}",
- "{$push: {a: { $each: [1], $position:3.000000000001}}}",
- "{$push: {a: { $each: [1], $position:1.2}}}",
- "{$push: {a: { $each: [1], $position:-1.2}}}",
- "{$push: {a: { $each: [1], $position:9.0e19}}}",
- "{$push: {a: { $each: [1], $position:-9.0e19}}}",
- NULL,
- };
-
- int i = 0;
- while(bad[i] != NULL)
- {
- ModifierPush pushMod(ModifierPush::PUSH_NORMAL);
- BSONObj modObj = fromjson(bad[i]);
- ASSERT_NOT_OK(pushMod.init(modObj.firstElement().embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- i++;
- }
}
-
- TEST(ToPosition, GoodInputs) {
- {
- Document doc(fromjson("{a: []}"));
- Mod pushMod(fromjson("{$push: {a: { $each: [1], $position: NumberLong(1)}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
- }
- {
- Document doc(fromjson("{a: []}"));
- Mod pushMod(fromjson("{$push: {a: { $each: [1], $position:NumberInt(100)}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
- }
- {
- Document doc(fromjson("{a: []}"));
- Mod pushMod(fromjson("{$push: {a: { $each: [1], $position:1.0}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
- }
- {
- Document doc(fromjson("{a: []}"));
- Mod pushMod(fromjson("{$push: {a: { $each: [1], $position:1000000}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
- }
- {
- Document doc(fromjson("{a: []}"));
- Mod pushMod(fromjson("{$push: {a: { $each: [1], $position:0}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
- }
- }
-
- TEST(ToPosition, EmptyArrayFront) {
+ {
Document doc(fromjson("{a: []}"));
Mod pushMod(fromjson("{$push: {a: { $each: [1], $position:0}}}"));
ModifierInterface::ExecInfo execInfo;
ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
+ }
+}
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
+TEST(ToPosition, EmptyArrayFront) {
+ Document doc(fromjson("{a: []}"));
+ Mod pushMod(fromjson("{$push: {a: { $each: [1], $position:0}}}"));
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [1]}"), doc);
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {'a':[1]}}"), logDoc);
- }
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
- TEST(ToPosition, EmptyArrayBackBigPosition) {
- Document doc(fromjson("{a: []}"));
- Mod pushMod(fromjson("{$push: {a: { $each: [1], $position:1000}}}"));
+ ASSERT_OK(pushMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: [1]}"), doc);
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(pushMod.log(&logBuilder));
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(fromjson("{$set: {'a':[1]}}"), logDoc);
+}
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
+TEST(ToPosition, EmptyArrayBackBigPosition) {
+ Document doc(fromjson("{a: []}"));
+ Mod pushMod(fromjson("{$push: {a: { $each: [1], $position:1000}}}"));
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [1]}"), doc);
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {'a':[1]}}"), logDoc);
- }
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
- TEST(ToPosition, EmptyArrayBack) {
- Document doc(fromjson("{a: []}"));
- Mod pushMod(fromjson("{$push: {a: { $each: [1], $position:1}}}"));
+ ASSERT_OK(pushMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: [1]}"), doc);
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(pushMod.log(&logBuilder));
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(fromjson("{$set: {'a':[1]}}"), logDoc);
+}
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
+TEST(ToPosition, EmptyArrayBack) {
+ Document doc(fromjson("{a: []}"));
+ Mod pushMod(fromjson("{$push: {a: { $each: [1], $position:1}}}"));
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [1]}"), doc);
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {'a':[1]}}"), logDoc);
- }
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
- TEST(ToPosition, Front) {
- Document doc(fromjson("{a: [0]}"));
- Mod pushMod(fromjson("{$push: {a: { $each: [1], $position:0}}}"));
+ ASSERT_OK(pushMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: [1]}"), doc);
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(pushMod.log(&logBuilder));
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(fromjson("{$set: {'a':[1]}}"), logDoc);
+}
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
+TEST(ToPosition, Front) {
+ Document doc(fromjson("{a: [0]}"));
+ Mod pushMod(fromjson("{$push: {a: { $each: [1], $position:0}}}"));
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [1, 0]}"), doc);
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {'a':[1, 0]}}"), logDoc);
- }
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
- TEST(ToPosition, Back) {
- Document doc(fromjson("{a: [0]}"));
- Mod pushMod(fromjson("{$push: {a: { $each: [1], $position:100}}}"));
+ ASSERT_OK(pushMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: [1, 0]}"), doc);
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(pushMod.log(&logBuilder));
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(fromjson("{$set: {'a':[1, 0]}}"), logDoc);
+}
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
+TEST(ToPosition, Back) {
+ Document doc(fromjson("{a: [0]}"));
+ Mod pushMod(fromjson("{$push: {a: { $each: [1], $position:100}}}"));
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [0,1]}"), doc);
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {'a.1':1}}"), logDoc);
- }
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(pushMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: [0,1]}"), doc);
-} // unnamed namespace
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(pushMod.log(&logBuilder));
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(fromjson("{$set: {'a.1':1}}"), logDoc);
+}
+
+} // unnamed namespace
diff --git a/src/mongo/db/ops/modifier_rename.cpp b/src/mongo/db/ops/modifier_rename.cpp
index 13ec0c70beb..123b253f227 100644
--- a/src/mongo/db/ops/modifier_rename.cpp
+++ b/src/mongo/db/ops/modifier_rename.cpp
@@ -38,279 +38,258 @@
namespace mongo {
- namespace str = mongoutils::str;
+namespace str = mongoutils::str;
- struct ModifierRename::PreparedState {
+struct ModifierRename::PreparedState {
+ PreparedState(mutablebson::Element root)
+ : doc(root.getDocument()),
+ fromElemFound(doc.end()),
+ toIdxFound(0),
+ toElemFound(doc.end()),
+ applyCalled(false) {}
- PreparedState(mutablebson::Element root)
- : doc(root.getDocument())
- , fromElemFound(doc.end())
- , toIdxFound(0)
- , toElemFound(doc.end())
- , applyCalled(false){
- }
-
- // Document that is going to be changed.
- mutablebson::Document& doc;
+ // Document that is going to be changed.
+ mutablebson::Document& doc;
- // The element to rename
- mutablebson::Element fromElemFound;
+ // The element to rename
+ mutablebson::Element fromElemFound;
- // Index in _fieldRef for which an Element exist in the document.
- size_t toIdxFound;
+ // Index in _fieldRef for which an Element exist in the document.
+ size_t toIdxFound;
- // Element to remove (in the destination position)
- mutablebson::Element toElemFound;
+ // Element to remove (in the destination position)
+ mutablebson::Element toElemFound;
- // Was apply called?
- bool applyCalled;
+ // Was apply called?
+ bool applyCalled;
+};
- };
+ModifierRename::ModifierRename() : _fromFieldRef(), _toFieldRef() {}
- ModifierRename::ModifierRename()
- : _fromFieldRef()
- , _toFieldRef() {
- }
+ModifierRename::~ModifierRename() {}
- ModifierRename::~ModifierRename() {
+Status ModifierRename::init(const BSONElement& modExpr, const Options& opts, bool* positional) {
+ if (modExpr.type() != String) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "The 'to' field for $rename must be a string: " << modExpr);
}
- Status ModifierRename::init(const BSONElement& modExpr, const Options& opts,
- bool* positional) {
-
- if (modExpr.type() != String) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "The 'to' field for $rename must be a string: "
- << modExpr);
- }
+ // Extract the field names from the mod expression
- // Extract the field names from the mod expression
+ _fromFieldRef.parse(modExpr.fieldName());
+ Status status = fieldchecker::isUpdatable(_fromFieldRef);
+ if (!status.isOK())
+ return status;
- _fromFieldRef.parse(modExpr.fieldName());
- Status status = fieldchecker::isUpdatable(_fromFieldRef);
- if (!status.isOK())
- return status;
+ _toFieldRef.parse(modExpr.String());
+ status = fieldchecker::isUpdatable(_toFieldRef);
+ if (!status.isOK())
+ return status;
- _toFieldRef.parse(modExpr.String());
- status = fieldchecker::isUpdatable(_toFieldRef);
- if (!status.isOK())
+ // TODO: Remove this restriction and make a noOp to lift restriction
+ // Old restriction is that if the fields are the same then it is not allowed.
+ if (_fromFieldRef == _toFieldRef)
+ return Status(ErrorCodes::BadValue,
+ str::stream()
+ << "The source and target field for $rename must differ: " << modExpr);
+
+ // TODO: Remove this restriction by allowing moving deeping from the 'from' path
+ // Old restriction is that if the to/from is on the same path it fails
+ if (_fromFieldRef.isPrefixOf(_toFieldRef) || _toFieldRef.isPrefixOf(_fromFieldRef)) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "The source and target field for $rename must "
+ "not be on the same path: " << modExpr);
+ }
+ // TODO: We can remove this restriction as long as there is only one,
+ // or it is the same array -- should think on this a bit.
+ //
+ // If a $-positional operator was used it is an error
+ size_t dummyPos;
+ if (fieldchecker::isPositional(_fromFieldRef, &dummyPos))
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "The source field for $rename may not be dynamic: "
+ << _fromFieldRef.dottedField());
+ else if (fieldchecker::isPositional(_toFieldRef, &dummyPos))
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "The destination field for $rename may not be dynamic: "
+ << _toFieldRef.dottedField());
+
+ if (positional)
+ *positional = false;
+
+ return Status::OK();
+}
+
+Status ModifierRename::prepare(mutablebson::Element root,
+ StringData matchedField,
+ ExecInfo* execInfo) {
+ // Rename doesn't work with positional fields ($)
+ dassert(matchedField.empty());
+
+ _preparedState.reset(new PreparedState(root));
+
+ // Locate the to field name in 'root', which must exist.
+ size_t fromIdxFound;
+ Status status = pathsupport::findLongestPrefix(
+ _fromFieldRef, root, &fromIdxFound, &_preparedState->fromElemFound);
+
+ const bool sourceExists =
+ (_preparedState->fromElemFound.ok() && fromIdxFound == (_fromFieldRef.numParts() - 1));
+
+ // If we can't find the full element in the from field then we can't do anything.
+ if (!status.isOK() || !sourceExists) {
+ execInfo->noOp = true;
+ _preparedState->fromElemFound = root.getDocument().end();
+
+ // TODO: remove this special case from existing behavior
+ if (status.code() == ErrorCodes::PathNotViable) {
return status;
-
- // TODO: Remove this restriction and make a noOp to lift restriction
- // Old restriction is that if the fields are the same then it is not allowed.
- if (_fromFieldRef == _toFieldRef)
- return Status(ErrorCodes::BadValue,
- str::stream() << "The source and target field for $rename must differ: "
- << modExpr);
-
- // TODO: Remove this restriction by allowing moving deeping from the 'from' path
- // Old restriction is that if the to/from is on the same path it fails
- if (_fromFieldRef.isPrefixOf(_toFieldRef) || _toFieldRef.isPrefixOf(_fromFieldRef)){
- return Status(ErrorCodes::BadValue,
- str::stream() << "The source and target field for $rename must "
- "not be on the same path: "
- << modExpr);
}
- // TODO: We can remove this restriction as long as there is only one,
- // or it is the same array -- should think on this a bit.
- //
- // If a $-positional operator was used it is an error
- size_t dummyPos;
- if (fieldchecker::isPositional(_fromFieldRef, &dummyPos))
- return Status(ErrorCodes::BadValue,
- str::stream() << "The source field for $rename may not be dynamic: "
- << _fromFieldRef.dottedField());
- else if (fieldchecker::isPositional(_toFieldRef, &dummyPos))
- return Status(ErrorCodes::BadValue,
- str::stream() << "The destination field for $rename may not be dynamic: "
- << _toFieldRef.dottedField());
-
- if (positional)
- *positional = false;
return Status::OK();
}
- Status ModifierRename::prepare(mutablebson::Element root,
- StringData matchedField,
- ExecInfo* execInfo) {
- // Rename doesn't work with positional fields ($)
- dassert(matchedField.empty());
-
- _preparedState.reset(new PreparedState(root));
-
- // Locate the to field name in 'root', which must exist.
- size_t fromIdxFound;
- Status status = pathsupport::findLongestPrefix(_fromFieldRef,
- root,
- &fromIdxFound,
- &_preparedState->fromElemFound);
+ // Ensure no array in ancestry if what we found is not at the root
+ mutablebson::Element curr = _preparedState->fromElemFound.parent();
+ if (curr != curr.getDocument().root())
+ while (curr.ok() && (curr != curr.getDocument().root())) {
+ if (curr.getType() == Array)
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "The source field cannot be an array element, '"
+ << _fromFieldRef.dottedField() << "' in doc with "
+ << findElementNamed(root.leftChild(), "_id").toString()
+ << " has an array field called '" << curr.getFieldName()
+ << "'");
+ curr = curr.parent();
+ }
- const bool sourceExists = (_preparedState->fromElemFound.ok() &&
- fromIdxFound == (_fromFieldRef.numParts() - 1));
+ // "To" side validation below
- // If we can't find the full element in the from field then we can't do anything.
- if (!status.isOK() || !sourceExists) {
- execInfo->noOp = true;
- _preparedState->fromElemFound = root.getDocument().end();
+ status = pathsupport::findLongestPrefix(
+ _toFieldRef, root, &_preparedState->toIdxFound, &_preparedState->toElemFound);
- // TODO: remove this special case from existing behavior
- if (status.code() == ErrorCodes::PathNotViable) {
- return status;
- }
+ // FindLongestPrefix may return not viable or any other error and then we cannot proceed.
+ if (status.code() == ErrorCodes::NonExistentPath) {
+ // Not an error condition as we will create the "to" path as needed.
+ } else if (!status.isOK()) {
+ return status;
+ }
- return Status::OK();
+ const bool destExists = _preparedState->toElemFound.ok() &&
+ (_preparedState->toIdxFound == (_toFieldRef.numParts() - 1));
+
+ // Ensure no array in ancestry of "to" Element
+ // Set to either parent, or node depending on if the full path element was found
+ curr = (destExists ? _preparedState->toElemFound.parent() : _preparedState->toElemFound);
+ if (curr != curr.getDocument().root()) {
+ while (curr.ok()) {
+ if (curr.getType() == Array)
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "The destination field cannot be an array element, '"
+ << _fromFieldRef.dottedField() << "' in doc with "
+ << findElementNamed(root.leftChild(), "_id").toString()
+ << " has an array field called '" << curr.getFieldName()
+ << "'");
+ curr = curr.parent();
}
+ }
- // Ensure no array in ancestry if what we found is not at the root
- mutablebson::Element curr = _preparedState->fromElemFound.parent();
- if (curr != curr.getDocument().root())
- while (curr.ok() && (curr != curr.getDocument().root())) {
- if (curr.getType() == Array)
- return Status(ErrorCodes::BadValue,
- str::stream() << "The source field cannot be an array element, '"
- << _fromFieldRef.dottedField() << "' in doc with "
- << findElementNamed(root.leftChild(), "_id").toString()
- << " has an array field called '" << curr.getFieldName() << "'");
- curr = curr.parent();
- }
-
- // "To" side validation below
-
- status = pathsupport::findLongestPrefix(_toFieldRef,
- root,
- &_preparedState->toIdxFound,
- &_preparedState->toElemFound);
-
- // FindLongestPrefix may return not viable or any other error and then we cannot proceed.
- if (status.code() == ErrorCodes::NonExistentPath) {
- // Not an error condition as we will create the "to" path as needed.
- } else if (!status.isOK()) {
- return status;
- }
+ // We register interest in the field name. The driver needs this info to sort out if
+ // there is any conflict among mods.
+ execInfo->fieldRef[0] = &_fromFieldRef;
+ execInfo->fieldRef[1] = &_toFieldRef;
- const bool destExists = _preparedState->toElemFound.ok() &&
- (_preparedState->toIdxFound == (_toFieldRef.numParts()-1));
-
- // Ensure no array in ancestry of "to" Element
- // Set to either parent, or node depending on if the full path element was found
- curr = (destExists ? _preparedState->toElemFound.parent() : _preparedState->toElemFound);
- if (curr != curr.getDocument().root()) {
- while (curr.ok()) {
- if (curr.getType() == Array)
- return Status(ErrorCodes::BadValue,
- str::stream()
- << "The destination field cannot be an array element, '"
- << _fromFieldRef.dottedField() << "' in doc with "
- << findElementNamed(root.leftChild(), "_id").toString()
- << " has an array field called '" << curr.getFieldName() << "'");
- curr = curr.parent();
- }
- }
+ execInfo->noOp = false;
- // We register interest in the field name. The driver needs this info to sort out if
- // there is any conflict among mods.
- execInfo->fieldRef[0] = &_fromFieldRef;
- execInfo->fieldRef[1] = &_toFieldRef;
+ return Status::OK();
+}
- execInfo->noOp = false;
+Status ModifierRename::apply() const {
+ dassert(_preparedState->fromElemFound.ok());
- return Status::OK();
- }
+ _preparedState->applyCalled = true;
- Status ModifierRename::apply() const {
- dassert(_preparedState->fromElemFound.ok());
+ // Remove from source
+ Status removeStatus = _preparedState->fromElemFound.remove();
+ if (!removeStatus.isOK()) {
+ return removeStatus;
+ }
- _preparedState->applyCalled = true;
+ // If there's no need to create any further field part, the op is simply a value
+ // assignment.
+ const bool destExists = _preparedState->toElemFound.ok() &&
+ (_preparedState->toIdxFound == (_toFieldRef.numParts() - 1));
- // Remove from source
- Status removeStatus = _preparedState->fromElemFound.remove();
+ if (destExists) {
+ removeStatus = _preparedState->toElemFound.remove();
if (!removeStatus.isOK()) {
return removeStatus;
}
+ }
- // If there's no need to create any further field part, the op is simply a value
- // assignment.
- const bool destExists = _preparedState->toElemFound.ok() &&
- (_preparedState->toIdxFound == (_toFieldRef.numParts()-1));
-
- if (destExists) {
- removeStatus = _preparedState->toElemFound.remove();
- if (!removeStatus.isOK()) {
- return removeStatus;
- }
- }
+ // Creates the final element that's going to be the in 'doc'.
+ mutablebson::Document& doc = _preparedState->doc;
+ StringData lastPart = _toFieldRef.getPart(_toFieldRef.numParts() - 1);
+ mutablebson::Element elemToSet =
+ doc.makeElementWithNewFieldName(lastPart, _preparedState->fromElemFound);
+ if (!elemToSet.ok()) {
+ return Status(ErrorCodes::InternalError, "can't create new element");
+ }
- // Creates the final element that's going to be the in 'doc'.
- mutablebson::Document& doc = _preparedState->doc;
- StringData lastPart = _toFieldRef.getPart(_toFieldRef.numParts()-1);
- mutablebson::Element elemToSet = doc.makeElementWithNewFieldName(
- lastPart,
- _preparedState->fromElemFound);
- if (!elemToSet.ok()) {
- return Status(ErrorCodes::InternalError, "can't create new element");
- }
+ // Find the new place to put the "to" element:
+ // createPathAt does not use existing prefix elements so we
+ // need to get the prefix match position for createPathAt below
+ size_t tempIdx = 0;
+ mutablebson::Element tempElem = doc.end();
+ Status status = pathsupport::findLongestPrefix(_toFieldRef, doc.root(), &tempIdx, &tempElem);
+
+ // createPathAt will complete the path and attach 'elemToSet' at the end of it.
+ return pathsupport::createPathAt(_toFieldRef,
+ tempElem == doc.end() ? 0 : tempIdx + 1,
+ tempElem == doc.end() ? doc.root() : tempElem,
+ elemToSet);
+}
+
+Status ModifierRename::log(LogBuilder* logBuilder) const {
+ // If there was no element found then it was a noop, so return immediately
+ if (!_preparedState->fromElemFound.ok())
+ return Status::OK();
- // Find the new place to put the "to" element:
- // createPathAt does not use existing prefix elements so we
- // need to get the prefix match position for createPathAt below
- size_t tempIdx = 0;
- mutablebson::Element tempElem = doc.end();
- Status status = pathsupport::findLongestPrefix(_toFieldRef,
- doc.root(),
- &tempIdx,
- &tempElem);
-
- // createPathAt will complete the path and attach 'elemToSet' at the end of it.
- return pathsupport::createPathAt(_toFieldRef,
- tempElem == doc.end() ? 0 : tempIdx + 1,
- tempElem == doc.end() ? doc.root() : tempElem,
- elemToSet);
- }
+ // debug assert if apply not called, since we found an element to move.
+ dassert(_preparedState->applyCalled);
- Status ModifierRename::log(LogBuilder* logBuilder) const {
+ const bool isPrefix = _fromFieldRef.isPrefixOf(_toFieldRef);
+ const StringData setPath = (isPrefix ? _fromFieldRef : _toFieldRef).dottedField();
+ const StringData unsetPath = isPrefix ? StringData() : _fromFieldRef.dottedField();
+ const bool doUnset = !isPrefix;
- // If there was no element found then it was a noop, so return immediately
- if (!_preparedState->fromElemFound.ok())
- return Status::OK();
+ // We'd like to create an entry such as {$set: {<fieldname>: <value>}} under 'logRoot'.
+ // We start by creating the {$set: ...} Element.
+ mutablebson::Document& doc = logBuilder->getDocument();
- // debug assert if apply not called, since we found an element to move.
- dassert(_preparedState->applyCalled);
+ // Create the {<fieldname>: <value>} Element. Note that we log the mod with a
+ // dotted field, if it was applied over a dotted field. The rationale is that the
+ // secondary may be in a different state than the primary and thus make different
+ // decisions about creating the intermediate path in _fieldRef or not.
+ mutablebson::Element logElement =
+ doc.makeElementWithNewFieldName(setPath, _preparedState->fromElemFound.getValue());
- const bool isPrefix = _fromFieldRef.isPrefixOf(_toFieldRef);
- const StringData setPath =
- (isPrefix ? _fromFieldRef : _toFieldRef).dottedField();
- const StringData unsetPath =
- isPrefix ? StringData() : _fromFieldRef.dottedField();
- const bool doUnset = !isPrefix;
+ if (!logElement.ok()) {
+ return Status(ErrorCodes::InternalError, "cannot create details for $rename mod");
+ }
- // We'd like to create an entry such as {$set: {<fieldname>: <value>}} under 'logRoot'.
- // We start by creating the {$set: ...} Element.
- mutablebson::Document& doc = logBuilder->getDocument();
+ // Now, we attach the {<fieldname>: <value>} Element under the {$set: ...} section.
+ Status status = logBuilder->addToSets(logElement);
+ if (status.isOK() && doUnset) {
// Create the {<fieldname>: <value>} Element. Note that we log the mod with a
// dotted field, if it was applied over a dotted field. The rationale is that the
// secondary may be in a different state than the primary and thus make different
// decisions about creating the intermediate path in _fieldRef or not.
- mutablebson::Element logElement = doc.makeElementWithNewFieldName(
- setPath, _preparedState->fromElemFound.getValue());
-
- if (!logElement.ok()) {
- return Status(ErrorCodes::InternalError, "cannot create details for $rename mod");
- }
-
- // Now, we attach the {<fieldname>: <value>} Element under the {$set: ...} section.
- Status status = logBuilder->addToSets(logElement);
-
- if (status.isOK() && doUnset) {
- // Create the {<fieldname>: <value>} Element. Note that we log the mod with a
- // dotted field, if it was applied over a dotted field. The rationale is that the
- // secondary may be in a different state than the primary and thus make different
- // decisions about creating the intermediate path in _fieldRef or not.
- status = logBuilder->addToUnsets(unsetPath);
- }
-
- return status;
+ status = logBuilder->addToUnsets(unsetPath);
}
-} // namespace mongo
+ return status;
+}
+
+} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_rename.h b/src/mongo/db/ops/modifier_rename.h
index a43523f5320..883443893b3 100644
--- a/src/mongo/db/ops/modifier_rename.h
+++ b/src/mongo/db/ops/modifier_rename.h
@@ -38,61 +38,56 @@
namespace mongo {
- class LogBuilder;
+class LogBuilder;
- /**
- * The $rename modifier moves the field from source to the destination to perform
- * the rename.
- *
- * Example: {$rename: {<source>:<dest>}} where both <source/dest> are field names
- * Start with {a:1} and applying a {$rename: {"a":"b"} } produces {b:1}
- **/
- class ModifierRename : public ModifierInterface {
- MONGO_DISALLOW_COPYING(ModifierRename);
-
- public:
-
- ModifierRename();
- virtual ~ModifierRename();
-
- /**
- * We will check that the to/from are valid paths; in prepare more validation is done
- */
- virtual Status init(const BSONElement& modExpr, const Options& opts,
- bool* positional = NULL);
-
- /**
- * In prepare we will ensure that all restrictions are met:
- * -- The 'from' field exists, and is valid, else it is a no-op
- * -- The 'to' field is valid as a destination
- * -- The 'to' field is not on the path (or the same path) as the 'from' field
- * -- Neither 'to' nor 'from' have an array ancestor
- */
- virtual Status prepare(mutablebson::Element root,
- StringData matchedField,
- ExecInfo* execInfo);
-
- /**
- * We will transform the document by first making sure that the 'to' element
- * is empty before moving the 'from' element there.
- */
- virtual Status apply() const;
-
- /**
- * For the oplog entry we will generate an $unset on the 'from' field, and $set for
- * the 'to' field. If no 'from' element is found then function will return immediately.
- */
- virtual Status log(LogBuilder* logBuilder) const;
+/**
+* The $rename modifier moves the field from source to the destination to perform
+* the rename.
+*
+* Example: {$rename: {<source>:<dest>}} where both <source/dest> are field names
+* Start with {a:1} and applying a {$rename: {"a":"b"} } produces {b:1}
+**/
+class ModifierRename : public ModifierInterface {
+ MONGO_DISALLOW_COPYING(ModifierRename);
+
+public:
+ ModifierRename();
+ virtual ~ModifierRename();
- private:
+ /**
+ * We will check that the to/from are valid paths; in prepare more validation is done
+ */
+ virtual Status init(const BSONElement& modExpr, const Options& opts, bool* positional = NULL);
- // The source and destination fields
- FieldRef _fromFieldRef;
- FieldRef _toFieldRef;
+ /**
+ * In prepare we will ensure that all restrictions are met:
+ * -- The 'from' field exists, and is valid, else it is a no-op
+ * -- The 'to' field is valid as a destination
+ * -- The 'to' field is not on the path (or the same path) as the 'from' field
+ * -- Neither 'to' nor 'from' have an array ancestor
+ */
+ virtual Status prepare(mutablebson::Element root, StringData matchedField, ExecInfo* execInfo);
- // The state carried over from prepare for apply/log
- struct PreparedState;
- std::unique_ptr<PreparedState> _preparedState;
- };
+ /**
+ * We will transform the document by first making sure that the 'to' element
+ * is empty before moving the 'from' element there.
+ */
+ virtual Status apply() const;
-} // namespace mongo
+ /**
+ * For the oplog entry we will generate an $unset on the 'from' field, and $set for
+ * the 'to' field. If no 'from' element is found then function will return immediately.
+ */
+ virtual Status log(LogBuilder* logBuilder) const;
+
+private:
+ // The source and destination fields
+ FieldRef _fromFieldRef;
+ FieldRef _toFieldRef;
+
+ // The state carried over from prepare for apply/log
+ struct PreparedState;
+ std::unique_ptr<PreparedState> _preparedState;
+};
+
+} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_rename_test.cpp b/src/mongo/db/ops/modifier_rename_test.cpp
index 637926da8e1..4841c4b15a1 100644
--- a/src/mongo/db/ops/modifier_rename_test.cpp
+++ b/src/mongo/db/ops/modifier_rename_test.cpp
@@ -41,355 +41,355 @@
namespace {
- using mongo::BSONObj;
- using mongo::fromjson;
- using mongo::LogBuilder;
- using mongo::ModifierInterface;
- using mongo::NumberInt;
- using mongo::ModifierRename;
- using mongo::Status;
- using mongo::StringData;
- using mongo::mutablebson::ConstElement;
- using mongo::mutablebson::Document;
- using mongo::mutablebson::Element;
-
- /** Helper to build and manipulate the mod. */
- class Mod {
- public:
- Mod() : _mod() {}
-
- explicit Mod(BSONObj modObj) {
- _modObj = modObj;
- ASSERT_OK(_mod.init(_modObj["$rename"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
-
- 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);
- }
-
- ModifierRename& mod() { return _mod; }
-
- private:
- ModifierRename _mod;
- BSONObj _modObj;
- };
-
- /**
- * These test negative cases:
- * -- No '$' support for positional operator
- * -- No empty field names (ex. .a, b. )
- * -- Can't rename to an invalid fieldname (empty fieldname part)
- */
- TEST(InvalidInit, FromDbTests) {
- ModifierRename mod;
- ASSERT_NOT_OK(mod.init(fromjson("{'a.$':'b'}").firstElement(),
- ModifierInterface::Options::normal()));
- ASSERT_NOT_OK(mod.init(fromjson("{'a':'b.$'}").firstElement(),
- ModifierInterface::Options::normal()));
- ASSERT_NOT_OK(mod.init(fromjson("{'.b':'a'}").firstElement(),
- ModifierInterface::Options::normal()));
- ASSERT_NOT_OK(mod.init(fromjson("{'b.':'a'}").firstElement(),
- ModifierInterface::Options::normal()));
- ASSERT_NOT_OK(mod.init(fromjson("{'b':'.a'}").firstElement(),
- ModifierInterface::Options::normal()));
- ASSERT_NOT_OK(mod.init(fromjson("{'b':'a.'}").firstElement(),
- ModifierInterface::Options::normal()));
+using mongo::BSONObj;
+using mongo::fromjson;
+using mongo::LogBuilder;
+using mongo::ModifierInterface;
+using mongo::NumberInt;
+using mongo::ModifierRename;
+using mongo::Status;
+using mongo::StringData;
+using mongo::mutablebson::ConstElement;
+using mongo::mutablebson::Document;
+using mongo::mutablebson::Element;
+
+/** Helper to build and manipulate the mod. */
+class Mod {
+public:
+ Mod() : _mod() {}
+
+ explicit Mod(BSONObj modObj) {
+ _modObj = modObj;
+ ASSERT_OK(_mod.init(_modObj["$rename"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
}
- TEST(MissingFrom, InitPrepLog) {
- Document doc(fromjson("{a: 2}"));
- Mod setMod(fromjson("{$rename: {'b':'a'}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- BSONObj logObj = fromjson("{}");
- ASSERT_OK(setMod.log(&logBuilder));
- ASSERT_EQUALS(logDoc, logObj);
- }
-
- TEST(MissingFromDotted, InitPrepLog) {
- Document doc(fromjson("{a: {r:2}}"));
- Mod setMod(fromjson("{$rename: {'a.b':'a.c'}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- BSONObj logObj = fromjson("{}");
- ASSERT_OK(setMod.log(&logBuilder));
- ASSERT_EQUALS(logDoc, logObj);
- }
-
- TEST(BasicInit, DifferentRoots) {
- Document doc(fromjson("{a: 2}"));
- Mod setMod(fromjson("{$rename: {'a':'f.g'}}"));
- }
-
- TEST(MoveOnSamePath, MoveUp) {
- ModifierRename mod;
- ASSERT_NOT_OK(mod.init(fromjson("{'b.a':'b'}").firstElement(),
- ModifierInterface::Options::normal()));
- }
-
- TEST(MoveOnSamePath, MoveDown) {
- ModifierRename mod;
- ASSERT_NOT_OK(mod.init(fromjson("{'b':'b.a'}").firstElement(),
- ModifierInterface::Options::normal()));
- }
-
- TEST(MissingTo, SimpleNumberAtRoot) {
- Document doc(fromjson("{a: 2}"));
- Mod setMod(fromjson("{$rename: {'a':'b'}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_EQUALS(execInfo.fieldRef[1]->dottedField(), "b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(doc, fromjson("{b:2}"));
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- BSONObj logObj = fromjson("{$set:{ 'b': 2}, $unset: {'a': true}}");
- ASSERT_OK(setMod.log(&logBuilder));
- ASSERT_EQUALS(logDoc, logObj);
- }
-
- TEST(SimpleReplace, SameLevel) {
- Document doc(fromjson("{a: 2, b: 1}"));
- Mod setMod(fromjson("{$rename: {'a':'b'}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_EQUALS(execInfo.fieldRef[1]->dottedField(), "b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(doc, fromjson("{b:2}"));
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- BSONObj logObj = fromjson("{$set:{ 'b': 2}, $unset: {'a': true}}");
- ASSERT_OK(setMod.log(&logBuilder));
- ASSERT_EQUALS(logDoc, logObj);
- }
-
- TEST(SimpleReplace, FromDottedElement) {
- Document doc(fromjson("{a: {c: {d: 6}}, b: 1}"));
- Mod setMod(fromjson("{$rename: {'a.c':'b'}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.c");
- ASSERT_EQUALS(execInfo.fieldRef[1]->dottedField(), "b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(doc, fromjson("{a: {}, b:{ d: 6}}"));
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- BSONObj logObj = fromjson("{$set:{ 'b': {d: 6}}, $unset: {'a.c': true}}");
- ASSERT_OK(setMod.log(&logBuilder));
- ASSERT_EQUALS(logDoc, logObj);
- }
-
- TEST(DottedTo, MissingCompleteTo) {
- Document doc(fromjson("{a: 2, b: 1, c: {}}"));
- Mod setMod(fromjson("{$rename: {'a':'c.r.d'}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_EQUALS(execInfo.fieldRef[1]->dottedField(), "c.r.d");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(doc, fromjson("{b:1, c: { r: { d: 2}}}"));
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- BSONObj logObj = fromjson("{$set:{ 'c.r.d': 2}, $unset: {'a': true}}");
- ASSERT_OK(setMod.log(&logBuilder));
- ASSERT_EQUALS(logDoc, logObj);
- }
-
- TEST(DottedTo, ToIsCompletelyMissing) {
- Document doc(fromjson("{a: 2}"));
- Mod setMod(fromjson("{$rename: {'a':'b.c.d'}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_EQUALS(execInfo.fieldRef[1]->dottedField(), "b.c.d");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(doc, fromjson("{b: {c: {d: 2}}}"));
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- BSONObj logObj = fromjson("{$set:{ 'b.c.d': 2}, $unset: {'a': true}}");
- ASSERT_OK(setMod.log(&logBuilder));
- ASSERT_EQUALS(logDoc, logObj);
+ Status prepare(Element root, StringData matchedField, ModifierInterface::ExecInfo* execInfo) {
+ return _mod.prepare(root, matchedField, execInfo);
}
- TEST(FromArrayOfEmbeddedDocs, ToMissingDottedField) {
- Document doc(fromjson("{a: [ {a:2, b:1} ] }"));
- Mod setMod(fromjson("{$rename: {'a':'b.c.d'}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_EQUALS(execInfo.fieldRef[1]->dottedField(), "b.c.d");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(doc, fromjson("{b: {c: {d: [ {a:2, b:1} ]}}}"));
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- BSONObj logObj = fromjson("{$set:{ 'b.c.d': [ {a:2, b:1} ]}, $unset: {'a': true}}");
- ASSERT_OK(setMod.log(&logBuilder));
- ASSERT_EQUALS(logDoc, logObj);
+ Status apply() const {
+ return _mod.apply();
}
- TEST(FromArrayOfEmbeddedDocs, ToArray) {
- Document doc(fromjson("{a: [ {a:2, b:1} ] }"));
- Mod setMod(fromjson("{$rename: {'a.a':'a.b'}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(setMod.prepare(doc.root(), "", &execInfo));
+ Status log(LogBuilder* logBuilder) const {
+ return _mod.log(logBuilder);
}
- TEST(Arrays, MoveInto) {
- Document doc(fromjson("{a: [1, 2], b:2}"));
- Mod setMod(fromjson("{$rename: {'b':'a.2'}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(setMod.prepare(doc.root(), "", &execInfo));
+ ModifierRename& mod() {
+ return _mod;
}
- TEST(Arrays, MoveOut) {
- Document doc(fromjson("{a: [1, 2]}"));
- Mod setMod(fromjson("{$rename: {'a.0':'b'}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(setMod.prepare(doc.root(), "", &execInfo));
- }
-
- TEST(Arrays, MoveNonexistantEmbeddedFieldOut) {
- Document doc(fromjson("{a: [{a:1}, {b:2}]}"));
- Mod setMod(fromjson("{$rename: {'a.a':'b'}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(setMod.prepare(doc.root(), "", &execInfo));
- }
-
- TEST(Arrays, MoveEmbeddedFieldOutWithElementNumber) {
- Document doc(fromjson("{a: [{a:1}, {b:2}]}"));
- Mod setMod(fromjson("{$rename: {'a.0.a':'b'}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(setMod.prepare(doc.root(), "", &execInfo));
- }
+private:
+ ModifierRename _mod;
+ BSONObj _modObj;
+};
- TEST(Arrays, ReplaceArrayField) {
- Document doc(fromjson("{a: 2, b: []}"));
- Mod setMod(fromjson("{$rename: {'a':'b'}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_EQUALS(execInfo.fieldRef[1]->dottedField(), "b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(doc, fromjson("{b:2}"));
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- BSONObj logObj = fromjson("{$set:{ 'b': 2}, $unset: {'a': true}}");
- ASSERT_OK(setMod.log(&logBuilder));
- ASSERT_EQUALS(logDoc, logObj);
- }
-
-
- TEST(Arrays, ReplaceWithArrayField) {
- Document doc(fromjson("{a: [], b: 2}"));
- Mod setMod(fromjson("{$rename: {'a':'b'}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_EQUALS(execInfo.fieldRef[1]->dottedField(), "b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(doc, fromjson("{b:[]}"));
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- BSONObj logObj = fromjson("{$set:{ 'b': []}, $unset: {'a': true}}");
- ASSERT_OK(setMod.log(&logBuilder));
- ASSERT_EQUALS(logDoc, logObj);
- }
-
- TEST(LegacyData, CanRenameFromInvalidFieldName) {
- Document doc(fromjson("{$a: 2}"));
- Mod setMod(fromjson("{$rename: {'$a':'a'}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "$a");
- ASSERT_EQUALS(execInfo.fieldRef[1]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
+/**
+ * These test negative cases:
+ * -- No '$' support for positional operator
+ * -- No empty field names (ex. .a, b. )
+ * -- Can't rename to an invalid fieldname (empty fieldname part)
+ */
+TEST(InvalidInit, FromDbTests) {
+ ModifierRename mod;
+ ASSERT_NOT_OK(
+ mod.init(fromjson("{'a.$':'b'}").firstElement(), ModifierInterface::Options::normal()));
+ ASSERT_NOT_OK(
+ mod.init(fromjson("{'a':'b.$'}").firstElement(), ModifierInterface::Options::normal()));
+ ASSERT_NOT_OK(
+ mod.init(fromjson("{'.b':'a'}").firstElement(), ModifierInterface::Options::normal()));
+ ASSERT_NOT_OK(
+ mod.init(fromjson("{'b.':'a'}").firstElement(), ModifierInterface::Options::normal()));
+ ASSERT_NOT_OK(
+ mod.init(fromjson("{'b':'.a'}").firstElement(), ModifierInterface::Options::normal()));
+ ASSERT_NOT_OK(
+ mod.init(fromjson("{'b':'a.'}").firstElement(), ModifierInterface::Options::normal()));
+}
+
+TEST(MissingFrom, InitPrepLog) {
+ Document doc(fromjson("{a: 2}"));
+ Mod setMod(fromjson("{$rename: {'b':'a'}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ BSONObj logObj = fromjson("{}");
+ ASSERT_OK(setMod.log(&logBuilder));
+ ASSERT_EQUALS(logDoc, logObj);
+}
+
+TEST(MissingFromDotted, InitPrepLog) {
+ Document doc(fromjson("{a: {r:2}}"));
+ Mod setMod(fromjson("{$rename: {'a.b':'a.c'}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.noOp);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ BSONObj logObj = fromjson("{}");
+ ASSERT_OK(setMod.log(&logBuilder));
+ ASSERT_EQUALS(logDoc, logObj);
+}
+
+TEST(BasicInit, DifferentRoots) {
+ Document doc(fromjson("{a: 2}"));
+ Mod setMod(fromjson("{$rename: {'a':'f.g'}}"));
+}
+
+TEST(MoveOnSamePath, MoveUp) {
+ ModifierRename mod;
+ ASSERT_NOT_OK(
+ mod.init(fromjson("{'b.a':'b'}").firstElement(), ModifierInterface::Options::normal()));
+}
+
+TEST(MoveOnSamePath, MoveDown) {
+ ModifierRename mod;
+ ASSERT_NOT_OK(
+ mod.init(fromjson("{'b':'b.a'}").firstElement(), ModifierInterface::Options::normal()));
+}
+
+TEST(MissingTo, SimpleNumberAtRoot) {
+ Document doc(fromjson("{a: 2}"));
+ Mod setMod(fromjson("{$rename: {'a':'b'}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_EQUALS(execInfo.fieldRef[1]->dottedField(), "b");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(setMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(doc, fromjson("{b:2}"));
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ BSONObj logObj = fromjson("{$set:{ 'b': 2}, $unset: {'a': true}}");
+ ASSERT_OK(setMod.log(&logBuilder));
+ ASSERT_EQUALS(logDoc, logObj);
+}
+
+TEST(SimpleReplace, SameLevel) {
+ Document doc(fromjson("{a: 2, b: 1}"));
+ Mod setMod(fromjson("{$rename: {'a':'b'}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_EQUALS(execInfo.fieldRef[1]->dottedField(), "b");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(setMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(doc, fromjson("{b:2}"));
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ BSONObj logObj = fromjson("{$set:{ 'b': 2}, $unset: {'a': true}}");
+ ASSERT_OK(setMod.log(&logBuilder));
+ ASSERT_EQUALS(logDoc, logObj);
+}
+
+TEST(SimpleReplace, FromDottedElement) {
+ Document doc(fromjson("{a: {c: {d: 6}}, b: 1}"));
+ Mod setMod(fromjson("{$rename: {'a.c':'b'}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.c");
+ ASSERT_EQUALS(execInfo.fieldRef[1]->dottedField(), "b");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(setMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(doc, fromjson("{a: {}, b:{ d: 6}}"));
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ BSONObj logObj = fromjson("{$set:{ 'b': {d: 6}}, $unset: {'a.c': true}}");
+ ASSERT_OK(setMod.log(&logBuilder));
+ ASSERT_EQUALS(logDoc, logObj);
+}
+
+TEST(DottedTo, MissingCompleteTo) {
+ Document doc(fromjson("{a: 2, b: 1, c: {}}"));
+ Mod setMod(fromjson("{$rename: {'a':'c.r.d'}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_EQUALS(execInfo.fieldRef[1]->dottedField(), "c.r.d");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(setMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(doc, fromjson("{b:1, c: { r: { d: 2}}}"));
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ BSONObj logObj = fromjson("{$set:{ 'c.r.d': 2}, $unset: {'a': true}}");
+ ASSERT_OK(setMod.log(&logBuilder));
+ ASSERT_EQUALS(logDoc, logObj);
+}
+
+TEST(DottedTo, ToIsCompletelyMissing) {
+ Document doc(fromjson("{a: 2}"));
+ Mod setMod(fromjson("{$rename: {'a':'b.c.d'}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_EQUALS(execInfo.fieldRef[1]->dottedField(), "b.c.d");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(setMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(doc, fromjson("{b: {c: {d: 2}}}"));
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ BSONObj logObj = fromjson("{$set:{ 'b.c.d': 2}, $unset: {'a': true}}");
+ ASSERT_OK(setMod.log(&logBuilder));
+ ASSERT_EQUALS(logDoc, logObj);
+}
+
+TEST(FromArrayOfEmbeddedDocs, ToMissingDottedField) {
+ Document doc(fromjson("{a: [ {a:2, b:1} ] }"));
+ Mod setMod(fromjson("{$rename: {'a':'b.c.d'}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_EQUALS(execInfo.fieldRef[1]->dottedField(), "b.c.d");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(setMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(doc, fromjson("{b: {c: {d: [ {a:2, b:1} ]}}}"));
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ BSONObj logObj = fromjson("{$set:{ 'b.c.d': [ {a:2, b:1} ]}, $unset: {'a': true}}");
+ ASSERT_OK(setMod.log(&logBuilder));
+ ASSERT_EQUALS(logDoc, logObj);
+}
+
+TEST(FromArrayOfEmbeddedDocs, ToArray) {
+ Document doc(fromjson("{a: [ {a:2, b:1} ] }"));
+ Mod setMod(fromjson("{$rename: {'a.a':'a.b'}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_NOT_OK(setMod.prepare(doc.root(), "", &execInfo));
+}
+
+TEST(Arrays, MoveInto) {
+ Document doc(fromjson("{a: [1, 2], b:2}"));
+ Mod setMod(fromjson("{$rename: {'b':'a.2'}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_NOT_OK(setMod.prepare(doc.root(), "", &execInfo));
+}
+
+TEST(Arrays, MoveOut) {
+ Document doc(fromjson("{a: [1, 2]}"));
+ Mod setMod(fromjson("{$rename: {'a.0':'b'}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_NOT_OK(setMod.prepare(doc.root(), "", &execInfo));
+}
+
+TEST(Arrays, MoveNonexistantEmbeddedFieldOut) {
+ Document doc(fromjson("{a: [{a:1}, {b:2}]}"));
+ Mod setMod(fromjson("{$rename: {'a.a':'b'}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_NOT_OK(setMod.prepare(doc.root(), "", &execInfo));
+}
+
+TEST(Arrays, MoveEmbeddedFieldOutWithElementNumber) {
+ Document doc(fromjson("{a: [{a:1}, {b:2}]}"));
+ Mod setMod(fromjson("{$rename: {'a.0.a':'b'}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_NOT_OK(setMod.prepare(doc.root(), "", &execInfo));
+}
+
+TEST(Arrays, ReplaceArrayField) {
+ Document doc(fromjson("{a: 2, b: []}"));
+ Mod setMod(fromjson("{$rename: {'a':'b'}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_EQUALS(execInfo.fieldRef[1]->dottedField(), "b");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(setMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(doc, fromjson("{b:2}"));
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ BSONObj logObj = fromjson("{$set:{ 'b': 2}, $unset: {'a': true}}");
+ ASSERT_OK(setMod.log(&logBuilder));
+ ASSERT_EQUALS(logDoc, logObj);
+}
+
+
+TEST(Arrays, ReplaceWithArrayField) {
+ Document doc(fromjson("{a: [], b: 2}"));
+ Mod setMod(fromjson("{$rename: {'a':'b'}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_EQUALS(execInfo.fieldRef[1]->dottedField(), "b");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(setMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(doc, fromjson("{b:[]}"));
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(doc, fromjson("{a:2}"));
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ BSONObj logObj = fromjson("{$set:{ 'b': []}, $unset: {'a': true}}");
+ ASSERT_OK(setMod.log(&logBuilder));
+ ASSERT_EQUALS(logDoc, logObj);
+}
+
+TEST(LegacyData, CanRenameFromInvalidFieldName) {
+ Document doc(fromjson("{$a: 2}"));
+ Mod setMod(fromjson("{$rename: {'$a':'a'}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "$a");
+ ASSERT_EQUALS(execInfo.fieldRef[1]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(setMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(doc, fromjson("{a:2}"));
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- BSONObj logObj = fromjson("{$set:{ 'a': 2}, $unset: {'$a': true}}");
- ASSERT_OK(setMod.log(&logBuilder));
- ASSERT_EQUALS(logDoc, logObj);
- }
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ BSONObj logObj = fromjson("{$set:{ 'a': 2}, $unset: {'$a': true}}");
+ ASSERT_OK(setMod.log(&logBuilder));
+ ASSERT_EQUALS(logDoc, logObj);
+}
-} // namespace
+} // namespace
diff --git a/src/mongo/db/ops/modifier_set.cpp b/src/mongo/db/ops/modifier_set.cpp
index fe808138c68..93abc04f892 100644
--- a/src/mongo/db/ops/modifier_set.cpp
+++ b/src/mongo/db/ops/modifier_set.cpp
@@ -37,253 +37,224 @@
namespace mongo {
- namespace str = mongoutils::str;
+namespace str = mongoutils::str;
- struct ModifierSet::PreparedState {
+struct ModifierSet::PreparedState {
+ PreparedState(mutablebson::Document* targetDoc)
+ : doc(*targetDoc), idxFound(0), elemFound(doc.end()), noOp(false), elemIsBlocking(false) {}
- PreparedState(mutablebson::Document* targetDoc)
- : doc(*targetDoc)
- , idxFound(0)
- , elemFound(doc.end())
- , noOp(false)
- , elemIsBlocking(false) {
- }
+ // Document that is going to be changed.
+ mutablebson::Document& doc;
- // Document that is going to be changed.
- mutablebson::Document& doc;
+ // Index in _fieldRef for which an Element exist in the document.
+ size_t idxFound;
- // Index in _fieldRef for which an Element exist in the document.
- size_t idxFound;
+ // Element corresponding to _fieldRef[0.._idxFound].
+ mutablebson::Element elemFound;
- // Element corresponding to _fieldRef[0.._idxFound].
- mutablebson::Element elemFound;
+ // This $set is a no-op?
+ bool noOp;
- // This $set is a no-op?
- bool noOp;
+ // The element we find during a replication operation that blocks our update path
+ bool elemIsBlocking;
+};
- // The element we find during a replication operation that blocks our update path
- bool elemIsBlocking;
+ModifierSet::ModifierSet(ModifierSet::ModifierSetMode mode)
+ : _fieldRef(), _posDollar(0), _setMode(mode), _val(), _modOptions() {}
- };
+ModifierSet::~ModifierSet() {}
- ModifierSet::ModifierSet(ModifierSet::ModifierSetMode mode)
- : _fieldRef()
- , _posDollar(0)
- , _setMode(mode)
- , _val()
- , _modOptions() {
- }
+Status ModifierSet::init(const BSONElement& modExpr, const Options& opts, bool* positional) {
+ //
+ // field name analysis
+ //
- ModifierSet::~ModifierSet() {
+ // Break down the field name into its 'dotted' components (aka parts) and check that
+ // the field is fit for updates
+ _fieldRef.parse(modExpr.fieldName());
+ Status status = fieldchecker::isUpdatable(_fieldRef);
+ if (!status.isOK()) {
+ return status;
}
- Status ModifierSet::init(const BSONElement& modExpr, const Options& opts,
- bool* positional) {
-
- //
- // field name analysis
- //
-
- // Break down the field name into its 'dotted' components (aka parts) and check that
- // the field is fit for updates
- _fieldRef.parse(modExpr.fieldName());
- Status status = fieldchecker::isUpdatable(_fieldRef);
- if (! status.isOK()) {
- return status;
- }
-
- // If a $-positional operator was used, get the index in which it occurred
- // and ensure only one occurrence.
- size_t foundCount;
- bool foundDollar = fieldchecker::isPositional(_fieldRef, &_posDollar, &foundCount);
-
- if (positional)
- *positional = foundDollar;
+ // If a $-positional operator was used, get the index in which it occurred
+ // and ensure only one occurrence.
+ size_t foundCount;
+ bool foundDollar = fieldchecker::isPositional(_fieldRef, &_posDollar, &foundCount);
- if (foundDollar && foundCount > 1) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "Too many positional (i.e. '$') elements found in path '"
- << _fieldRef.dottedField() << "'");
- }
+ if (positional)
+ *positional = foundDollar;
- //
- // value analysis
- //
-
- if (!modExpr.ok())
- return Status(ErrorCodes::BadValue, "cannot $set an empty value");
-
- _val = modExpr;
- _modOptions = opts;
-
- return Status::OK();
+ if (foundDollar && foundCount > 1) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "Too many positional (i.e. '$') elements found in path '"
+ << _fieldRef.dottedField() << "'");
}
- Status ModifierSet::prepare(mutablebson::Element root,
- StringData matchedField,
- ExecInfo* execInfo) {
-
- _preparedState.reset(new PreparedState(&root.getDocument()));
-
- // If we have a $-positional field, it is time to bind it to an actual field part.
- if (_posDollar) {
- if (matchedField.empty()) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "The positional operator did not find the match "
- "needed from the query. Unexpanded update: "
- << _fieldRef.dottedField());
- }
- _fieldRef.setPart(_posDollar, matchedField);
- }
+ //
+ // value analysis
+ //
- // Locate the field name in 'root'. Note that we may not have all the parts in the path
- // in the doc -- which is fine. Our goal now is merely to reason about whether this mod
- // apply is a noOp or whether is can be in place. The remainin path, if missing, will
- // be created during the apply.
- Status status = pathsupport::findLongestPrefix(_fieldRef,
- root,
- &_preparedState->idxFound,
- &_preparedState->elemFound);
-
- // FindLongestPrefix may say the path does not exist at all, which is fine here, or
- // that the path was not viable or otherwise wrong, in which case, the mod cannot
- // proceed.
- if (status.code() == ErrorCodes::NonExistentPath) {
- _preparedState->elemFound = root.getDocument().end();
- }
- else if (_modOptions.fromReplication && status.code() == ErrorCodes::PathNotViable) {
- // If we are coming from replication and it is an invalid path,
- // then push on indicating that we had a blocking element, which we stopped at
- _preparedState->elemIsBlocking = true;
- }
- else if (!status.isOK()) {
- return status;
- }
+ if (!modExpr.ok())
+ return Status(ErrorCodes::BadValue, "cannot $set an empty value");
- if (_setMode == SET_ON_INSERT) {
- execInfo->context = ModifierInterface::ExecInfo::INSERT_CONTEXT;
- }
+ _val = modExpr;
+ _modOptions = opts;
- // We register interest in the field name. The driver needs this info to sort out if
- // there is any conflict among mods.
- execInfo->fieldRef[0] = &_fieldRef;
+ return Status::OK();
+}
- //
- // in-place and no-op logic
- //
+Status ModifierSet::prepare(mutablebson::Element root,
+ StringData matchedField,
+ ExecInfo* execInfo) {
+ _preparedState.reset(new PreparedState(&root.getDocument()));
- // If the field path is not fully present, then this mod cannot be in place, nor is a
- // noOp.
- if (!_preparedState->elemFound.ok() ||
- _preparedState->idxFound < (_fieldRef.numParts()-1)) {
- return Status::OK();
+ // If we have a $-positional field, it is time to bind it to an actual field part.
+ if (_posDollar) {
+ if (matchedField.empty()) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "The positional operator did not find the match "
+ "needed from the query. Unexpanded update: "
+ << _fieldRef.dottedField());
}
+ _fieldRef.setPart(_posDollar, matchedField);
+ }
- // If the value being $set is the same as the one already in the doc, than this is a
- // noOp.
- if (_preparedState->elemFound.ok() &&
- _preparedState->idxFound == (_fieldRef.numParts()-1) &&
- _preparedState->elemFound.compareWithBSONElement(_val, false /*ignore field*/) == 0) {
- execInfo->noOp = _preparedState->noOp = true;
- }
+ // Locate the field name in 'root'. Note that we may not have all the parts in the path
+ // in the doc -- which is fine. Our goal now is merely to reason about whether this mod
+ // apply is a noOp or whether is can be in place. The remainin path, if missing, will
+ // be created during the apply.
+ Status status = pathsupport::findLongestPrefix(
+ _fieldRef, root, &_preparedState->idxFound, &_preparedState->elemFound);
+
+ // FindLongestPrefix may say the path does not exist at all, which is fine here, or
+ // that the path was not viable or otherwise wrong, in which case, the mod cannot
+ // proceed.
+ if (status.code() == ErrorCodes::NonExistentPath) {
+ _preparedState->elemFound = root.getDocument().end();
+ } else if (_modOptions.fromReplication && status.code() == ErrorCodes::PathNotViable) {
+ // If we are coming from replication and it is an invalid path,
+ // then push on indicating that we had a blocking element, which we stopped at
+ _preparedState->elemIsBlocking = true;
+ } else if (!status.isOK()) {
+ return status;
+ }
- return Status::OK();
+ if (_setMode == SET_ON_INSERT) {
+ execInfo->context = ModifierInterface::ExecInfo::INSERT_CONTEXT;
}
- Status ModifierSet::apply() const {
- dassert(!_preparedState->noOp);
+ // We register interest in the field name. The driver needs this info to sort out if
+ // there is any conflict among mods.
+ execInfo->fieldRef[0] = &_fieldRef;
- const bool destExists = _preparedState->elemFound.ok() &&
- _preparedState->idxFound == (_fieldRef.numParts()-1);
- // If there's no need to create any further field part, the $set is simply a value
- // assignment.
- if (destExists) {
- return _preparedState->elemFound.setValueBSONElement(_val);
- }
+ //
+ // in-place and no-op logic
+ //
- //
- // Complete document path logic
- //
+ // If the field path is not fully present, then this mod cannot be in place, nor is a
+ // noOp.
+ if (!_preparedState->elemFound.ok() || _preparedState->idxFound < (_fieldRef.numParts() - 1)) {
+ return Status::OK();
+ }
- // Creates the final element that's going to be $set in 'doc'.
- mutablebson::Document& doc = _preparedState->doc;
- StringData lastPart = _fieldRef.getPart(_fieldRef.numParts()-1);
- mutablebson::Element elemToSet = doc.makeElementWithNewFieldName(lastPart, _val);
- if (!elemToSet.ok()) {
- return Status(ErrorCodes::InternalError, "can't create new element");
- }
+ // If the value being $set is the same as the one already in the doc, than this is a
+ // noOp.
+ if (_preparedState->elemFound.ok() && _preparedState->idxFound == (_fieldRef.numParts() - 1) &&
+ _preparedState->elemFound.compareWithBSONElement(_val, false /*ignore field*/) == 0) {
+ execInfo->noOp = _preparedState->noOp = true;
+ }
- // Now, we can be in two cases here, as far as attaching the element being set goes:
- // (a) none of the parts in the element's path exist, or (b) some parts of the path
- // exist but not all.
- if (!_preparedState->elemFound.ok()) {
- _preparedState->elemFound = doc.root();
- _preparedState->idxFound = 0;
- }
- else {
- _preparedState->idxFound++;
- }
+ return Status::OK();
+}
- // Remove the blocking element, if we are from replication applier. See comment below.
- if (_modOptions.fromReplication && !destExists && _preparedState->elemFound.ok() &&
- _preparedState->elemIsBlocking &&
- (!(_preparedState->elemFound.isType(Array)) ||
- !(_preparedState->elemFound.isType(Object)))
- ) {
-
- /**
- * With replication we want to be able to remove blocking elements for $set (only).
- * The reason they are blocking elements is that they are not embedded documents
- * (objects) nor an array (a special type of an embedded doc) and we cannot
- * add children to them (because the $set path requires adding children past
- * the blocking element).
- *
- * Imagine that we started with this:
- * {_id:1, a:1} + {$set : {"a.b.c" : 1}} -> {_id:1, a: {b: {c:1}}}
- * Above we found that element (a:1) is blocking at position 1. We now will replace
- * it with an empty object so the normal logic below can be
- * applied from the root (in this example case).
- *
- * Here is an array example:
- * {_id:1, a:[1, 2]} + {$set : {"a.0.c" : 1}} -> {_id:1, a: [ {c:1}, 2]}
- * The blocking element is "a.0" since it is a number, non-object, and we must
- * then replace it with an empty object so we can add c:1 to that empty object
- */
-
- mutablebson::Element blockingElem = _preparedState->elemFound;
- BSONObj newObj;
- // Replace blocking non-object with an empty object
- Status status = blockingElem.setValueObject(newObj);
- if (!status.isOK()) {
- return status;
- }
- }
+Status ModifierSet::apply() const {
+ dassert(!_preparedState->noOp);
- // createPathAt() will complete the path and attach 'elemToSet' at the end of it.
- return pathsupport::createPathAt(_fieldRef,
- _preparedState->idxFound,
- _preparedState->elemFound,
- elemToSet);
+ const bool destExists =
+ _preparedState->elemFound.ok() && _preparedState->idxFound == (_fieldRef.numParts() - 1);
+ // If there's no need to create any further field part, the $set is simply a value
+ // assignment.
+ if (destExists) {
+ return _preparedState->elemFound.setValueBSONElement(_val);
}
- Status ModifierSet::log(LogBuilder* logBuilder) const {
+ //
+ // Complete document path logic
+ //
- // We'd like to create an entry such as {$set: {<fieldname>: <value>}} under 'logRoot'.
- // We start by creating the {$set: ...} Element.
- mutablebson::Document& doc = logBuilder->getDocument();
+ // Creates the final element that's going to be $set in 'doc'.
+ mutablebson::Document& doc = _preparedState->doc;
+ StringData lastPart = _fieldRef.getPart(_fieldRef.numParts() - 1);
+ mutablebson::Element elemToSet = doc.makeElementWithNewFieldName(lastPart, _val);
+ if (!elemToSet.ok()) {
+ return Status(ErrorCodes::InternalError, "can't create new element");
+ }
- // Create the {<fieldname>: <value>} Element. Note that we log the mod with a
- // dotted field, if it was applied over a dotted field. The rationale is that the
- // secondary may be in a different state than the primary and thus make different
- // decisions about creating the intermediate path in _fieldRef or not.
- mutablebson::Element logElement = doc.makeElementWithNewFieldName(
- _fieldRef.dottedField(), _val);
+ // Now, we can be in two cases here, as far as attaching the element being set goes:
+ // (a) none of the parts in the element's path exist, or (b) some parts of the path
+ // exist but not all.
+ if (!_preparedState->elemFound.ok()) {
+ _preparedState->elemFound = doc.root();
+ _preparedState->idxFound = 0;
+ } else {
+ _preparedState->idxFound++;
+ }
- if (!logElement.ok()) {
- return Status(ErrorCodes::InternalError, "cannot create details for $set mod");
+ // Remove the blocking element, if we are from replication applier. See comment below.
+ if (_modOptions.fromReplication && !destExists && _preparedState->elemFound.ok() &&
+ _preparedState->elemIsBlocking && (!(_preparedState->elemFound.isType(Array)) ||
+ !(_preparedState->elemFound.isType(Object)))) {
+ /**
+ * With replication we want to be able to remove blocking elements for $set (only).
+ * The reason they are blocking elements is that they are not embedded documents
+ * (objects) nor an array (a special type of an embedded doc) and we cannot
+ * add children to them (because the $set path requires adding children past
+ * the blocking element).
+ *
+ * Imagine that we started with this:
+ * {_id:1, a:1} + {$set : {"a.b.c" : 1}} -> {_id:1, a: {b: {c:1}}}
+ * Above we found that element (a:1) is blocking at position 1. We now will replace
+ * it with an empty object so the normal logic below can be
+ * applied from the root (in this example case).
+ *
+ * Here is an array example:
+ * {_id:1, a:[1, 2]} + {$set : {"a.0.c" : 1}} -> {_id:1, a: [ {c:1}, 2]}
+ * The blocking element is "a.0" since it is a number, non-object, and we must
+ * then replace it with an empty object so we can add c:1 to that empty object
+ */
+
+ mutablebson::Element blockingElem = _preparedState->elemFound;
+ BSONObj newObj;
+ // Replace blocking non-object with an empty object
+ Status status = blockingElem.setValueObject(newObj);
+ if (!status.isOK()) {
+ return status;
}
+ }
- return logBuilder->addToSets(logElement);
+ // createPathAt() will complete the path and attach 'elemToSet' at the end of it.
+ return pathsupport::createPathAt(
+ _fieldRef, _preparedState->idxFound, _preparedState->elemFound, elemToSet);
+}
+
+Status ModifierSet::log(LogBuilder* logBuilder) const {
+ // We'd like to create an entry such as {$set: {<fieldname>: <value>}} under 'logRoot'.
+ // We start by creating the {$set: ...} Element.
+ mutablebson::Document& doc = logBuilder->getDocument();
+
+ // Create the {<fieldname>: <value>} Element. Note that we log the mod with a
+ // dotted field, if it was applied over a dotted field. The rationale is that the
+ // secondary may be in a different state than the primary and thus make different
+ // decisions about creating the intermediate path in _fieldRef or not.
+ mutablebson::Element logElement =
+ doc.makeElementWithNewFieldName(_fieldRef.dottedField(), _val);
+
+ if (!logElement.ok()) {
+ return Status(ErrorCodes::InternalError, "cannot create details for $set mod");
}
-} // namespace mongo
+ return logBuilder->addToSets(logElement);
+}
+
+} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_set.h b/src/mongo/db/ops/modifier_set.h
index cc961163234..84d6f0ba8cf 100644
--- a/src/mongo/db/ops/modifier_set.h
+++ b/src/mongo/db/ops/modifier_set.h
@@ -38,77 +38,71 @@
namespace mongo {
- class LogBuilder;
-
- class ModifierSet : public ModifierInterface {
- MONGO_DISALLOW_COPYING(ModifierSet);
-
- public:
-
- enum ModifierSetMode { SET_NORMAL, SET_ON_INSERT };
- explicit ModifierSet(ModifierSetMode mode = SET_NORMAL);
-
- //
- // Modifier interface implementation
- //
-
- virtual ~ModifierSet();
-
- /**
- * A 'modExpr' is a BSONElement {<fieldname>: <value>} coming from a $set mod such as
- * {$set: {<fieldname: <value>}}. init() extracts the field name and the value to be
- * assigned to it from 'modExpr'. It returns OK if successful or a status describing
- * the error.
- */
- virtual Status init(const BSONElement& modExpr, const Options& opts,
- bool* positional = NULL);
-
- /**
- * Looks up the field name in the sub-tree rooted at 'root', and binds, if necessary,
- * the '$' field part using the 'matchedfield' number. prepare() returns OK and
- * fills in 'execInfo' with information of whether this mod is a no-op on 'root' and
- * whether it is an in-place candidate. Otherwise, returns a status describing the
- * error.
- */
- virtual Status prepare(mutablebson::Element root,
- StringData matchedField,
- ExecInfo* execInfo);
-
- /**
- * Applies the prepared mod over the element 'root' specified in the prepare()
- * call. Returns OK if successful or a status describing the error.
- */
- virtual Status apply() const;
-
- /**
- * Adds a log entry to logRoot corresponding to the operation applied here. Returns OK
- * if successful or a status describing the error.
- */
- virtual Status log(LogBuilder* logBuilder) const;
-
- private:
-
- // Access to each component of fieldName that's the target of this mod.
- FieldRef _fieldRef;
-
- // 0 or index for $-positional in _fieldRef.
- size_t _posDollar;
-
- // If on 'on insert' mode, We'd like to apply this mod only if we're in a upsert.
- const ModifierSetMode _setMode;
-
- // Element of the $set expression.
- BSONElement _val;
-
- // See the class comments in modifier_interface.h
- ModifierInterface::Options _modOptions;
-
- // The instance of the field in the provided doc. This state is valid after a
- // prepare() was issued and until a log() is issued. The document this mod is
- // being prepared against must be live throughout all the calls.
- struct PreparedState;
- std::unique_ptr<PreparedState> _preparedState;
-
- };
-
-} // namespace mongo
+class LogBuilder;
+
+class ModifierSet : public ModifierInterface {
+ MONGO_DISALLOW_COPYING(ModifierSet);
+
+public:
+ enum ModifierSetMode { SET_NORMAL, SET_ON_INSERT };
+ explicit ModifierSet(ModifierSetMode mode = SET_NORMAL);
+
+ //
+ // Modifier interface implementation
+ //
+
+ virtual ~ModifierSet();
+
+ /**
+ * A 'modExpr' is a BSONElement {<fieldname>: <value>} coming from a $set mod such as
+ * {$set: {<fieldname: <value>}}. init() extracts the field name and the value to be
+ * assigned to it from 'modExpr'. It returns OK if successful or a status describing
+ * the error.
+ */
+ virtual Status init(const BSONElement& modExpr, const Options& opts, bool* positional = NULL);
+
+ /**
+ * Looks up the field name in the sub-tree rooted at 'root', and binds, if necessary,
+ * the '$' field part using the 'matchedfield' number. prepare() returns OK and
+ * fills in 'execInfo' with information of whether this mod is a no-op on 'root' and
+ * whether it is an in-place candidate. Otherwise, returns a status describing the
+ * error.
+ */
+ virtual Status prepare(mutablebson::Element root, StringData matchedField, ExecInfo* execInfo);
+
+ /**
+ * Applies the prepared mod over the element 'root' specified in the prepare()
+ * call. Returns OK if successful or a status describing the error.
+ */
+ virtual Status apply() const;
+
+ /**
+ * Adds a log entry to logRoot corresponding to the operation applied here. Returns OK
+ * if successful or a status describing the error.
+ */
+ virtual Status log(LogBuilder* logBuilder) const;
+
+private:
+ // Access to each component of fieldName that's the target of this mod.
+ FieldRef _fieldRef;
+
+ // 0 or index for $-positional in _fieldRef.
+ size_t _posDollar;
+
+ // If on 'on insert' mode, We'd like to apply this mod only if we're in a upsert.
+ const ModifierSetMode _setMode;
+
+ // Element of the $set expression.
+ BSONElement _val;
+
+ // See the class comments in modifier_interface.h
+ ModifierInterface::Options _modOptions;
+
+ // The instance of the field in the provided doc. This state is valid after a
+ // prepare() was issued and until a log() is issued. The document this mod is
+ // being prepared against must be live throughout all the calls.
+ struct PreparedState;
+ std::unique_ptr<PreparedState> _preparedState;
+};
+
+} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_set_test.cpp b/src/mongo/db/ops/modifier_set_test.cpp
index 1a7e1db5a68..315da4e113d 100644
--- a/src/mongo/db/ops/modifier_set_test.cpp
+++ b/src/mongo/db/ops/modifier_set_test.cpp
@@ -42,715 +42,715 @@
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
- //
+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;
- TEST(SimpleMod, PrepareNoOp) {
- Document doc(fromjson("{a: 2}"));
- Mod setMod(fromjson("{$set: {a: 2}}"));
+/** Helper to build and manipulate a $set mod. */
+class Mod {
+public:
+ Mod() : _mod() {}
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_TRUE(execInfo.noOp);
+ 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()));
}
- 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);
+ Status prepare(Element root, StringData matchedField, ModifierInterface::ExecInfo* execInfo) {
+ return _mod.prepare(root, matchedField, execInfo);
}
- 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);
+ Status apply() const {
+ return _mod.apply();
}
- 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);
+ Status log(LogBuilder* logBuilder) const {
+ return _mod.log(logBuilder);
}
- 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);
+ ModifierSet& mod() {
+ return _mod;
}
- TEST(SimpleMod, PrepareApplyChangeType) {
- Document doc(fromjson("{a: 'str'}"));
- Mod setMod(fromjson("{$set: {a: 2}}"));
+private:
+ ModifierSet _mod;
+ BSONObj _modObj;
+};
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+//
+// Init tests
+//
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
+TEST(Init, EmptyOperation) {
+ BSONObj modObj = fromjson("{$set: {}}");
+ ModifierSet mod;
+ ASSERT_NOT_OK(mod.init(modObj["$set"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
+}
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: 2}"), doc);
- }
+//
+// Simple Mods
+//
- TEST(SimpleMod, PrepareApplyNewPath) {
- Document doc(fromjson("{b: 1}"));
- Mod setMod(fromjson("{$set: {a: 2}}"));
+TEST(SimpleMod, PrepareNoOp) {
+ Document doc(fromjson("{a: 2}"));
+ Mod setMod(fromjson("{$set: {a: 2}}"));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_TRUE(execInfo.noOp);
+}
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{b: 1, a: 2}"), doc);
- }
+TEST(SimpleMod, PrepareSetOnInsert) {
+ Document doc(fromjson("{a: 1}"));
+ Mod setMod(fromjson("{$setOnInsert: {a: 2}}"));
- TEST(SimpleMod, LogNormal) {
- BSONObj obj = fromjson("{a: 1}");
- Mod setMod(fromjson("{$set: {a: 2}}"));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
- Document doc(obj);
- ModifierInterface::ExecInfo dummy;
- ASSERT_OK(setMod.prepare(doc.root(), "", &dummy));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
+ ASSERT_EQUALS(execInfo.context, ModifierInterface::ExecInfo::INSERT_CONTEXT);
+}
- 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));
+TEST(SimpleMod, PrepareApplyEmptyDocument) {
+ Document doc(fromjson("{}"));
+ Mod setMod(fromjson("{$set: {a: 2}}"));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
- ASSERT_TRUE(execInfo.noOp);
- }
-
- TEST(DottedMod, PreparePathNotViable) {
- Document doc(fromjson("{a:1}"));
- Mod setMod(fromjson("{$set: {'a.b': 2}}"));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(setMod.prepare(doc.root(), "", &execInfo));
- }
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
- TEST(DottedMod, PreparePathNotViableArrray) {
- Document doc(fromjson("{a:[{b:1}]}"));
- Mod setMod(fromjson("{$set: {'a.b': 2}}"));
+ ASSERT_OK(setMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: 2}"), doc);
+}
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(setMod.prepare(doc.root(), "", &execInfo));
- }
+TEST(SimpleMod, PrepareApplyInPlace) {
+ Document doc(fromjson("{a: 1}"));
+ Mod setMod(fromjson("{$set: {a: 2}}"));
- 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));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
- 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);
- }
+ ASSERT_OK(setMod.apply());
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: 2}"), doc);
+}
- TEST(DottedMod, PrepareApplyChangePath) {
- Document doc(fromjson("{a: {b: {c: 1}}}"));
- Mod setMod(fromjson("{$set: {'a.b': 2}}"));
+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));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
- ASSERT_FALSE(execInfo.noOp);
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc);
- }
+ ASSERT_OK(setMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: 2}"), doc);
+}
- TEST(DottedMod, PrepareApplyExtendPath) {
- Document doc(fromjson("{a: {c: 1}}"));
- Mod setMod(fromjson("{$set: {'a.b': 2}}"));
+TEST(SimpleMod, PrepareApplyChangeType) {
+ Document doc(fromjson("{a: 'str'}"));
+ Mod setMod(fromjson("{$set: {a: 2}}"));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
- ASSERT_FALSE(execInfo.noOp);
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: {c: 1, b: 2}}"), doc);
- }
+ ASSERT_OK(setMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: 2}"), doc);
+}
- TEST(DottedMod, PrepareApplyNewPath) {
- Document doc(fromjson("{c: 1}"));
- Mod setMod(fromjson("{$set: {'a.b': 2}}"));
+TEST(SimpleMod, PrepareApplyNewPath) {
+ Document doc(fromjson("{b: 1}"));
+ Mod setMod(fromjson("{$set: {a: 2}}"));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
- ASSERT_FALSE(execInfo.noOp);
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{c: 1, a: {b: 2}}"), doc);
- }
+ ASSERT_OK(setMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{b: 1, a: 2}"), doc);
+}
- TEST(DottedMod, PrepareApplyEmptyDoc) {
- Document doc(fromjson("{}"));
- Mod setMod(fromjson("{$set: {'a.b': 2}}"));
+TEST(SimpleMod, LogNormal) {
+ BSONObj obj = fromjson("{a: 1}");
+ Mod setMod(fromjson("{$set: {a: 2}}"));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+ Document doc(obj);
+ ModifierInterface::ExecInfo dummy;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &dummy));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
- ASSERT_FALSE(execInfo.noOp);
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(setMod.log(&logBuilder));
+ ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
+ ASSERT_EQUALS(fromjson("{$set: {a: 2}}"), logDoc);
+}
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc);
- }
+//
+// Simple dotted mod
+//
- TEST(DottedMod, PrepareApplyFieldWithDot) {
- Document doc(fromjson("{'a.b':4}"));
- Mod setMod(fromjson("{$set: {'a.b': 2}}"));
+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));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
- ASSERT_FALSE(execInfo.noOp);
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
+ ASSERT_TRUE(execInfo.noOp);
+}
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{'a.b':4, a: {b: 2}}"), doc);
- }
+TEST(DottedMod, PreparePathNotViable) {
+ Document doc(fromjson("{a:1}"));
+ Mod setMod(fromjson("{$set: {'a.b': 2}}"));
- //
- // Indexed mod
- //
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_NOT_OK(setMod.prepare(doc.root(), "", &execInfo));
+}
- TEST(IndexedMod, PrepareNoOp) {
- Document doc(fromjson("{a: [{b: 0},{b: 1},{b: 2}]}"));
- Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
+TEST(DottedMod, PreparePathNotViableArrray) {
+ Document doc(fromjson("{a:[{b:1}]}"));
+ Mod setMod(fromjson("{$set: {'a.b': 2}}"));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_NOT_OK(setMod.prepare(doc.root(), "", &execInfo));
+}
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
- ASSERT_TRUE(execInfo.noOp);
- }
+TEST(DottedMod, PrepareApplyInPlace) {
+ Document doc(fromjson("{a: {b: 1}}"));
+ Mod setMod(fromjson("{$set: {'a.b': 2}}"));
- TEST(IndexedMod, PrepareNonViablePath) {
- Document doc(fromjson("{a: 0}"));
- Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(setMod.prepare(doc.root(), "", &execInfo));
- }
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
+ ASSERT_FALSE(execInfo.noOp);
- TEST(IndexedMod, PrepareApplyInPlace) {
- Document doc(fromjson("{a: [{b: 0},{b: 1},{b: 1}]}"));
- Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
+ ASSERT_OK(setMod.apply());
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc);
+}
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+TEST(DottedMod, PrepareApplyChangeType) {
+ Document doc(fromjson("{a: {b: 'str'}}"));
+ Mod setMod(fromjson("{$set: {'a.b': 2}}"));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
- ASSERT_FALSE(execInfo.noOp);
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
- ASSERT_OK(setMod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [{b: 0},{b: 1},{b: 2}]}"), doc);
- }
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
+ ASSERT_FALSE(execInfo.noOp);
- TEST(IndexedMod, PrepareApplyNormalArray) {
- Document doc(fromjson("{a: [{b: 0},{b: 1}]}"));
- Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
+ ASSERT_OK(setMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc);
+}
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+TEST(DottedMod, PrepareApplyChangePath) {
+ Document doc(fromjson("{a: {b: {c: 1}}}"));
+ Mod setMod(fromjson("{$set: {'a.b': 2}}"));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
- ASSERT_FALSE(execInfo.noOp);
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [{b: 0},{b: 1},{b: 2}]}"), doc);
- }
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
+ ASSERT_FALSE(execInfo.noOp);
- TEST(IndexedMod, PrepareApplyPaddingArray) {
- Document doc(fromjson("{a: [{b: 0}]}"));
- Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
+ ASSERT_OK(setMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc);
+}
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+TEST(DottedMod, PrepareApplyExtendPath) {
+ Document doc(fromjson("{a: {c: 1}}"));
+ Mod setMod(fromjson("{$set: {'a.b': 2}}"));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
- ASSERT_FALSE(execInfo.noOp);
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [{b: 0},null,{b: 2}]}"), doc);
- }
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
+ ASSERT_FALSE(execInfo.noOp);
- TEST(IndexedMod, PrepareApplyNumericObject) {
- Document doc(fromjson("{a: {b: 0}}"));
- Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
+ ASSERT_OK(setMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: {c: 1, b: 2}}"), doc);
+}
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+TEST(DottedMod, PrepareApplyNewPath) {
+ Document doc(fromjson("{c: 1}"));
+ Mod setMod(fromjson("{$set: {'a.b': 2}}"));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
- ASSERT_FALSE(execInfo.noOp);
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: {b: 0, '2': {b: 2}}}"), doc);
- }
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
+ ASSERT_FALSE(execInfo.noOp);
- TEST(IndexedMod, PrepareApplyNumericField) {
- Document doc(fromjson("{a: {'2': {b: 1}}}"));
- Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
+ ASSERT_OK(setMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{c: 1, a: {b: 2}}"), doc);
+}
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+TEST(DottedMod, PrepareApplyEmptyDoc) {
+ Document doc(fromjson("{}"));
+ Mod setMod(fromjson("{$set: {'a.b': 2}}"));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
- ASSERT_FALSE(execInfo.noOp);
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
- ASSERT_OK(setMod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: {'2': {b: 2}}}"), doc);
- }
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
+ ASSERT_FALSE(execInfo.noOp);
- TEST(IndexedMod, PrepareApplyExtendNumericField) {
- Document doc(fromjson("{a: {'2': {c: 1}}}"));
- Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
+ ASSERT_OK(setMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc);
+}
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+TEST(DottedMod, PrepareApplyFieldWithDot) {
+ Document doc(fromjson("{'a.b':4}"));
+ Mod setMod(fromjson("{$set: {'a.b': 2}}"));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
- ASSERT_FALSE(execInfo.noOp);
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: {'2': {c: 1, b: 2}}}"), doc);
- }
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
+ ASSERT_FALSE(execInfo.noOp);
- TEST(IndexedMod, PrepareApplyEmptyObject) {
- Document doc(fromjson("{a: {}}"));
- Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
+ ASSERT_OK(setMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{'a.b':4, a: {b: 2}}"), doc);
+}
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+//
+// Indexed mod
+//
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
- ASSERT_FALSE(execInfo.noOp);
+TEST(IndexedMod, PrepareNoOp) {
+ Document doc(fromjson("{a: [{b: 0},{b: 1},{b: 2}]}"));
+ Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: {'2': {b: 2}}}"), doc);
- }
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
- TEST(IndexedMod, PrepareApplyEmptyArray) {
- Document doc(fromjson("{a: []}"));
- Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
+ ASSERT_TRUE(execInfo.noOp);
+}
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+TEST(IndexedMod, PrepareNonViablePath) {
+ Document doc(fromjson("{a: 0}"));
+ Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
- ASSERT_FALSE(execInfo.noOp);
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_NOT_OK(setMod.prepare(doc.root(), "", &execInfo));
+}
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [null, null, {b: 2}]}"), doc);
- }
+TEST(IndexedMod, PrepareApplyInPlace) {
+ Document doc(fromjson("{a: [{b: 0},{b: 1},{b: 1}]}"));
+ Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
- TEST(IndexedMod, PrepareApplyInexistent) {
- Document doc(fromjson("{}"));
- Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
+ ASSERT_FALSE(execInfo.noOp);
- 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);
+}
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: {'2': {b: 2}}}"), doc);
- }
+TEST(IndexedMod, PrepareApplyNormalArray) {
+ Document doc(fromjson("{a: [{b: 0},{b: 1}]}"));
+ Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
- 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));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
+ ASSERT_FALSE(execInfo.noOp);
- 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);
- }
+ ASSERT_OK(setMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: [{b: 0},{b: 1},{b: 2}]}"), doc);
+}
- TEST(IndexedMod, LogEmptyArray) {
- BSONObj obj = fromjson("{a: []}");
- Document doc(obj);
- Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
+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));
+ 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);
- }
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
+ ASSERT_FALSE(execInfo.noOp);
- TEST(IndexedMod, LogEmptyObject) {
- BSONObj obj = fromjson("{a: []}");
- Document doc(obj);
- Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
+ ASSERT_OK(setMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: [{b: 0},null,{b: 2}]}"), doc);
+}
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+TEST(IndexedMod, PrepareApplyNumericObject) {
+ Document doc(fromjson("{a: {b: 0}}"));
+ Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
- 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);
- }
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
- //
- // Indexed complex mod
- //
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
+ ASSERT_FALSE(execInfo.noOp);
- 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}}}"));
+ ASSERT_OK(setMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: {b: 0, '2': {b: 2}}}"), doc);
+}
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+TEST(IndexedMod, PrepareApplyNumericField) {
+ Document doc(fromjson("{a: {'2': {b: 1}}}"));
+ Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.b");
- ASSERT_TRUE(execInfo.noOp);
- }
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
- 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}}}"));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
+ ASSERT_FALSE(execInfo.noOp);
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_OK(setMod.apply());
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: {'2': {b: 2}}}"), doc);
+}
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.b");
- ASSERT_FALSE(execInfo.noOp);
- }
+TEST(IndexedMod, PrepareApplyExtendNumericField) {
+ Document doc(fromjson("{a: {'2': {c: 1}}}"));
+ Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
- //
- // 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_OK(setMod.prepare(doc.root(), "", &execInfo));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(setMod.prepare(doc.root(), "", &execInfo));
- }
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
+ ASSERT_FALSE(execInfo.noOp);
- TEST(NonViablePathWithRepl, SingleField) {
- Document doc(fromjson("{_id:1, a: 1}"));
- Mod setMod(fromjson("{$set: {'a.1.b': 1}}"), true);
+ ASSERT_OK(setMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: {'2': {c: 1, b: 2}}}"), doc);
+}
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+TEST(IndexedMod, PrepareApplyEmptyObject) {
+ Document doc(fromjson("{a: {}}"));
+ Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.b");
- ASSERT_FALSE(execInfo.noOp);
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{_id:1, a: {'1': {b: 1}}}"), doc);
- }
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
+ ASSERT_FALSE(execInfo.noOp);
- TEST(NonViablePathWithRepl, SingleFieldNoId) {
- Document doc(fromjson("{a: 1}"));
- Mod setMod(fromjson("{$set: {'a.1.b': 1}}"), true);
+ ASSERT_OK(setMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: {'2': {b: 2}}}"), doc);
+}
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+TEST(IndexedMod, PrepareApplyEmptyArray) {
+ Document doc(fromjson("{a: []}"));
+ Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.b");
- ASSERT_FALSE(execInfo.noOp);
+ 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: {'1': {b: 1}}}"), doc);
- }
+ 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}}"));
- 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));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
+ ASSERT_FALSE(execInfo.noOp);
- 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: {'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}}"));
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{_id:1, a: {a: {'1': {b: 1}}}}"), doc);
- }
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
- TEST(NonViablePathWithRepl, DoubleNestedField) {
- Document doc(fromjson("{_id:1, a: {b: {c: 1}}}"));
- Mod setMod(fromjson("{$set: {'a.b.c.d': 2}}"), true);
+ 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));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b.c.d");
- ASSERT_FALSE(execInfo.noOp);
+ 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}}"));
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{_id:1, a: {b: {c: {d: 2}}}}"), doc);
- }
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
- TEST(NonViablePathWithRepl, NestedFieldNoId) {
- Document doc(fromjson("{a: {a: 1}}"));
- Mod setMod(fromjson("{$set: {'a.a.1.b': 1}}"), true);
+ 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
+//
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+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}}}"));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.a.1.b");
- ASSERT_FALSE(execInfo.noOp);
+ 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}}}"));
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: {a: {'1': {b: 1}}}}"), doc);
- }
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
- TEST(NonViablePathWithRepl, ReplayArrayFieldNotAppendedItermediate) {
- Document doc(fromjson("{_id: 0, a: [1, {b: [1]}]}"));
- Mod setMod(fromjson("{$set: {'a.0.b': [0,2]}}}"), true);
+ 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));
- 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.0.b");
- ASSERT_FALSE(execInfo.noOp);
+ 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);
+}
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{_id: 0, a: [{b: [0,2]}, {b: [1]}]}"), doc);
- }
+TEST(NonViablePathWithRepl, NestedFieldNoId) {
+ Document doc(fromjson("{a: {a: 1}}"));
+ Mod setMod(fromjson("{$set: {'a.a.1.b': 1}}"), true);
- // 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));
- 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_EQUALS(execInfo.fieldRef[0]->dottedField(), "r.a");
- 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_OK(setMod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{_id: 1, r: {a:2, b:2}}"), doc);
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0.b");
+ ASSERT_FALSE(execInfo.noOp);
- 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);
- }
+ 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}}"));
- // 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));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "r.a");
+ ASSERT_FALSE(execInfo.noOp);
- 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);
- 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);
+}
- 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);
- 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);
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
- Mod setMod(fromjson("{ $set: { x: { a: 100, b: 2 }}}"), true);
+ 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);
+}
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
+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);
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "x");
- ASSERT_FALSE(execInfo.noOp);
+ Mod setMod(fromjson("{ $set: { x: { a: 100, b: 2 }}}"), true);
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ x : { a : 100, b : 2 } }"), doc);
- }
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "x");
+ ASSERT_FALSE(execInfo.noOp);
-} // unnamed namespace
+ ASSERT_OK(setMod.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{ x : { a : 100, b : 2 } }"), doc);
+}
+
+} // unnamed namespace
diff --git a/src/mongo/db/ops/modifier_table.cpp b/src/mongo/db/ops/modifier_table.cpp
index f35e79cf5c5..81bc612df48 100644
--- a/src/mongo/db/ops/modifier_table.cpp
+++ b/src/mongo/db/ops/modifier_table.cpp
@@ -49,96 +49,93 @@
namespace mongo {
- using std::make_pair;
- using std::string;
+using std::make_pair;
+using std::string;
namespace modifiertable {
- namespace {
+namespace {
- struct ModifierEntry {
- string name;
- ModifierType type;
+struct ModifierEntry {
+ string name;
+ ModifierType type;
- ModifierEntry(StringData name, ModifierType type)
- : name(name.toString())
- , type(type) {
- }
- };
+ ModifierEntry(StringData name, ModifierType type) : name(name.toString()), type(type) {}
+};
- typedef unordered_map<StringData, ModifierEntry*, StringData::Hasher> NameMap;
+typedef unordered_map<StringData, ModifierEntry*, StringData::Hasher> NameMap;
- NameMap* MODIFIER_NAME_MAP;
+NameMap* MODIFIER_NAME_MAP;
- void init(NameMap* nameMap) {
- ModifierEntry* entryAddToSet = new ModifierEntry("$addToSet", MOD_ADD_TO_SET);
- nameMap->insert(make_pair(StringData(entryAddToSet->name), entryAddToSet));
+void init(NameMap* nameMap) {
+ ModifierEntry* entryAddToSet = new ModifierEntry("$addToSet", MOD_ADD_TO_SET);
+ nameMap->insert(make_pair(StringData(entryAddToSet->name), entryAddToSet));
- ModifierEntry* entryBit = new ModifierEntry("$bit", MOD_BIT);
- nameMap->insert(make_pair(StringData(entryBit->name), entryBit));
+ ModifierEntry* entryBit = new ModifierEntry("$bit", MOD_BIT);
+ nameMap->insert(make_pair(StringData(entryBit->name), entryBit));
- ModifierEntry* entryCurrentDate = new ModifierEntry("$currentDate", MOD_CURRENTDATE);
- nameMap->insert(make_pair(StringData(entryCurrentDate->name), entryCurrentDate));
+ ModifierEntry* entryCurrentDate = new ModifierEntry("$currentDate", MOD_CURRENTDATE);
+ nameMap->insert(make_pair(StringData(entryCurrentDate->name), entryCurrentDate));
- ModifierEntry* entryInc = new ModifierEntry("$inc", MOD_INC);
- nameMap->insert(make_pair(StringData(entryInc->name), entryInc));
+ ModifierEntry* entryInc = new ModifierEntry("$inc", MOD_INC);
+ nameMap->insert(make_pair(StringData(entryInc->name), entryInc));
- ModifierEntry* entryMax = new ModifierEntry("$max", MOD_MAX);
- nameMap->insert(make_pair(StringData(entryMax->name), entryMax));
+ ModifierEntry* entryMax = new ModifierEntry("$max", MOD_MAX);
+ nameMap->insert(make_pair(StringData(entryMax->name), entryMax));
- ModifierEntry* entryMin = new ModifierEntry("$min", MOD_MIN);
- nameMap->insert(make_pair(StringData(entryMin->name), entryMin));
+ ModifierEntry* entryMin = new ModifierEntry("$min", MOD_MIN);
+ nameMap->insert(make_pair(StringData(entryMin->name), entryMin));
- ModifierEntry* entryMul = new ModifierEntry("$mul", MOD_MUL);
- nameMap->insert(make_pair(StringData(entryMul->name), entryMul));
+ ModifierEntry* entryMul = new ModifierEntry("$mul", MOD_MUL);
+ nameMap->insert(make_pair(StringData(entryMul->name), entryMul));
- ModifierEntry* entryPop = new ModifierEntry("$pop", MOD_POP);
- nameMap->insert(make_pair(StringData(entryPop->name), entryPop));
+ ModifierEntry* entryPop = new ModifierEntry("$pop", MOD_POP);
+ nameMap->insert(make_pair(StringData(entryPop->name), entryPop));
- ModifierEntry* entryPull = new ModifierEntry("$pull", MOD_PULL);
- nameMap->insert(make_pair(StringData(entryPull->name), entryPull));
+ ModifierEntry* entryPull = new ModifierEntry("$pull", MOD_PULL);
+ nameMap->insert(make_pair(StringData(entryPull->name), entryPull));
- ModifierEntry* entryPullAll = new ModifierEntry("$pullAll", MOD_PULL_ALL);
- nameMap->insert(make_pair(StringData(entryPullAll->name), entryPullAll));
+ ModifierEntry* entryPullAll = new ModifierEntry("$pullAll", MOD_PULL_ALL);
+ nameMap->insert(make_pair(StringData(entryPullAll->name), entryPullAll));
- ModifierEntry* entryPush = new ModifierEntry("$push", MOD_PUSH);
- nameMap->insert(make_pair(StringData(entryPush->name), entryPush));
+ ModifierEntry* entryPush = new ModifierEntry("$push", MOD_PUSH);
+ nameMap->insert(make_pair(StringData(entryPush->name), entryPush));
- ModifierEntry* entryPushAll = new ModifierEntry("$pushAll", MOD_PUSH_ALL);
- nameMap->insert(make_pair(StringData(entryPushAll->name), entryPushAll));
+ ModifierEntry* entryPushAll = new ModifierEntry("$pushAll", MOD_PUSH_ALL);
+ nameMap->insert(make_pair(StringData(entryPushAll->name), entryPushAll));
- ModifierEntry* entrySet = new ModifierEntry("$set", MOD_SET);
- nameMap->insert(make_pair(StringData(entrySet->name), entrySet));
+ ModifierEntry* entrySet = new ModifierEntry("$set", MOD_SET);
+ nameMap->insert(make_pair(StringData(entrySet->name), entrySet));
- ModifierEntry* entrySetOnInsert = new ModifierEntry("$setOnInsert", MOD_SET_ON_INSERT);
- nameMap->insert(make_pair(StringData(entrySetOnInsert->name), entrySetOnInsert));
+ ModifierEntry* entrySetOnInsert = new ModifierEntry("$setOnInsert", MOD_SET_ON_INSERT);
+ nameMap->insert(make_pair(StringData(entrySetOnInsert->name), entrySetOnInsert));
- ModifierEntry* entryRename = new ModifierEntry("$rename", MOD_RENAME);
- nameMap->insert(make_pair(StringData(entryRename->name), entryRename));
+ ModifierEntry* entryRename = new ModifierEntry("$rename", MOD_RENAME);
+ nameMap->insert(make_pair(StringData(entryRename->name), entryRename));
- ModifierEntry* entryUnset = new ModifierEntry("$unset", MOD_UNSET);
- nameMap->insert(make_pair(StringData(entryUnset->name), entryUnset));
- }
+ ModifierEntry* entryUnset = new ModifierEntry("$unset", MOD_UNSET);
+ nameMap->insert(make_pair(StringData(entryUnset->name), entryUnset));
+}
- } // unnamed namespace
+} // unnamed namespace
- MONGO_INITIALIZER(ModifierTable)(InitializerContext* context) {
- MODIFIER_NAME_MAP = new NameMap;
- init(MODIFIER_NAME_MAP);
+MONGO_INITIALIZER(ModifierTable)(InitializerContext* context) {
+ MODIFIER_NAME_MAP = new NameMap;
+ init(MODIFIER_NAME_MAP);
- return Status::OK();
- }
+ return Status::OK();
+}
- ModifierType getType(StringData typeStr) {
- NameMap::const_iterator it = MODIFIER_NAME_MAP->find(typeStr);
- if (it == MODIFIER_NAME_MAP->end()) {
- return MOD_UNKNOWN;
- }
- return it->second->type;
+ModifierType getType(StringData typeStr) {
+ NameMap::const_iterator it = MODIFIER_NAME_MAP->find(typeStr);
+ if (it == MODIFIER_NAME_MAP->end()) {
+ return MOD_UNKNOWN;
}
+ return it->second->type;
+}
- ModifierInterface* makeUpdateMod(ModifierType modType) {
- switch (modType) {
+ModifierInterface* makeUpdateMod(ModifierType modType) {
+ switch (modType) {
case MOD_ADD_TO_SET:
return new ModifierAddToSet;
case MOD_BIT:
@@ -173,8 +170,8 @@ namespace modifiertable {
return new ModifierUnset;
default:
return NULL;
- }
}
+}
-} // namespace modifiertable
-} // namespace mongo
+} // namespace modifiertable
+} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_table.h b/src/mongo/db/ops/modifier_table.h
index 8087114e657..acefc3290dc 100644
--- a/src/mongo/db/ops/modifier_table.h
+++ b/src/mongo/db/ops/modifier_table.h
@@ -33,38 +33,38 @@
namespace mongo {
namespace modifiertable {
- // NOTE: Please update jstests/verify_update_mods.js or include a jstest for any new mods
- enum ModifierType {
- MOD_ADD_TO_SET,
- MOD_BIT,
- MOD_CURRENTDATE,
- MOD_INC,
- MOD_MAX,
- MOD_MIN,
- MOD_MUL,
- MOD_POP,
- MOD_PULL,
- MOD_PULL_ALL,
- MOD_PUSH,
- MOD_PUSH_ALL,
- MOD_SET,
- MOD_SET_ON_INSERT,
- MOD_RENAME,
- MOD_UNSET,
- MOD_UNKNOWN
- };
+// NOTE: Please update jstests/verify_update_mods.js or include a jstest for any new mods
+enum ModifierType {
+ MOD_ADD_TO_SET,
+ MOD_BIT,
+ MOD_CURRENTDATE,
+ MOD_INC,
+ MOD_MAX,
+ MOD_MIN,
+ MOD_MUL,
+ MOD_POP,
+ MOD_PULL,
+ MOD_PULL_ALL,
+ MOD_PUSH,
+ MOD_PUSH_ALL,
+ MOD_SET,
+ MOD_SET_ON_INSERT,
+ MOD_RENAME,
+ MOD_UNSET,
+ MOD_UNKNOWN
+};
- /**
- * Returns the modifier type for 'typeStr', if it was recognized as an existing update
- * mod, or MOD_UNKNOWN otherwise.
- */
- ModifierType getType(StringData typeStr);
+/**
+ * Returns the modifier type for 'typeStr', if it was recognized as an existing update
+ * mod, or MOD_UNKNOWN otherwise.
+ */
+ModifierType getType(StringData typeStr);
- /**
- * Instantiate an update mod that corresponds to 'modType' or NULL if 'modType' is not
- * valid. The ownership of the new object is the caller's.
- */
- ModifierInterface* makeUpdateMod(ModifierType modType);
+/**
+ * Instantiate an update mod that corresponds to 'modType' or NULL if 'modType' is not
+ * valid. The ownership of the new object is the caller's.
+ */
+ModifierInterface* makeUpdateMod(ModifierType modType);
-} // namespace modifiertable
-} // namespace mongo
+} // namespace modifiertable
+} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_table_test.cpp b/src/mongo/db/ops/modifier_table_test.cpp
index 239f9eb5a34..c5225142668 100644
--- a/src/mongo/db/ops/modifier_table_test.cpp
+++ b/src/mongo/db/ops/modifier_table_test.cpp
@@ -35,25 +35,25 @@
namespace {
- using namespace mongo::modifiertable;
+using namespace mongo::modifiertable;
- using mongo::ModifierInterface;
- using std::unique_ptr;
+using mongo::ModifierInterface;
+using std::unique_ptr;
- TEST(getType, Normal) {
- ASSERT_EQUALS(getType("$set"), MOD_SET);
- ASSERT_EQUALS(getType("$AModThatDoesn'tExist"), MOD_UNKNOWN);
- ASSERT_EQUALS(getType("NotAModExpression"), MOD_UNKNOWN);
- }
+TEST(getType, Normal) {
+ ASSERT_EQUALS(getType("$set"), MOD_SET);
+ ASSERT_EQUALS(getType("$AModThatDoesn'tExist"), MOD_UNKNOWN);
+ ASSERT_EQUALS(getType("NotAModExpression"), MOD_UNKNOWN);
+}
- TEST(makeUpdateMod, Normal) {
- unique_ptr<ModifierInterface> mod;
+TEST(makeUpdateMod, Normal) {
+ unique_ptr<ModifierInterface> mod;
- mod.reset(makeUpdateMod(MOD_SET));
- ASSERT_NOT_EQUALS(mod.get(), static_cast<ModifierInterface*>(0));
+ mod.reset(makeUpdateMod(MOD_SET));
+ ASSERT_NOT_EQUALS(mod.get(), static_cast<ModifierInterface*>(0));
- mod.reset(makeUpdateMod(MOD_UNKNOWN));
- ASSERT_EQUALS(mod.get(), static_cast<ModifierInterface*>(0));
- }
+ mod.reset(makeUpdateMod(MOD_UNKNOWN));
+ ASSERT_EQUALS(mod.get(), static_cast<ModifierInterface*>(0));
+}
-} // unnamed namespace
+} // unnamed namespace
diff --git a/src/mongo/db/ops/modifier_unset.cpp b/src/mongo/db/ops/modifier_unset.cpp
index 9b54480c62d..673cbdb8d16 100644
--- a/src/mongo/db/ops/modifier_unset.cpp
+++ b/src/mongo/db/ops/modifier_unset.cpp
@@ -37,149 +37,131 @@
namespace mongo {
- namespace str = mongoutils::str;
+namespace str = mongoutils::str;
- struct ModifierUnset::PreparedState {
+struct ModifierUnset::PreparedState {
+ PreparedState(mutablebson::Document* targetDoc)
+ : doc(*targetDoc), idxFound(0), elemFound(doc.end()), noOp(false) {}
- PreparedState(mutablebson::Document* targetDoc)
- : doc(*targetDoc)
- , idxFound(0)
- , elemFound(doc.end())
- , noOp(false) {
- }
+ // Document that is going to be changed.
+ mutablebson::Document& doc;
- // Document that is going to be changed.
- mutablebson::Document& doc;
+ // Index in _fieldRef for which an Element exist in the document.
+ size_t idxFound;
- // Index in _fieldRef for which an Element exist in the document.
- size_t idxFound;
+ // Element corresponding to _fieldRef[0.._idxFound].
+ mutablebson::Element elemFound;
- // Element corresponding to _fieldRef[0.._idxFound].
- mutablebson::Element elemFound;
+ // This $set is a no-op?
+ bool noOp;
+};
- // This $set is a no-op?
- bool noOp;
+ModifierUnset::ModifierUnset() : _fieldRef(), _posDollar(0), _val() {}
- };
+ModifierUnset::~ModifierUnset() {}
- ModifierUnset::ModifierUnset()
- : _fieldRef()
- , _posDollar(0)
- , _val() {
- }
+Status ModifierUnset::init(const BSONElement& modExpr, const Options& opts, bool* positional) {
+ //
+ // field name analysis
+ //
- ModifierUnset::~ModifierUnset() {
+ // Perform standard field name and updateable checks.
+ _fieldRef.parse(modExpr.fieldName());
+ Status status = fieldchecker::isUpdatable(_fieldRef);
+ if (!status.isOK()) {
+ return status;
}
- Status ModifierUnset::init(const BSONElement& modExpr, const Options& opts,
- bool* positional) {
-
- //
- // field name analysis
- //
-
- // Perform standard field name and updateable checks.
- _fieldRef.parse(modExpr.fieldName());
- Status status = fieldchecker::isUpdatable(_fieldRef);
- if (! status.isOK()) {
- return status;
- }
+ // If a $-positional operator was used, get the index in which it occurred
+ // and ensure only one occurrence.
+ size_t foundCount;
+ bool foundDollar = fieldchecker::isPositional(_fieldRef, &_posDollar, &foundCount);
- // If a $-positional operator was used, get the index in which it occurred
- // and ensure only one occurrence.
- size_t foundCount;
- bool foundDollar = fieldchecker::isPositional(_fieldRef, &_posDollar, &foundCount);
+ if (positional)
+ *positional = foundDollar;
- if (positional)
- *positional = foundDollar;
+ if (foundDollar && foundCount > 1) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "Too many positional (i.e. '$') elements found in path '"
+ << _fieldRef.dottedField() << "'");
+ }
- if (foundDollar && foundCount > 1) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "Too many positional (i.e. '$') elements found in path '"
- << _fieldRef.dottedField() << "'");
- }
+ //
+ // value analysis
+ //
- //
- // value analysis
- //
+ // Unset takes any value, since there is no semantics attached to such value.
+ _val = modExpr;
- // Unset takes any value, since there is no semantics attached to such value.
- _val = modExpr;
+ return Status::OK();
+}
- return Status::OK();
- }
+Status ModifierUnset::prepare(mutablebson::Element root,
+ StringData matchedField,
+ ExecInfo* execInfo) {
+ _preparedState.reset(new PreparedState(&root.getDocument()));
- Status ModifierUnset::prepare(mutablebson::Element root,
- StringData matchedField,
- ExecInfo* execInfo) {
-
- _preparedState.reset(new PreparedState(&root.getDocument()));
-
- // If we have a $-positional field, it is time to bind it to an actual field part.
- if (_posDollar) {
- if (matchedField.empty()) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "The positional operator did not find the match "
- "needed from the query. Unexpanded update: "
- << _fieldRef.dottedField());
- }
- _fieldRef.setPart(_posDollar, matchedField);
+ // If we have a $-positional field, it is time to bind it to an actual field part.
+ if (_posDollar) {
+ if (matchedField.empty()) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "The positional operator did not find the match "
+ "needed from the query. Unexpanded update: "
+ << _fieldRef.dottedField());
}
+ _fieldRef.setPart(_posDollar, matchedField);
+ }
- // Locate the field name in 'root'. Note that if we don't have the full path in the
- // doc, there isn't anything to unset, really.
- Status status = pathsupport::findLongestPrefix(_fieldRef,
- root,
- &_preparedState->idxFound,
- &_preparedState->elemFound);
- if (!status.isOK() ||
- _preparedState->idxFound != (_fieldRef.numParts() -1)) {
- execInfo->noOp = _preparedState->noOp = true;
- execInfo->fieldRef[0] = &_fieldRef;
-
- return Status::OK();
- }
-
- // If there is indeed something to unset, we register so, along with the interest in
- // the field name. The driver needs this info to sort out if there is any conflict
- // among mods.
+ // Locate the field name in 'root'. Note that if we don't have the full path in the
+ // doc, there isn't anything to unset, really.
+ Status status = pathsupport::findLongestPrefix(
+ _fieldRef, root, &_preparedState->idxFound, &_preparedState->elemFound);
+ if (!status.isOK() || _preparedState->idxFound != (_fieldRef.numParts() - 1)) {
+ execInfo->noOp = _preparedState->noOp = true;
execInfo->fieldRef[0] = &_fieldRef;
- // The only way for an $unset to be inplace is for its target field to be the last one
- // of the object. That is, it is always the right child on its paths. The current
- // rationale is that there should be no holes in a BSONObj and, to be in place, no
- // field boundaries must change.
- //
- // TODO:
- // mutablebson::Element curr = _preparedState->elemFound;
- // while (curr.ok()) {
- // if (curr.rightSibling().ok()) {
- // }
- // curr = curr.parent();
- // }
-
return Status::OK();
}
- Status ModifierUnset::apply() const {
- dassert(!_preparedState->noOp);
-
- // Our semantics says that, if we're unseting an element of an array, we swap that
- // value to null. The rationale is that we don't want other array elements to change
- // indices. (That could be achieved with $pull-ing element from it.)
- if (_preparedState->elemFound.parent().ok() &&
- _preparedState->elemFound.parent().getType() == Array) {
- return _preparedState->elemFound.setValueNull();
- }
- else {
- return _preparedState->elemFound.remove();
- }
+ // If there is indeed something to unset, we register so, along with the interest in
+ // the field name. The driver needs this info to sort out if there is any conflict
+ // among mods.
+ execInfo->fieldRef[0] = &_fieldRef;
+
+ // The only way for an $unset to be inplace is for its target field to be the last one
+ // of the object. That is, it is always the right child on its paths. The current
+ // rationale is that there should be no holes in a BSONObj and, to be in place, no
+ // field boundaries must change.
+ //
+ // TODO:
+ // mutablebson::Element curr = _preparedState->elemFound;
+ // while (curr.ok()) {
+ // if (curr.rightSibling().ok()) {
+ // }
+ // curr = curr.parent();
+ // }
+
+ return Status::OK();
+}
+
+Status ModifierUnset::apply() const {
+ dassert(!_preparedState->noOp);
+
+ // Our semantics says that, if we're unseting an element of an array, we swap that
+ // value to null. The rationale is that we don't want other array elements to change
+ // indices. (That could be achieved with $pull-ing element from it.)
+ if (_preparedState->elemFound.parent().ok() &&
+ _preparedState->elemFound.parent().getType() == Array) {
+ return _preparedState->elemFound.setValueNull();
+ } else {
+ return _preparedState->elemFound.remove();
}
+}
- Status ModifierUnset::log(LogBuilder* logBuilder) const {
- return logBuilder->addToUnsets(_fieldRef.dottedField());
- }
+Status ModifierUnset::log(LogBuilder* logBuilder) const {
+ return logBuilder->addToUnsets(_fieldRef.dottedField());
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_unset.h b/src/mongo/db/ops/modifier_unset.h
index 53417a3c37e..a36b16ac19c 100644
--- a/src/mongo/db/ops/modifier_unset.h
+++ b/src/mongo/db/ops/modifier_unset.h
@@ -38,66 +38,60 @@
namespace mongo {
- class LogBuilder;
-
- class ModifierUnset : public ModifierInterface {
- MONGO_DISALLOW_COPYING(ModifierUnset);
-
- public:
-
- ModifierUnset();
-
- //
- // Modifier interface implementation
- //
-
- virtual ~ModifierUnset();
-
- /**
- * A 'modExpr' is a BSONElement {<fieldname>: <value>} coming from a $set mod such as
- * {$unset: {<fieldname: <value>}}. init() extracts the field name and the value to be
- * assigned to it from 'modExpr'. It returns OK if successful or a status describing
- * the error.
- */
- virtual Status init(const BSONElement& modExpr, const Options& opts,
- bool* positional = NULL);
-
- /**
- * Locates the field to be removed under the 'root' element, if it exist, and fills in
- * 'execInfo' accordingly. Return OK if successful or a status describing the error.
- */
- virtual Status prepare(mutablebson::Element root,
- StringData matchedField,
- ExecInfo* execInfo);
-
- /**
- * Removes the found element from the document. If such element was inside an array,
- * removal means setting that array position to 'null'.
- */
- virtual Status apply() const;
-
- /**
- * Adds the exact $unset mod to the log.
- */
- virtual Status log(LogBuilder* logBuilder) const;
-
- private:
-
- // Access to each component of fieldName that's the target of this mod.
- FieldRef _fieldRef;
-
- // 0 or index for $-positional in _fieldRef.
- size_t _posDollar;
-
- // Element of the $set expression.
- BSONElement _val;
-
- // The instance of the field in the provided doc. This state is valid after a
- // prepare() was issued and until a log() is issued. The document this mod is
- // being prepared against must be live throughout all the calls.
- struct PreparedState;
- std::unique_ptr<PreparedState> _preparedState;
-
- };
-
-} // namespace mongo
+class LogBuilder;
+
+class ModifierUnset : public ModifierInterface {
+ MONGO_DISALLOW_COPYING(ModifierUnset);
+
+public:
+ ModifierUnset();
+
+ //
+ // Modifier interface implementation
+ //
+
+ virtual ~ModifierUnset();
+
+ /**
+ * A 'modExpr' is a BSONElement {<fieldname>: <value>} coming from a $set mod such as
+ * {$unset: {<fieldname: <value>}}. init() extracts the field name and the value to be
+ * assigned to it from 'modExpr'. It returns OK if successful or a status describing
+ * the error.
+ */
+ virtual Status init(const BSONElement& modExpr, const Options& opts, bool* positional = NULL);
+
+ /**
+ * Locates the field to be removed under the 'root' element, if it exist, and fills in
+ * 'execInfo' accordingly. Return OK if successful or a status describing the error.
+ */
+ virtual Status prepare(mutablebson::Element root, StringData matchedField, ExecInfo* execInfo);
+
+ /**
+ * Removes the found element from the document. If such element was inside an array,
+ * removal means setting that array position to 'null'.
+ */
+ virtual Status apply() const;
+
+ /**
+ * Adds the exact $unset mod to the log.
+ */
+ virtual Status log(LogBuilder* logBuilder) const;
+
+private:
+ // Access to each component of fieldName that's the target of this mod.
+ FieldRef _fieldRef;
+
+ // 0 or index for $-positional in _fieldRef.
+ size_t _posDollar;
+
+ // Element of the $set expression.
+ BSONElement _val;
+
+ // The instance of the field in the provided doc. This state is valid after a
+ // prepare() was issued and until a log() is issued. The document this mod is
+ // being prepared against must be live throughout all the calls.
+ struct PreparedState;
+ std::unique_ptr<PreparedState> _preparedState;
+};
+
+} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_unset_test.cpp b/src/mongo/db/ops/modifier_unset_test.cpp
index 3ca4957e94c..d68f471c7e7 100644
--- a/src/mongo/db/ops/modifier_unset_test.cpp
+++ b/src/mongo/db/ops/modifier_unset_test.cpp
@@ -42,410 +42,412 @@
namespace {
- using mongo::Array;
- using mongo::BSONObj;
- using mongo::fromjson;
- using mongo::LogBuilder;
- using mongo::ModifierInterface;
- using mongo::ModifierUnset;
- using mongo::Status;
- using mongo::StringData;
- 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) {
- _modObj = modObj;
- ASSERT_OK(_mod.init(_modObj["$unset"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal()));
- }
-
- 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);
- }
-
- ModifierUnset& mod() { return _mod; }
-
- BSONObj modObj() { return _modObj; }
-
- private:
- ModifierUnset _mod;
- BSONObj _modObj;
- };
-
- //
- // Simple mod
- //
-
- TEST(SimpleMod, PrepareNoOp) {
- Document doc(fromjson("{}"));
- Mod modUnset(fromjson("{$unset: {a: true}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_TRUE(execInfo.noOp);
+using mongo::Array;
+using mongo::BSONObj;
+using mongo::fromjson;
+using mongo::LogBuilder;
+using mongo::ModifierInterface;
+using mongo::ModifierUnset;
+using mongo::Status;
+using mongo::StringData;
+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) {
+ _modObj = modObj;
+ ASSERT_OK(_mod.init(_modObj["$unset"].embeddedObject().firstElement(),
+ ModifierInterface::Options::normal()));
}
- TEST(SimpleMod, PrepareApplyNormal) {
- Document doc(fromjson("{a: 1, b: 2}"));
- Mod modUnset(fromjson("{$unset: {a: true}}"));
+ Status prepare(Element root, StringData matchedField, ModifierInterface::ExecInfo* execInfo) {
+ return _mod.prepare(root, matchedField, execInfo);
+ }
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
+ Status apply() const {
+ return _mod.apply();
+ }
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
+ Status log(LogBuilder* logBuilder) const {
+ return _mod.log(logBuilder);
+ }
- ASSERT_OK(modUnset.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{b: 2}"), doc);
+ ModifierUnset& mod() {
+ return _mod;
}
- TEST(SimpleMod, PrepareApplyInPlace) {
- Document doc(fromjson("{x: 0, a: 1}"));
- Mod modUnset(fromjson("{$unset: {a: true}}"));
+ BSONObj modObj() {
+ return _modObj;
+ }
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
+private:
+ ModifierUnset _mod;
+ BSONObj _modObj;
+};
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
+//
+// Simple mod
+//
- ASSERT_OK(modUnset.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled()); // TODO turn in-place on for this.
- ASSERT_EQUALS(fromjson("{x: 0}"), doc);
- }
+TEST(SimpleMod, PrepareNoOp) {
+ Document doc(fromjson("{}"));
+ Mod modUnset(fromjson("{$unset: {a: true}}"));
- TEST(SimpleMod, PrepareApplyGeneratesEmptyDocument) {
- Document doc(fromjson("{a: 1}"));
- Mod modUnset(fromjson("{$unset: {a: true}}"));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_TRUE(execInfo.noOp);
+}
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
+TEST(SimpleMod, PrepareApplyNormal) {
+ Document doc(fromjson("{a: 1, b: 2}"));
+ Mod modUnset(fromjson("{$unset: {a: true}}"));
- ASSERT_OK(modUnset.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled()); // TODO turn in-place on for this.
- ASSERT_EQUALS(fromjson("{}"), doc);
- }
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
- TEST(SimpleMod, PrepareApplyUnsetSubtree) {
- Document doc(fromjson("{a: {b: 1}, c: 2}"));
- Mod modUnset(fromjson("{$unset: {a: true}}"));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
+ ASSERT_OK(modUnset.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{b: 2}"), doc);
+}
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
+TEST(SimpleMod, PrepareApplyInPlace) {
+ Document doc(fromjson("{x: 0, a: 1}"));
+ Mod modUnset(fromjson("{$unset: {a: true}}"));
- ASSERT_OK(modUnset.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{c: 2}"), doc);
- }
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
- TEST(SimpleMod, LogNormal) {
- BSONObj obj = fromjson("{a: 1}");
- Document doc(obj);
- Mod modUnset(fromjson("{$unset: {a: true}}"));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
+ ASSERT_OK(modUnset.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled()); // TODO turn in-place on for this.
+ ASSERT_EQUALS(fromjson("{x: 0}"), doc);
+}
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
+TEST(SimpleMod, PrepareApplyGeneratesEmptyDocument) {
+ Document doc(fromjson("{a: 1}"));
+ Mod modUnset(fromjson("{$unset: {a: true}}"));
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(modUnset.log(&logBuilder));
- ASSERT_EQUALS(modUnset.modObj(), logDoc);
- }
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
- //
- // Dotted mod
- //
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
- TEST(DottedMod, PrepareNoOp) {
- Document doc(fromjson("{c:2}"));
- Mod modUnset(fromjson("{$unset: {'a.b': true}}"));
+ ASSERT_OK(modUnset.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled()); // TODO turn in-place on for this.
+ ASSERT_EQUALS(fromjson("{}"), doc);
+}
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
+TEST(SimpleMod, PrepareApplyUnsetSubtree) {
+ Document doc(fromjson("{a: {b: 1}, c: 2}"));
+ Mod modUnset(fromjson("{$unset: {a: true}}"));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
- ASSERT_TRUE(execInfo.noOp);
- }
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
- TEST(DottedMod, PrepareApplyNormal) {
- Document doc(fromjson("{a: {b: 1}, c: 2}"));
- Mod modUnset(fromjson("{$unset: {'a.b': true}}"));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
+ ASSERT_OK(modUnset.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{c: 2}"), doc);
+}
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
- ASSERT_FALSE(execInfo.noOp);
+TEST(SimpleMod, LogNormal) {
+ BSONObj obj = fromjson("{a: 1}");
+ Document doc(obj);
+ Mod modUnset(fromjson("{$unset: {a: true}}"));
- ASSERT_OK(modUnset.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a:{}, c:2}"), doc);
- }
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
- TEST(DottedMod, PrepareApplyInPlace) {
- Document doc(fromjson("{x: 0, a: {b: 1}}"));
- Mod modUnset(fromjson("{$unset: {'a.b': true}}"));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_FALSE(execInfo.noOp);
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(modUnset.log(&logBuilder));
+ ASSERT_EQUALS(modUnset.modObj(), logDoc);
+}
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
- ASSERT_FALSE(execInfo.noOp);
+//
+// Dotted mod
+//
- ASSERT_OK(modUnset.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled()); // TODO turn in-place on for this.
- ASSERT_EQUALS(fromjson("{x: 0, a:{}}"), doc);
- }
+TEST(DottedMod, PrepareNoOp) {
+ Document doc(fromjson("{c:2}"));
+ Mod modUnset(fromjson("{$unset: {'a.b': true}}"));
- TEST(DottedMod, PrepareApplyUnsetNestedSubobject) {
- Document doc(fromjson("{a: {b: {c: 1}}}"));
- Mod modUnset(fromjson("{$unset: {'a.b': true}}"));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
+ ASSERT_TRUE(execInfo.noOp);
+}
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
- ASSERT_FALSE(execInfo.noOp);
+TEST(DottedMod, PrepareApplyNormal) {
+ Document doc(fromjson("{a: {b: 1}, c: 2}"));
+ Mod modUnset(fromjson("{$unset: {'a.b': true}}"));
- ASSERT_OK(modUnset.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled()); // TODO turn in-place on for this.
- ASSERT_EQUALS(fromjson("{a: {}}"), doc);
- }
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
- //
- // Indexed mod
- //
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
+ ASSERT_FALSE(execInfo.noOp);
- TEST(IndexedMod, PrepareNoOp) {
- Document doc(fromjson("{a:[]}"));
- Mod modUnset(fromjson("{$unset: {'a.0': true}}"));
+ ASSERT_OK(modUnset.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a:{}, c:2}"), doc);
+}
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
+TEST(DottedMod, PrepareApplyInPlace) {
+ Document doc(fromjson("{x: 0, a: {b: 1}}"));
+ Mod modUnset(fromjson("{$unset: {'a.b': true}}"));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0");
- ASSERT_TRUE(execInfo.noOp);
- }
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
- TEST(IndexedMod, PrepareApplyNormal) {
- Document doc(fromjson("{a:[0,1,2]}"));
- Mod modUnset(fromjson("{$unset: {'a.0': true}}"));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
+ ASSERT_FALSE(execInfo.noOp);
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
+ ASSERT_OK(modUnset.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled()); // TODO turn in-place on for this.
+ ASSERT_EQUALS(fromjson("{x: 0, a:{}}"), doc);
+}
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0");
- ASSERT_FALSE(execInfo.noOp);
+TEST(DottedMod, PrepareApplyUnsetNestedSubobject) {
+ Document doc(fromjson("{a: {b: {c: 1}}}"));
+ Mod modUnset(fromjson("{$unset: {'a.b': true}}"));
- ASSERT_OK(modUnset.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a:[null,1,2]}"), doc);
- }
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
- TEST(IndexedMod, PrepareApplyInPlace) {
- Document doc(fromjson("{b:1, a:[1]}"));
- Mod modUnset(fromjson("{$unset: {'a.0': true}}"));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
+ ASSERT_FALSE(execInfo.noOp);
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
+ ASSERT_OK(modUnset.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled()); // TODO turn in-place on for this.
+ ASSERT_EQUALS(fromjson("{a: {}}"), doc);
+}
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0");
- ASSERT_FALSE(execInfo.noOp);
+//
+// Indexed mod
+//
- ASSERT_OK(modUnset.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled()); // TODO turn in-place on for this.
- ASSERT_EQUALS(fromjson("{b:1, a:[null]}"), doc);
- }
+TEST(IndexedMod, PrepareNoOp) {
+ Document doc(fromjson("{a:[]}"));
+ Mod modUnset(fromjson("{$unset: {'a.0': true}}"));
- TEST(IndexedMod, PrepareApplyInPlaceNuance) {
- // Can't change the encoding in the middle of a bson stream.
- Document doc(fromjson("{a:[1], b:1}"));
- Mod modUnset(fromjson("{$unset: {'a.0': true}}"));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0");
+ ASSERT_TRUE(execInfo.noOp);
+}
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0");
- ASSERT_FALSE(execInfo.noOp);
+TEST(IndexedMod, PrepareApplyNormal) {
+ Document doc(fromjson("{a:[0,1,2]}"));
+ Mod modUnset(fromjson("{$unset: {'a.0': true}}"));
- ASSERT_OK(modUnset.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a:[null], b:1}"), doc);
- }
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
- TEST(IndexedMod, PrepareApplyInnerObject) {
- Document doc(fromjson("{a:[{b:1}]}"));
- Mod modUnset(fromjson("{$unset: {'a.0.b': true}}"));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0");
+ ASSERT_FALSE(execInfo.noOp);
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
+ ASSERT_OK(modUnset.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a:[null,1,2]}"), doc);
+}
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0.b");
- ASSERT_FALSE(execInfo.noOp);
+TEST(IndexedMod, PrepareApplyInPlace) {
+ Document doc(fromjson("{b:1, a:[1]}"));
+ Mod modUnset(fromjson("{$unset: {'a.0': true}}"));
- ASSERT_OK(modUnset.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a:[{}]}"), doc);
- }
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
- TEST(IndexedMod, PrepareApplyObject) {
- Document doc(fromjson("{a:[{b:1}]}"));
- Mod modUnset(fromjson("{$unset: {'a.0': true}}"));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0");
+ ASSERT_FALSE(execInfo.noOp);
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
+ ASSERT_OK(modUnset.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled()); // TODO turn in-place on for this.
+ ASSERT_EQUALS(fromjson("{b:1, a:[null]}"), doc);
+}
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0");
- ASSERT_FALSE(execInfo.noOp);
+TEST(IndexedMod, PrepareApplyInPlaceNuance) {
+ // Can't change the encoding in the middle of a bson stream.
+ Document doc(fromjson("{a:[1], b:1}"));
+ Mod modUnset(fromjson("{$unset: {'a.0': true}}"));
- ASSERT_OK(modUnset.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a:[null]}"), doc);
- }
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
- TEST(IndexedMod, LogNormal) {
- Document doc(fromjson("{a:[0,1,2]}"));
- Mod modUnset(fromjson("{$unset: {'a.0': true}}"));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0");
+ ASSERT_FALSE(execInfo.noOp);
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
+ ASSERT_OK(modUnset.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a:[null], b:1}"), doc);
+}
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(modUnset.log(&logBuilder));
- ASSERT_EQUALS(modUnset.modObj(), logDoc);
- }
+TEST(IndexedMod, PrepareApplyInnerObject) {
+ Document doc(fromjson("{a:[{b:1}]}"));
+ Mod modUnset(fromjson("{$unset: {'a.0.b': true}}"));
- //
- // Positional mod
- //
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
- TEST(PositionalMod, PrepareNoOp) {
- Document doc(fromjson("{a:[{b:0}]}"));
- Mod modUnset(fromjson("{$unset: {'a.$.b': true}}"));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0.b");
+ ASSERT_FALSE(execInfo.noOp);
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "1", &execInfo));
+ ASSERT_OK(modUnset.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a:[{}]}"), doc);
+}
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.b");
- ASSERT_TRUE(execInfo.noOp);
- }
+TEST(IndexedMod, PrepareApplyObject) {
+ Document doc(fromjson("{a:[{b:1}]}"));
+ Mod modUnset(fromjson("{$unset: {'a.0': true}}"));
- TEST(PositionalMod, PrepareMissingPositional) {
- Document doc(fromjson("{a:[{b:0},{c:1}]}"));
- Mod modUnset(fromjson("{$unset: {'a.$.b': true}}"));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(modUnset.prepare(doc.root(), "" /* no position */, &execInfo));
- }
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0");
+ ASSERT_FALSE(execInfo.noOp);
- TEST(PositionalMod, PrepareApplyNormal) {
- Document doc(fromjson("{a:[{b:0},{c:1}]}"));
- Mod modUnset(fromjson("{$unset: {'a.$.b': true}}"));
+ ASSERT_OK(modUnset.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a:[null]}"), doc);
+}
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "0", &execInfo));
+TEST(IndexedMod, LogNormal) {
+ Document doc(fromjson("{a:[0,1,2]}"));
+ Mod modUnset(fromjson("{$unset: {'a.0': true}}"));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0.b");
- ASSERT_FALSE(execInfo.noOp);
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
- ASSERT_OK(modUnset.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [{}, {c:1}]}"), doc);
- }
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(modUnset.log(&logBuilder));
+ ASSERT_EQUALS(modUnset.modObj(), logDoc);
+}
- TEST(PositionalMod, PrepareApplyObject) {
- Document doc(fromjson("{a:[{b:0},{c:1}]}"));
- Mod modUnset(fromjson("{$unset: {'a.$': true}}"));
+//
+// Positional mod
+//
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "0", &execInfo));
+TEST(PositionalMod, PrepareNoOp) {
+ Document doc(fromjson("{a:[{b:0}]}"));
+ Mod modUnset(fromjson("{$unset: {'a.$.b': true}}"));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0");
- ASSERT_FALSE(execInfo.noOp);
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(modUnset.prepare(doc.root(), "1", &execInfo));
- ASSERT_OK(modUnset.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [null, {c:1}]}"), doc);
- }
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.b");
+ ASSERT_TRUE(execInfo.noOp);
+}
- TEST(PositionalMod, PrepareApplyInPlace) {
- Document doc(fromjson("{b:1, a:[{b:1}]}"));
- Mod modUnset(fromjson("{$unset: {'a.$.b': true}}"));
+TEST(PositionalMod, PrepareMissingPositional) {
+ Document doc(fromjson("{a:[{b:0},{c:1}]}"));
+ Mod modUnset(fromjson("{$unset: {'a.$.b': true}}"));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "0", &execInfo));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_NOT_OK(modUnset.prepare(doc.root(), "" /* no position */, &execInfo));
+}
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0.b");
- ASSERT_FALSE(execInfo.noOp);
+TEST(PositionalMod, PrepareApplyNormal) {
+ Document doc(fromjson("{a:[{b:0},{c:1}]}"));
+ Mod modUnset(fromjson("{$unset: {'a.$.b': true}}"));
- ASSERT_OK(modUnset.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled()); // TODO turn in-place on for this.
- ASSERT_EQUALS(fromjson("{b:1, a:[{}]}"), doc);
- }
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(modUnset.prepare(doc.root(), "0", &execInfo));
- TEST(PositionalMod, LogNormal) {
- Document doc(fromjson("{b:1, a:[{b:1}]}"));
- Mod modUnset(fromjson("{$unset: {'a.$.b': true}}"));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0.b");
+ ASSERT_FALSE(execInfo.noOp);
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "0", &execInfo));
+ ASSERT_OK(modUnset.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: [{}, {c:1}]}"), doc);
+}
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0.b");
- ASSERT_FALSE(execInfo.noOp);
+TEST(PositionalMod, PrepareApplyObject) {
+ Document doc(fromjson("{a:[{b:0},{c:1}]}"));
+ Mod modUnset(fromjson("{$unset: {'a.$': true}}"));
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(modUnset.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{$unset: {'a.0.b': true}}"), logDoc);
- }
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(modUnset.prepare(doc.root(), "0", &execInfo));
- TEST(LegacyData, CanUnsetInvalidField) {
- Document doc(fromjson("{b:1, a:[{$b:1}]}"));
- Mod modUnset(fromjson("{$unset: {'a.$.$b': true}}"));
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0");
+ ASSERT_FALSE(execInfo.noOp);
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "0", &execInfo));
+ ASSERT_OK(modUnset.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{a: [null, {c:1}]}"), doc);
+}
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0.$b");
- ASSERT_FALSE(execInfo.noOp);
+TEST(PositionalMod, PrepareApplyInPlace) {
+ Document doc(fromjson("{b:1, a:[{b:1}]}"));
+ Mod modUnset(fromjson("{$unset: {'a.$.b': true}}"));
- ASSERT_OK(modUnset.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{b:1, a:[{}]}"), doc);
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(modUnset.prepare(doc.root(), "0", &execInfo));
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(modUnset.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{$unset: {'a.0.$b': true}}"), logDoc);
- }
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0.b");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(modUnset.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled()); // TODO turn in-place on for this.
+ ASSERT_EQUALS(fromjson("{b:1, a:[{}]}"), doc);
+}
+
+TEST(PositionalMod, LogNormal) {
+ Document doc(fromjson("{b:1, a:[{b:1}]}"));
+ Mod modUnset(fromjson("{$unset: {'a.$.b': true}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(modUnset.prepare(doc.root(), "0", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0.b");
+ ASSERT_FALSE(execInfo.noOp);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(modUnset.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{$unset: {'a.0.b': true}}"), logDoc);
+}
+
+TEST(LegacyData, CanUnsetInvalidField) {
+ Document doc(fromjson("{b:1, a:[{$b:1}]}"));
+ Mod modUnset(fromjson("{$unset: {'a.$.$b': true}}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(modUnset.prepare(doc.root(), "0", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0.$b");
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(modUnset.apply());
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{b:1, a:[{}]}"), doc);
+
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ ASSERT_OK(modUnset.log(&logBuilder));
+ ASSERT_EQUALS(fromjson("{$unset: {'a.0.$b': true}}"), logDoc);
+}
-} // unnamed namespace
+} // unnamed namespace
diff --git a/src/mongo/db/ops/parsed_delete.cpp b/src/mongo/db/ops/parsed_delete.cpp
index 62243d0818d..46a2a33752d 100644
--- a/src/mongo/db/ops/parsed_delete.cpp
+++ b/src/mongo/db/ops/parsed_delete.cpp
@@ -46,88 +46,86 @@
namespace mongo {
- ParsedDelete::ParsedDelete(OperationContext* txn, const DeleteRequest* request) :
- _txn(txn),
- _request(request) { }
+ParsedDelete::ParsedDelete(OperationContext* txn, const DeleteRequest* request)
+ : _txn(txn), _request(request) {}
- Status ParsedDelete::parseRequest() {
- dassert(!_canonicalQuery.get());
- // It is invalid to request that the DeleteStage return the deleted document during a
- // multi-remove.
- invariant(!(_request->shouldReturnDeleted() && _request->isMulti()));
+Status ParsedDelete::parseRequest() {
+ dassert(!_canonicalQuery.get());
+ // It is invalid to request that the DeleteStage return the deleted document during a
+ // multi-remove.
+ invariant(!(_request->shouldReturnDeleted() && _request->isMulti()));
- // It is invalid to request that a ProjectionStage be applied to the DeleteStage if the
- // DeleteStage would not return the deleted document.
- invariant(_request->getProj().isEmpty() || _request->shouldReturnDeleted());
+ // It is invalid to request that a ProjectionStage be applied to the DeleteStage if the
+ // DeleteStage would not return the deleted document.
+ invariant(_request->getProj().isEmpty() || _request->shouldReturnDeleted());
- if (CanonicalQuery::isSimpleIdQuery(_request->getQuery())) {
- return Status::OK();
- }
-
- return parseQueryToCQ();
+ if (CanonicalQuery::isSimpleIdQuery(_request->getQuery())) {
+ return Status::OK();
}
- Status ParsedDelete::parseQueryToCQ() {
- dassert(!_canonicalQuery.get());
-
- CanonicalQuery* cqRaw;
- const WhereCallbackReal whereCallback(_txn, _request->getNamespaceString().db());
-
- // Limit should only used for the findAndModify command when a sort is specified. If a sort
- // is requested, we want to use a top-k sort for efficiency reasons, so should pass the
- // limit through. Generally, a delete stage expects to be able to skip documents that were
- // deleted out from under it, but a limit could inhibit that and give an EOF when the delete
- // has not actually deleted a document. This behavior is fine for findAndModify, but should
- // not apply to deletes in general.
- long long limit = (!_request->isMulti() && !_request->getSort().isEmpty()) ? -1 : 0;
-
- // The projection needs to be applied after the delete operation, so we specify an empty
- // BSONObj as the projection during canonicalization.
- const BSONObj emptyObj;
- Status status = CanonicalQuery::canonicalize(_request->getNamespaceString().ns(),
- _request->getQuery(),
- _request->getSort(),
- emptyObj, // projection
- 0, // skip
- limit,
- emptyObj, // hint
- emptyObj, // min
- emptyObj, // max
- false, // snapshot
- _request->isExplain(),
- &cqRaw,
- whereCallback);
-
- if (status.isOK()) {
- _canonicalQuery.reset(cqRaw);
- }
-
- return status;
+ return parseQueryToCQ();
+}
+
+Status ParsedDelete::parseQueryToCQ() {
+ dassert(!_canonicalQuery.get());
+
+ CanonicalQuery* cqRaw;
+ const WhereCallbackReal whereCallback(_txn, _request->getNamespaceString().db());
+
+ // Limit should only used for the findAndModify command when a sort is specified. If a sort
+ // is requested, we want to use a top-k sort for efficiency reasons, so should pass the
+ // limit through. Generally, a delete stage expects to be able to skip documents that were
+ // deleted out from under it, but a limit could inhibit that and give an EOF when the delete
+ // has not actually deleted a document. This behavior is fine for findAndModify, but should
+ // not apply to deletes in general.
+ long long limit = (!_request->isMulti() && !_request->getSort().isEmpty()) ? -1 : 0;
+
+ // The projection needs to be applied after the delete operation, so we specify an empty
+ // BSONObj as the projection during canonicalization.
+ const BSONObj emptyObj;
+ Status status = CanonicalQuery::canonicalize(_request->getNamespaceString().ns(),
+ _request->getQuery(),
+ _request->getSort(),
+ emptyObj, // projection
+ 0, // skip
+ limit,
+ emptyObj, // hint
+ emptyObj, // min
+ emptyObj, // max
+ false, // snapshot
+ _request->isExplain(),
+ &cqRaw,
+ whereCallback);
+
+ if (status.isOK()) {
+ _canonicalQuery.reset(cqRaw);
}
- const DeleteRequest* ParsedDelete::getRequest() const {
- return _request;
- }
+ return status;
+}
- bool ParsedDelete::canYield() const {
- return !_request->isGod() &&
- PlanExecutor::YIELD_AUTO == _request->getYieldPolicy() &&
- !isIsolated();
- }
+const DeleteRequest* ParsedDelete::getRequest() const {
+ return _request;
+}
- bool ParsedDelete::isIsolated() const {
- return _canonicalQuery.get()
- ? QueryPlannerCommon::hasNode(_canonicalQuery->root(), MatchExpression::ATOMIC)
- : LiteParsedQuery::isQueryIsolated(_request->getQuery());
- }
+bool ParsedDelete::canYield() const {
+ return !_request->isGod() && PlanExecutor::YIELD_AUTO == _request->getYieldPolicy() &&
+ !isIsolated();
+}
- bool ParsedDelete::hasParsedQuery() const {
- return _canonicalQuery.get() != NULL;
- }
+bool ParsedDelete::isIsolated() const {
+ return _canonicalQuery.get()
+ ? QueryPlannerCommon::hasNode(_canonicalQuery->root(), MatchExpression::ATOMIC)
+ : LiteParsedQuery::isQueryIsolated(_request->getQuery());
+}
- CanonicalQuery* ParsedDelete::releaseParsedQuery() {
- invariant(_canonicalQuery.get() != NULL);
- return _canonicalQuery.release();
- }
+bool ParsedDelete::hasParsedQuery() const {
+ return _canonicalQuery.get() != NULL;
+}
+
+CanonicalQuery* ParsedDelete::releaseParsedQuery() {
+ invariant(_canonicalQuery.get() != NULL);
+ return _canonicalQuery.release();
+}
} // namespace mongo
diff --git a/src/mongo/db/ops/parsed_delete.h b/src/mongo/db/ops/parsed_delete.h
index c65f0da69e9..0745cbb85e8 100644
--- a/src/mongo/db/ops/parsed_delete.h
+++ b/src/mongo/db/ops/parsed_delete.h
@@ -34,85 +34,85 @@
namespace mongo {
- class CanonicalQuery;
- class Database;
- class DeleteRequest;
- class OperationContext;
+class CanonicalQuery;
+class Database;
+class DeleteRequest;
+class OperationContext;
+/**
+ * This class takes a pointer to a DeleteRequest, and converts that request into a parsed form
+ * via the parseRequest() method. A ParsedDelete can then be used to retrieve a PlanExecutor
+ * capable of executing the delete.
+ *
+ * It is invalid to request that the DeleteStage return the deleted document during a
+ * multi-remove. It is also invalid to request that a ProjectionStage be applied to the
+ * DeleteStage if the DeleteStage would not return the deleted document.
+ *
+ * A delete request is parsed to a CanonicalQuery, so this class is a thin, delete-specific
+ * wrapper around canonicalization.
+ *
+ * No locks need to be held during parsing.
+ */
+class ParsedDelete {
+ MONGO_DISALLOW_COPYING(ParsedDelete);
+
+public:
/**
- * This class takes a pointer to a DeleteRequest, and converts that request into a parsed form
- * via the parseRequest() method. A ParsedDelete can then be used to retrieve a PlanExecutor
- * capable of executing the delete.
- *
- * It is invalid to request that the DeleteStage return the deleted document during a
- * multi-remove. It is also invalid to request that a ProjectionStage be applied to the
- * DeleteStage if the DeleteStage would not return the deleted document.
- *
- * A delete request is parsed to a CanonicalQuery, so this class is a thin, delete-specific
- * wrapper around canonicalization.
+ * Constructs a parsed delete.
*
- * No locks need to be held during parsing.
+ * The object pointed to by "request" must stay in scope for the life of the constructed
+ * ParsedDelete.
+ */
+ ParsedDelete(OperationContext* txn, const DeleteRequest* request);
+
+ /**
+ * Parses the delete request to a canonical query. On success, the parsed delete can be
+ * used to create a PlanExecutor capable of executing this delete.
+ */
+ Status parseRequest();
+
+ /**
+ * As an optimization, we do not create a canonical query if the predicate is a simple
+ * _id equality. This method can be used to force full parsing to a canonical query,
+ * as a fallback if the idhack path is not available (e.g. no _id index).
+ */
+ Status parseQueryToCQ();
+
+ /**
+ * Get the raw request.
+ */
+ const DeleteRequest* getRequest() const;
+
+ /**
+ * Is this delete allowed to yield?
+ */
+ bool canYield() const;
+
+ /**
+ * Is this update supposed to be isolated?
+ */
+ bool isIsolated() const;
+
+ /**
+ * As an optimization, we don't create a canonical query for updates with simple _id
+ * queries. Use this method to determine whether or not we actually parsed the query.
+ */
+ bool hasParsedQuery() const;
+
+ /**
+ * Releases ownership of the canonical query to the caller.
*/
- class ParsedDelete {
- MONGO_DISALLOW_COPYING(ParsedDelete);
- public:
- /**
- * Constructs a parsed delete.
- *
- * The object pointed to by "request" must stay in scope for the life of the constructed
- * ParsedDelete.
- */
- ParsedDelete(OperationContext* txn, const DeleteRequest* request);
-
- /**
- * Parses the delete request to a canonical query. On success, the parsed delete can be
- * used to create a PlanExecutor capable of executing this delete.
- */
- Status parseRequest();
-
- /**
- * As an optimization, we do not create a canonical query if the predicate is a simple
- * _id equality. This method can be used to force full parsing to a canonical query,
- * as a fallback if the idhack path is not available (e.g. no _id index).
- */
- Status parseQueryToCQ();
-
- /**
- * Get the raw request.
- */
- const DeleteRequest* getRequest() const;
-
- /**
- * Is this delete allowed to yield?
- */
- bool canYield() const;
-
- /**
- * Is this update supposed to be isolated?
- */
- bool isIsolated() const;
-
- /**
- * As an optimization, we don't create a canonical query for updates with simple _id
- * queries. Use this method to determine whether or not we actually parsed the query.
- */
- bool hasParsedQuery() const;
-
- /**
- * Releases ownership of the canonical query to the caller.
- */
- CanonicalQuery* releaseParsedQuery();
-
- private:
- // Transactional context. Not owned by us.
- OperationContext* _txn;
-
- // Unowned pointer to the request object that this executor will process.
- const DeleteRequest* const _request;
-
- // Parsed query object, or NULL if the query proves to be an id hack query.
- std::unique_ptr<CanonicalQuery> _canonicalQuery;
-
- };
+ CanonicalQuery* releaseParsedQuery();
+
+private:
+ // Transactional context. Not owned by us.
+ OperationContext* _txn;
+
+ // Unowned pointer to the request object that this executor will process.
+ const DeleteRequest* const _request;
+
+ // Parsed query object, or NULL if the query proves to be an id hack query.
+ std::unique_ptr<CanonicalQuery> _canonicalQuery;
+};
} // namespace mongo
diff --git a/src/mongo/db/ops/parsed_update.cpp b/src/mongo/db/ops/parsed_update.cpp
index d4651371e3f..80138e0385d 100644
--- a/src/mongo/db/ops/parsed_update.cpp
+++ b/src/mongo/db/ops/parsed_update.cpp
@@ -35,126 +35,120 @@
namespace mongo {
- ParsedUpdate::ParsedUpdate(OperationContext* txn, const UpdateRequest* request) :
- _txn(txn),
- _request(request),
- _driver(UpdateDriver::Options()),
- _canonicalQuery() { }
-
- Status ParsedUpdate::parseRequest() {
- // It is invalid to request that the UpdateStage return the prior or newly-updated version
- // of a document during a multi-update.
- invariant(!(_request->shouldReturnAnyDocs() && _request->isMulti()));
-
- // It is invalid to request that a ProjectionStage be applied to the UpdateStage if the
- // UpdateStage would not return any document.
- invariant(_request->getProj().isEmpty() || _request->shouldReturnAnyDocs());
-
- // We parse the update portion before the query portion because the dispostion of the update
- // may determine whether or not we need to produce a CanonicalQuery at all. For example, if
- // the update involves the positional-dollar operator, we must have a CanonicalQuery even if
- // it isn't required for query execution.
- Status status = parseUpdate();
- if (!status.isOK())
- return status;
- status = parseQuery();
- if (!status.isOK())
- return status;
- return Status::OK();
- }
-
- Status ParsedUpdate::parseQuery() {
- dassert(!_canonicalQuery.get());
+ParsedUpdate::ParsedUpdate(OperationContext* txn, const UpdateRequest* request)
+ : _txn(txn), _request(request), _driver(UpdateDriver::Options()), _canonicalQuery() {}
+
+Status ParsedUpdate::parseRequest() {
+ // It is invalid to request that the UpdateStage return the prior or newly-updated version
+ // of a document during a multi-update.
+ invariant(!(_request->shouldReturnAnyDocs() && _request->isMulti()));
+
+ // It is invalid to request that a ProjectionStage be applied to the UpdateStage if the
+ // UpdateStage would not return any document.
+ invariant(_request->getProj().isEmpty() || _request->shouldReturnAnyDocs());
+
+ // We parse the update portion before the query portion because the dispostion of the update
+ // may determine whether or not we need to produce a CanonicalQuery at all. For example, if
+ // the update involves the positional-dollar operator, we must have a CanonicalQuery even if
+ // it isn't required for query execution.
+ Status status = parseUpdate();
+ if (!status.isOK())
+ return status;
+ status = parseQuery();
+ if (!status.isOK())
+ return status;
+ return Status::OK();
+}
- if (!_driver.needMatchDetails() && CanonicalQuery::isSimpleIdQuery(_request->getQuery())) {
- return Status::OK();
- }
+Status ParsedUpdate::parseQuery() {
+ dassert(!_canonicalQuery.get());
- return parseQueryToCQ();
+ if (!_driver.needMatchDetails() && CanonicalQuery::isSimpleIdQuery(_request->getQuery())) {
+ return Status::OK();
}
- Status ParsedUpdate::parseQueryToCQ() {
- dassert(!_canonicalQuery.get());
-
- CanonicalQuery* cqRaw;
- const WhereCallbackReal whereCallback(_txn, _request->getNamespaceString().db());
-
- // Limit should only used for the findAndModify command when a sort is specified. If a sort
- // is requested, we want to use a top-k sort for efficiency reasons, so should pass the
- // limit through. Generally, a update stage expects to be able to skip documents that were
- // deleted/modified under it, but a limit could inhibit that and give an EOF when the update
- // has not actually updated a document. This behavior is fine for findAndModify, but should
- // not apply to update in general.
- long long limit = (!_request->isMulti() && !_request->getSort().isEmpty()) ? -1 : 0;
-
- // The projection needs to be applied after the update operation, so we specify an empty
- // BSONObj as the projection during canonicalization.
- const BSONObj emptyObj;
- Status status = CanonicalQuery::canonicalize(_request->getNamespaceString().ns(),
- _request->getQuery(),
- _request->getSort(),
- emptyObj, // projection
- 0, // skip
- limit,
- emptyObj, // hint
- emptyObj, // min
- emptyObj, // max
- false, // snapshot
- _request->isExplain(),
- &cqRaw,
- whereCallback);
- if (status.isOK()) {
- _canonicalQuery.reset(cqRaw);
- }
-
- return status;
+ return parseQueryToCQ();
+}
+
+Status ParsedUpdate::parseQueryToCQ() {
+ dassert(!_canonicalQuery.get());
+
+ CanonicalQuery* cqRaw;
+ const WhereCallbackReal whereCallback(_txn, _request->getNamespaceString().db());
+
+ // Limit should only used for the findAndModify command when a sort is specified. If a sort
+ // is requested, we want to use a top-k sort for efficiency reasons, so should pass the
+ // limit through. Generally, a update stage expects to be able to skip documents that were
+ // deleted/modified under it, but a limit could inhibit that and give an EOF when the update
+ // has not actually updated a document. This behavior is fine for findAndModify, but should
+ // not apply to update in general.
+ long long limit = (!_request->isMulti() && !_request->getSort().isEmpty()) ? -1 : 0;
+
+ // The projection needs to be applied after the update operation, so we specify an empty
+ // BSONObj as the projection during canonicalization.
+ const BSONObj emptyObj;
+ Status status = CanonicalQuery::canonicalize(_request->getNamespaceString().ns(),
+ _request->getQuery(),
+ _request->getSort(),
+ emptyObj, // projection
+ 0, // skip
+ limit,
+ emptyObj, // hint
+ emptyObj, // min
+ emptyObj, // max
+ false, // snapshot
+ _request->isExplain(),
+ &cqRaw,
+ whereCallback);
+ if (status.isOK()) {
+ _canonicalQuery.reset(cqRaw);
}
- Status ParsedUpdate::parseUpdate() {
- const NamespaceString& ns(_request->getNamespaceString());
+ return status;
+}
- // Should the modifiers validate their embedded docs via okForStorage
- // Only user updates should be checked. Any system or replication stuff should pass through.
- // Config db docs shouldn't get checked for valid field names since the shard key can have
- // a dot (".") in it.
- const bool shouldValidate = !(!_txn->writesAreReplicated() ||
- ns.isConfigDB() ||
- _request->isFromMigration());
+Status ParsedUpdate::parseUpdate() {
+ const NamespaceString& ns(_request->getNamespaceString());
- _driver.setLogOp(true);
- _driver.setModOptions(ModifierInterface::Options(!_txn->writesAreReplicated(),
- shouldValidate));
+ // Should the modifiers validate their embedded docs via okForStorage
+ // Only user updates should be checked. Any system or replication stuff should pass through.
+ // Config db docs shouldn't get checked for valid field names since the shard key can have
+ // a dot (".") in it.
+ const bool shouldValidate =
+ !(!_txn->writesAreReplicated() || ns.isConfigDB() || _request->isFromMigration());
- return _driver.parse(_request->getUpdates(), _request->isMulti());
- }
+ _driver.setLogOp(true);
+ _driver.setModOptions(ModifierInterface::Options(!_txn->writesAreReplicated(), shouldValidate));
- bool ParsedUpdate::canYield() const {
- return !_request->isGod() &&
- PlanExecutor::YIELD_AUTO == _request->getYieldPolicy() &&
- !isIsolated();
- }
+ return _driver.parse(_request->getUpdates(), _request->isMulti());
+}
- bool ParsedUpdate::isIsolated() const {
- return _canonicalQuery.get()
- ? QueryPlannerCommon::hasNode(_canonicalQuery->root(), MatchExpression::ATOMIC)
- : LiteParsedQuery::isQueryIsolated(_request->getQuery());
- }
+bool ParsedUpdate::canYield() const {
+ return !_request->isGod() && PlanExecutor::YIELD_AUTO == _request->getYieldPolicy() &&
+ !isIsolated();
+}
- bool ParsedUpdate::hasParsedQuery() const {
- return _canonicalQuery.get() != NULL;
- }
+bool ParsedUpdate::isIsolated() const {
+ return _canonicalQuery.get()
+ ? QueryPlannerCommon::hasNode(_canonicalQuery->root(), MatchExpression::ATOMIC)
+ : LiteParsedQuery::isQueryIsolated(_request->getQuery());
+}
- CanonicalQuery* ParsedUpdate::releaseParsedQuery() {
- invariant(_canonicalQuery.get() != NULL);
- return _canonicalQuery.release();
- }
+bool ParsedUpdate::hasParsedQuery() const {
+ return _canonicalQuery.get() != NULL;
+}
- const UpdateRequest* ParsedUpdate::getRequest() const {
- return _request;
- }
+CanonicalQuery* ParsedUpdate::releaseParsedQuery() {
+ invariant(_canonicalQuery.get() != NULL);
+ return _canonicalQuery.release();
+}
- UpdateDriver* ParsedUpdate::getDriver() {
- return &_driver;
- }
+const UpdateRequest* ParsedUpdate::getRequest() const {
+ return _request;
+}
+
+UpdateDriver* ParsedUpdate::getDriver() {
+ return &_driver;
+}
} // namespace mongo
diff --git a/src/mongo/db/ops/parsed_update.h b/src/mongo/db/ops/parsed_update.h
index d109904d1bd..8ce08aaabc3 100644
--- a/src/mongo/db/ops/parsed_update.h
+++ b/src/mongo/db/ops/parsed_update.h
@@ -34,102 +34,103 @@
namespace mongo {
- class CanonicalQuery;
- class OperationContext;
- class UpdateRequest;
+class CanonicalQuery;
+class OperationContext;
+class UpdateRequest;
+/**
+ * This class takes a pointer to an UpdateRequest, and converts that request into a parsed form
+ * via the parseRequest() method. A ParsedUpdate can then be used to retrieve a PlanExecutor
+ * capable of executing the update.
+ *
+ * It is invalid to request that the UpdateStage return the prior or newly-updated version of a
+ * document during a multi-update. It is also invalid to request that a ProjectionStage be
+ * applied to the UpdateStage if the UpdateStage would not return any document.
+ *
+ * No locks need to be held during parsing.
+ *
+ * The query part of the update is parsed to a CanonicalQuery, and the update part is parsed
+ * using the UpdateDriver.
+ */
+class ParsedUpdate {
+ MONGO_DISALLOW_COPYING(ParsedUpdate);
+
+public:
/**
- * This class takes a pointer to an UpdateRequest, and converts that request into a parsed form
- * via the parseRequest() method. A ParsedUpdate can then be used to retrieve a PlanExecutor
- * capable of executing the update.
+ * Constructs a parsed update.
*
- * It is invalid to request that the UpdateStage return the prior or newly-updated version of a
- * document during a multi-update. It is also invalid to request that a ProjectionStage be
- * applied to the UpdateStage if the UpdateStage would not return any document.
- *
- * No locks need to be held during parsing.
- *
- * The query part of the update is parsed to a CanonicalQuery, and the update part is parsed
- * using the UpdateDriver.
+ * The object pointed to by "request" must stay in scope for the life of the constructed
+ * ParsedUpdate.
+ */
+ ParsedUpdate(OperationContext* txn, const UpdateRequest* request);
+
+ /**
+ * Parses the update request to a canonical query and an update driver. On success, the
+ * parsed update can be used to create a PlanExecutor for this update.
+ */
+ Status parseRequest();
+
+ /**
+ * As an optimization, we do not create a canonical query if the predicate is a simple
+ * _id equality. This method can be used to force full parsing to a canonical query,
+ * as a fallback if the idhack path is not available (e.g. no _id index).
+ */
+ Status parseQueryToCQ();
+
+ /**
+ * Get the raw request.
+ */
+ const UpdateRequest* getRequest() const;
+
+ /**
+ * Get a pointer to the update driver, the abstraction which both parses the update and
+ * is capable of applying mods / computing damages.
+ */
+ UpdateDriver* getDriver();
+
+ /**
+ * Is this update allowed to yield?
+ */
+ bool canYield() const;
+
+ /**
+ * Is this update supposed to be isolated?
+ */
+ bool isIsolated() const;
+
+ /**
+ * As an optimization, we don't create a canonical query for updates with simple _id
+ * queries. Use this method to determine whether or not we actually parsed the query.
+ */
+ bool hasParsedQuery() const;
+
+ /**
+ * Releases ownership of the canonical query to the caller.
*/
- class ParsedUpdate {
- MONGO_DISALLOW_COPYING(ParsedUpdate);
- public:
- /**
- * Constructs a parsed update.
- *
- * The object pointed to by "request" must stay in scope for the life of the constructed
- * ParsedUpdate.
- */
- ParsedUpdate(OperationContext* txn, const UpdateRequest* request);
-
- /**
- * Parses the update request to a canonical query and an update driver. On success, the
- * parsed update can be used to create a PlanExecutor for this update.
- */
- Status parseRequest();
-
- /**
- * As an optimization, we do not create a canonical query if the predicate is a simple
- * _id equality. This method can be used to force full parsing to a canonical query,
- * as a fallback if the idhack path is not available (e.g. no _id index).
- */
- Status parseQueryToCQ();
-
- /**
- * Get the raw request.
- */
- const UpdateRequest* getRequest() const;
-
- /**
- * Get a pointer to the update driver, the abstraction which both parses the update and
- * is capable of applying mods / computing damages.
- */
- UpdateDriver* getDriver();
-
- /**
- * Is this update allowed to yield?
- */
- bool canYield() const;
-
- /**
- * Is this update supposed to be isolated?
- */
- bool isIsolated() const;
-
- /**
- * As an optimization, we don't create a canonical query for updates with simple _id
- * queries. Use this method to determine whether or not we actually parsed the query.
- */
- bool hasParsedQuery() const;
-
- /**
- * Releases ownership of the canonical query to the caller.
- */
- CanonicalQuery* releaseParsedQuery();
-
- private:
- /**
- * Parses the query portion of the update request.
- */
- Status parseQuery();
-
- /**
- * Parses the update-descriptor portion of the update request.
- */
- Status parseUpdate();
-
- // Unowned pointer to the transactional context.
- OperationContext* _txn;
-
- // Unowned pointer to the request object to process.
- const UpdateRequest* const _request;
-
- // Driver for processing updates on matched documents.
- UpdateDriver _driver;
-
- // Parsed query object, or NULL if the query proves to be an id hack query.
- std::unique_ptr<CanonicalQuery> _canonicalQuery;
- };
+ CanonicalQuery* releaseParsedQuery();
+
+private:
+ /**
+ * Parses the query portion of the update request.
+ */
+ Status parseQuery();
+
+ /**
+ * Parses the update-descriptor portion of the update request.
+ */
+ Status parseUpdate();
+
+ // Unowned pointer to the transactional context.
+ OperationContext* _txn;
+
+ // Unowned pointer to the request object to process.
+ const UpdateRequest* const _request;
+
+ // Driver for processing updates on matched documents.
+ UpdateDriver _driver;
+
+ // Parsed query object, or NULL if the query proves to be an id hack query.
+ std::unique_ptr<CanonicalQuery> _canonicalQuery;
+};
} // namespace mongo
diff --git a/src/mongo/db/ops/path_support.cpp b/src/mongo/db/ops/path_support.cpp
index 4ac4b03fb36..894b0117393 100644
--- a/src/mongo/db/ops/path_support.cpp
+++ b/src/mongo/db/ops/path_support.cpp
@@ -38,78 +38,72 @@
namespace mongo {
namespace pathsupport {
- using std::string;
- using mongoutils::str::stream;
-
- namespace {
-
- bool isNumeric(StringData str, size_t* num) {
- size_t res = 0;
- for (size_t i = 0; i < str.size(); ++i) {
- if (str[i] < '0' || str[i] > '9') {
- return false;
- }
- else {
- res = res * 10 + (str[i] - '0');
- }
- }
- *num = res;
- return true;
+using std::string;
+using mongoutils::str::stream;
+
+namespace {
+
+bool isNumeric(StringData str, size_t* num) {
+ size_t res = 0;
+ for (size_t i = 0; i < str.size(); ++i) {
+ if (str[i] < '0' || str[i] > '9') {
+ return false;
+ } else {
+ res = res * 10 + (str[i] - '0');
}
+ }
+ *num = res;
+ return true;
+}
- Status maybePadTo(mutablebson::Element* elemArray,
- size_t sizeRequired) {
- dassert(elemArray->getType() == Array);
+Status maybePadTo(mutablebson::Element* elemArray, size_t sizeRequired) {
+ dassert(elemArray->getType() == Array);
- if (sizeRequired > kMaxPaddingAllowed) {
- return Status(ErrorCodes::CannotBackfillArray,
- mongoutils::str::stream() << "can't backfill array to larger than "
- << kMaxPaddingAllowed << " elements");
- }
+ if (sizeRequired > kMaxPaddingAllowed) {
+ return Status(ErrorCodes::CannotBackfillArray,
+ mongoutils::str::stream() << "can't backfill array to larger than "
+ << kMaxPaddingAllowed << " elements");
+ }
- size_t currSize = mutablebson::countChildren(*elemArray);
- if (sizeRequired > currSize) {
- size_t toPad = sizeRequired - currSize;
- for (size_t i = 0; i < toPad; i++) {
- Status status = elemArray->appendNull("");
- if (!status.isOK()) {
- return status;
- }
- }
+ size_t currSize = mutablebson::countChildren(*elemArray);
+ if (sizeRequired > currSize) {
+ size_t toPad = sizeRequired - currSize;
+ for (size_t i = 0; i < toPad; i++) {
+ Status status = elemArray->appendNull("");
+ if (!status.isOK()) {
+ return status;
}
- return Status::OK();
}
+ }
+ return Status::OK();
+}
+
+} // unnamed namespace
+
+Status findLongestPrefix(const FieldRef& prefix,
+ mutablebson::Element root,
+ size_t* idxFound,
+ mutablebson::Element* elemFound) {
+ // If root is empty or the prefix is so, there's no point in looking for a prefix.
+ const size_t prefixSize = prefix.numParts();
+ if (!root.hasChildren() || prefixSize == 0) {
+ return Status(ErrorCodes::NonExistentPath, "either the document or the path are empty");
+ }
- } // unnamed namespace
-
- Status findLongestPrefix(const FieldRef& prefix,
- mutablebson::Element root,
- size_t* idxFound,
- mutablebson::Element* elemFound) {
-
- // If root is empty or the prefix is so, there's no point in looking for a prefix.
- const size_t prefixSize = prefix.numParts();
- if (!root.hasChildren() || prefixSize == 0) {
- return Status(ErrorCodes::NonExistentPath,
- "either the document or the path are empty");
- }
-
- // Loop through prefix's parts. At each iteration, check that the part ('curr') exists
- // in 'root' and that the type of the previous part ('prev') allows for children.
- mutablebson::Element curr = root;
- mutablebson::Element prev = root;
- size_t i = 0;
- size_t numericPart = 0;
- bool viable = true;
- for (;i < prefixSize; i++) {
-
- // If prefix wants to reach 'curr' by applying a non-numeric index to an array
- // 'prev', or if 'curr' wants to traverse a leaf 'prev', then we'd be in a
- // non-viable path (see definition on the header file).
- StringData prefixPart = prefix.getPart(i);
- prev = curr;
- switch (curr.getType()) {
-
+ // Loop through prefix's parts. At each iteration, check that the part ('curr') exists
+ // in 'root' and that the type of the previous part ('prev') allows for children.
+ mutablebson::Element curr = root;
+ mutablebson::Element prev = root;
+ size_t i = 0;
+ size_t numericPart = 0;
+ bool viable = true;
+ for (; i < prefixSize; i++) {
+ // If prefix wants to reach 'curr' by applying a non-numeric index to an array
+ // 'prev', or if 'curr' wants to traverse a leaf 'prev', then we'd be in a
+ // non-viable path (see definition on the header file).
+ StringData prefixPart = prefix.getPart(i);
+ prev = curr;
+ switch (curr.getType()) {
case Object:
curr = prev[prefixPart];
break;
@@ -124,338 +118,307 @@ namespace pathsupport {
default:
viable = false;
- }
-
- // If we couldn't find the next field part of the prefix in the document or if the
- // field part we're in constitutes a non-viable path, we can stop looking.
- if (!curr.ok() || !viable) {
- break;
- }
}
- // We broke out of the loop because one of four things happened. (a) 'prefix' and
- // 'root' have nothing in common, (b) 'prefix' is not viable in 'root', (c) not all the
- // parts in 'prefix' exist in 'root', or (d) all parts do. In each case, we need to
- // figure out what index and Element pointer to return.
- if (i == 0) {
- return Status(ErrorCodes::NonExistentPath,
- "cannot find path in the document");
- }
- else if (!viable) {
- *idxFound = i - 1;
- *elemFound = prev;
- return Status(ErrorCodes::PathNotViable,
- mongoutils::str::stream() << "cannot use the part (" <<
- prefix.getPart(i-1) << " of " << prefix.dottedField() <<
- ") to traverse the element ({" <<
- curr.toString() << "})");
- }
- else if (curr.ok()) {
- *idxFound = i - 1;
- *elemFound = curr;
- return Status::OK();
- }
- else {
- *idxFound = i - 1;
- *elemFound = prev;
- return Status::OK();
+ // If we couldn't find the next field part of the prefix in the document or if the
+ // field part we're in constitutes a non-viable path, we can stop looking.
+ if (!curr.ok() || !viable) {
+ break;
}
}
- Status createPathAt(const FieldRef& prefix,
- size_t idxFound,
- mutablebson::Element elemFound,
- mutablebson::Element newElem) {
- Status status = Status::OK();
-
- // Sanity check that 'idxField' is an actual part.
- const size_t size = prefix.numParts();
- if (idxFound >= size) {
- return Status(ErrorCodes::BadValue, "index larger than path size");
- }
-
- mutablebson::Document& doc = elemFound.getDocument();
-
- // If we are creating children under an array and a numeric index is next, then perhaps
- // we need padding.
- size_t i = idxFound;
- bool inArray = false;
- if (elemFound.getType() == mongo::Array) {
- size_t newIdx = 0;
- if (!isNumeric(prefix.getPart(idxFound), &newIdx)) {
- return Status(ErrorCodes::InvalidPath, "Array require numeric fields");
- }
+ // We broke out of the loop because one of four things happened. (a) 'prefix' and
+ // 'root' have nothing in common, (b) 'prefix' is not viable in 'root', (c) not all the
+ // parts in 'prefix' exist in 'root', or (d) all parts do. In each case, we need to
+ // figure out what index and Element pointer to return.
+ if (i == 0) {
+ return Status(ErrorCodes::NonExistentPath, "cannot find path in the document");
+ } else if (!viable) {
+ *idxFound = i - 1;
+ *elemFound = prev;
+ return Status(ErrorCodes::PathNotViable,
+ mongoutils::str::stream() << "cannot use the part (" << prefix.getPart(i - 1)
+ << " of " << prefix.dottedField()
+ << ") to traverse the element ({" << curr.toString()
+ << "})");
+ } else if (curr.ok()) {
+ *idxFound = i - 1;
+ *elemFound = curr;
+ return Status::OK();
+ } else {
+ *idxFound = i - 1;
+ *elemFound = prev;
+ return Status::OK();
+ }
+}
+
+Status createPathAt(const FieldRef& prefix,
+ size_t idxFound,
+ mutablebson::Element elemFound,
+ mutablebson::Element newElem) {
+ Status status = Status::OK();
+
+ // Sanity check that 'idxField' is an actual part.
+ const size_t size = prefix.numParts();
+ if (idxFound >= size) {
+ return Status(ErrorCodes::BadValue, "index larger than path size");
+ }
- status = maybePadTo(&elemFound, newIdx);
- if (!status.isOK()) {
- return status;
- }
+ mutablebson::Document& doc = elemFound.getDocument();
- // If there is a next field, that would be an array element. We'd like to mark that
- // field because we create array elements differently than we do regular objects.
- if (++i < size) {
- inArray = true;
- }
+ // If we are creating children under an array and a numeric index is next, then perhaps
+ // we need padding.
+ size_t i = idxFound;
+ bool inArray = false;
+ if (elemFound.getType() == mongo::Array) {
+ size_t newIdx = 0;
+ if (!isNumeric(prefix.getPart(idxFound), &newIdx)) {
+ return Status(ErrorCodes::InvalidPath, "Array require numeric fields");
}
- // Create all the remaining parts but the last one.
- for (; i < size - 1 ; i++) {
- mutablebson::Element elem = doc.makeElementObject(prefix.getPart(i));
- if (!elem.ok()) {
- return Status(ErrorCodes::InternalError, "cannot create path");
- }
+ status = maybePadTo(&elemFound, newIdx);
+ if (!status.isOK()) {
+ return status;
+ }
- // If this field is an array element, we wrap it in an object (because array
- // elements are wraped in { "N": <element> } objects.
- if (inArray) {
- // TODO pass empty StringData to makeElementObject, when that's supported.
- mutablebson::Element arrayObj = doc.makeElementObject("" /* it's an array */);
- if (!arrayObj.ok()) {
- return Status(ErrorCodes::InternalError, "cannot create item on array");
- }
- status = arrayObj.pushBack(elem);
- if (!status.isOK()) {
- return status;
- }
- status = elemFound.pushBack(arrayObj);
- if (!status.isOK()) {
- return status;
- }
- inArray = false;
- }
- else {
- status = elemFound.pushBack(elem);
- if (!status.isOK()) {
- return status;
- }
- }
+ // If there is a next field, that would be an array element. We'd like to mark that
+ // field because we create array elements differently than we do regular objects.
+ if (++i < size) {
+ inArray = true;
+ }
+ }
- elemFound = elem;
+ // Create all the remaining parts but the last one.
+ for (; i < size - 1; i++) {
+ mutablebson::Element elem = doc.makeElementObject(prefix.getPart(i));
+ if (!elem.ok()) {
+ return Status(ErrorCodes::InternalError, "cannot create path");
}
- // Attach the last element. Here again, if we're in a field that is an array element,
- // we wrap it in an object first.
+ // If this field is an array element, we wrap it in an object (because array
+ // elements are wraped in { "N": <element> } objects.
if (inArray) {
// TODO pass empty StringData to makeElementObject, when that's supported.
mutablebson::Element arrayObj = doc.makeElementObject("" /* it's an array */);
if (!arrayObj.ok()) {
return Status(ErrorCodes::InternalError, "cannot create item on array");
}
-
- status = arrayObj.pushBack(newElem);
+ status = arrayObj.pushBack(elem);
if (!status.isOK()) {
return status;
}
-
status = elemFound.pushBack(arrayObj);
if (!status.isOK()) {
return status;
}
-
- }
- else {
- status = elemFound.pushBack(newElem);
+ inArray = false;
+ } else {
+ status = elemFound.pushBack(elem);
if (!status.isOK()) {
return status;
}
}
- return Status::OK();
+ elemFound = elem;
}
- Status setElementAtPath(const FieldRef& path,
- const BSONElement& value,
- mutablebson::Document* doc) {
-
- size_t deepestElemPathPart;
- mutablebson::Element deepestElem(doc->end());
-
- // Get the existing parents of this path
- Status status = findLongestPrefix(path,
- doc->root(),
- &deepestElemPathPart,
- &deepestElem);
-
- // TODO: All this is pretty awkward, why not return the position immediately after the
- // consumed path or use a signed sentinel? Why is it a special case when we've consumed the
- // whole path?
+ // Attach the last element. Here again, if we're in a field that is an array element,
+ // we wrap it in an object first.
+ if (inArray) {
+ // TODO pass empty StringData to makeElementObject, when that's supported.
+ mutablebson::Element arrayObj = doc.makeElementObject("" /* it's an array */);
+ if (!arrayObj.ok()) {
+ return Status(ErrorCodes::InternalError, "cannot create item on array");
+ }
- if (!status.isOK() && status.code() != ErrorCodes::NonExistentPath)
+ status = arrayObj.pushBack(newElem);
+ if (!status.isOK()) {
return status;
-
- // Inc the path by one *unless* we matched nothing
- if (status.code() != ErrorCodes::NonExistentPath) {
- ++deepestElemPathPart;
- }
- else {
- deepestElemPathPart = 0;
- deepestElem = doc->root();
}
- if (deepestElemPathPart == path.numParts()) {
- // The full path exists already in the document, so just set a value
- return deepestElem.setValueBSONElement(value);
+ status = elemFound.pushBack(arrayObj);
+ if (!status.isOK()) {
+ return status;
}
- else {
- // Construct the rest of the path we need with empty documents and set the value
- StringData leafFieldName = path.getPart(path.numParts() - 1);
- mutablebson::Element leafElem = doc->makeElementWithNewFieldName(leafFieldName,
- value);
- dassert(leafElem.ok());
- return createPathAt(path, deepestElemPathPart, deepestElem, leafElem);
+
+ } else {
+ status = elemFound.pushBack(newElem);
+ if (!status.isOK()) {
+ return status;
}
}
- const BSONElement& findParentEqualityElement(const EqualityMatches& equalities,
- const FieldRef& path,
- int* parentPathParts) {
+ return Status::OK();
+}
- // We may have an equality match to an object at a higher point in the pattern path, check
- // all path prefixes for equality matches
- // ex: path: 'a.b', query : { 'a' : { b : <value> } }
- // ex: path: 'a.b.c', query : { 'a.b' : { c : <value> } }
- for (int i = static_cast<int>(path.numParts()); i >= 0; --i) {
+Status setElementAtPath(const FieldRef& path,
+ const BSONElement& value,
+ mutablebson::Document* doc) {
+ size_t deepestElemPathPart;
+ mutablebson::Element deepestElem(doc->end());
- // "" element is *not* a parent of anyone but itself
- if (i == 0 && path.numParts() != 0)
- continue;
+ // Get the existing parents of this path
+ Status status = findLongestPrefix(path, doc->root(), &deepestElemPathPart, &deepestElem);
- StringData subPathStr = path.dottedSubstring(0, i);
- EqualityMatches::const_iterator seenIt = equalities.find(subPathStr);
- if (seenIt == equalities.end())
- continue;
+ // TODO: All this is pretty awkward, why not return the position immediately after the
+ // consumed path or use a signed sentinel? Why is it a special case when we've consumed the
+ // whole path?
- *parentPathParts = i;
- return seenIt->second->getData();
- }
+ if (!status.isOK() && status.code() != ErrorCodes::NonExistentPath)
+ return status;
- *parentPathParts = -1;
- static const BSONElement eooElement;
- return eooElement;
+ // Inc the path by one *unless* we matched nothing
+ if (status.code() != ErrorCodes::NonExistentPath) {
+ ++deepestElemPathPart;
+ } else {
+ deepestElemPathPart = 0;
+ deepestElem = doc->root();
}
- /**
- * Helper function to check if the current equality match paths conflict with a new path.
- */
- static Status checkEqualityConflicts(const EqualityMatches& equalities, const FieldRef& path) {
-
- int parentPathPart = -1;
- const BSONElement& parentEl = findParentEqualityElement(equalities,
- path,
- &parentPathPart);
-
- if (parentEl.eoo())
- return Status::OK();
+ if (deepestElemPathPart == path.numParts()) {
+ // The full path exists already in the document, so just set a value
+ return deepestElem.setValueBSONElement(value);
+ } else {
+ // Construct the rest of the path we need with empty documents and set the value
+ StringData leafFieldName = path.getPart(path.numParts() - 1);
+ mutablebson::Element leafElem = doc->makeElementWithNewFieldName(leafFieldName, value);
+ dassert(leafElem.ok());
+ return createPathAt(path, deepestElemPathPart, deepestElem, leafElem);
+ }
+}
+
+const BSONElement& findParentEqualityElement(const EqualityMatches& equalities,
+ const FieldRef& path,
+ int* parentPathParts) {
+ // We may have an equality match to an object at a higher point in the pattern path, check
+ // all path prefixes for equality matches
+ // ex: path: 'a.b', query : { 'a' : { b : <value> } }
+ // ex: path: 'a.b.c', query : { 'a.b' : { c : <value> } }
+ for (int i = static_cast<int>(path.numParts()); i >= 0; --i) {
+ // "" element is *not* a parent of anyone but itself
+ if (i == 0 && path.numParts() != 0)
+ continue;
+
+ StringData subPathStr = path.dottedSubstring(0, i);
+ EqualityMatches::const_iterator seenIt = equalities.find(subPathStr);
+ if (seenIt == equalities.end())
+ continue;
+
+ *parentPathParts = i;
+ return seenIt->second->getData();
+ }
- string errMsg = "cannot infer query fields to set, ";
+ *parentPathParts = -1;
+ static const BSONElement eooElement;
+ return eooElement;
+}
- StringData pathStr = path.dottedField();
- StringData prefixStr = path.dottedSubstring(0, parentPathPart);
- StringData suffixStr = path.dottedSubstring(parentPathPart, path.numParts());
+/**
+ * Helper function to check if the current equality match paths conflict with a new path.
+ */
+static Status checkEqualityConflicts(const EqualityMatches& equalities, const FieldRef& path) {
+ int parentPathPart = -1;
+ const BSONElement& parentEl = findParentEqualityElement(equalities, path, &parentPathPart);
- if (suffixStr.size() != 0)
- errMsg += stream() << "both paths '" << pathStr << "' and '" << prefixStr
- << "' are matched";
- else
- errMsg += stream() << "path '" << pathStr << "' is matched twice";
+ if (parentEl.eoo())
+ return Status::OK();
- return Status(ErrorCodes::NotSingleValueField, errMsg);
- }
+ string errMsg = "cannot infer query fields to set, ";
- /**
- * Helper function to check if path conflicts are all prefixes.
- */
- static Status checkPathIsPrefixOf(const FieldRef& path, const FieldRefSet& conflictPaths) {
+ StringData pathStr = path.dottedField();
+ StringData prefixStr = path.dottedSubstring(0, parentPathPart);
+ StringData suffixStr = path.dottedSubstring(parentPathPart, path.numParts());
- for (FieldRefSet::const_iterator it = conflictPaths.begin(); it != conflictPaths.end();
- ++it) {
+ if (suffixStr.size() != 0)
+ errMsg += stream() << "both paths '" << pathStr << "' and '" << prefixStr
+ << "' are matched";
+ else
+ errMsg += stream() << "path '" << pathStr << "' is matched twice";
- const FieldRef* conflictingPath = *it;
- // Conflicts are always prefixes (or equal to) the path, or vice versa
- if (path.numParts() > conflictingPath->numParts()) {
+ return Status(ErrorCodes::NotSingleValueField, errMsg);
+}
- string errMsg = stream() << "field at '" << conflictingPath->dottedField()
- << "' must be exactly specified, field at sub-path '"
- << path.dottedField() << "'found";
- return Status(ErrorCodes::NotExactValueField, errMsg);
- }
+/**
+ * Helper function to check if path conflicts are all prefixes.
+ */
+static Status checkPathIsPrefixOf(const FieldRef& path, const FieldRefSet& conflictPaths) {
+ for (FieldRefSet::const_iterator it = conflictPaths.begin(); it != conflictPaths.end(); ++it) {
+ const FieldRef* conflictingPath = *it;
+ // Conflicts are always prefixes (or equal to) the path, or vice versa
+ if (path.numParts() > conflictingPath->numParts()) {
+ string errMsg = stream() << "field at '" << conflictingPath->dottedField()
+ << "' must be exactly specified, field at sub-path '"
+ << path.dottedField() << "'found";
+ return Status(ErrorCodes::NotExactValueField, errMsg);
}
-
- return Status::OK();
}
- static Status _extractFullEqualityMatches(const MatchExpression& root,
- const FieldRefSet* fullPathsToExtract,
- EqualityMatches* equalities) {
-
- if (root.matchType() == MatchExpression::EQ) {
-
- // Extract equality matches
- const EqualityMatchExpression& eqChild =
- static_cast<const EqualityMatchExpression&>(root);
-
- FieldRef path(eqChild.path());
+ return Status::OK();
+}
- if (fullPathsToExtract) {
+static Status _extractFullEqualityMatches(const MatchExpression& root,
+ const FieldRefSet* fullPathsToExtract,
+ EqualityMatches* equalities) {
+ if (root.matchType() == MatchExpression::EQ) {
+ // Extract equality matches
+ const EqualityMatchExpression& eqChild = static_cast<const EqualityMatchExpression&>(root);
- FieldRefSet conflictPaths;
- fullPathsToExtract->findConflicts(&path, &conflictPaths);
+ FieldRef path(eqChild.path());
- // Ignore if this path is unrelated to the full paths
- if (conflictPaths.empty())
- return Status::OK();
+ if (fullPathsToExtract) {
+ FieldRefSet conflictPaths;
+ fullPathsToExtract->findConflicts(&path, &conflictPaths);
- // Make sure we're a prefix of all the conflict paths
- Status status = checkPathIsPrefixOf(path, conflictPaths);
- if (!status.isOK())
- return status;
- }
+ // Ignore if this path is unrelated to the full paths
+ if (conflictPaths.empty())
+ return Status::OK();
- Status status = checkEqualityConflicts(*equalities, path);
+ // Make sure we're a prefix of all the conflict paths
+ Status status = checkPathIsPrefixOf(path, conflictPaths);
if (!status.isOK())
return status;
-
- equalities->insert(std::make_pair(eqChild.path(), &eqChild));
- }
- else if (root.matchType() == MatchExpression::AND) {
-
- // Further explore $and matches
- for (size_t i = 0; i < root.numChildren(); ++i) {
- MatchExpression* child = root.getChild(i);
- Status status = _extractFullEqualityMatches(*child, fullPathsToExtract, equalities);
- if (!status.isOK())
- return status;
- }
}
- return Status::OK();
- }
-
- Status extractFullEqualityMatches(const MatchExpression& root,
- const FieldRefSet& fullPathsToExtract,
- EqualityMatches* equalities) {
- return _extractFullEqualityMatches(root, &fullPathsToExtract, equalities);
- }
+ Status status = checkEqualityConflicts(*equalities, path);
+ if (!status.isOK())
+ return status;
- Status extractEqualityMatches(const MatchExpression& root, EqualityMatches* equalities) {
- return _extractFullEqualityMatches(root, NULL, equalities);
+ equalities->insert(std::make_pair(eqChild.path(), &eqChild));
+ } else if (root.matchType() == MatchExpression::AND) {
+ // Further explore $and matches
+ for (size_t i = 0; i < root.numChildren(); ++i) {
+ MatchExpression* child = root.getChild(i);
+ Status status = _extractFullEqualityMatches(*child, fullPathsToExtract, equalities);
+ if (!status.isOK())
+ return status;
+ }
}
- Status addEqualitiesToDoc(const EqualityMatches& equalities, mutablebson::Document* doc) {
+ return Status::OK();
+}
- for (EqualityMatches::const_iterator it = equalities.begin(); it != equalities.end();
- ++it) {
+Status extractFullEqualityMatches(const MatchExpression& root,
+ const FieldRefSet& fullPathsToExtract,
+ EqualityMatches* equalities) {
+ return _extractFullEqualityMatches(root, &fullPathsToExtract, equalities);
+}
- FieldRef path(it->first);
- const BSONElement& data = it->second->getData();
+Status extractEqualityMatches(const MatchExpression& root, EqualityMatches* equalities) {
+ return _extractFullEqualityMatches(root, NULL, equalities);
+}
- Status status = setElementAtPath(path, data, doc);
- if (!status.isOK())
- return status;
- }
+Status addEqualitiesToDoc(const EqualityMatches& equalities, mutablebson::Document* doc) {
+ for (EqualityMatches::const_iterator it = equalities.begin(); it != equalities.end(); ++it) {
+ FieldRef path(it->first);
+ const BSONElement& data = it->second->getData();
- return Status::OK();
+ Status status = setElementAtPath(path, data, doc);
+ if (!status.isOK())
+ return status;
}
-} // namespace pathsupport
-} // namespace mongo
+ return Status::OK();
+}
+
+} // namespace pathsupport
+} // namespace mongo
diff --git a/src/mongo/db/ops/path_support.h b/src/mongo/db/ops/path_support.h
index ed4515506b8..95d0a3da44c 100644
--- a/src/mongo/db/ops/path_support.h
+++ b/src/mongo/db/ops/path_support.h
@@ -40,152 +40,149 @@
namespace mongo {
- namespace pathsupport {
-
- // Cap on the number of nulls we'll add to an array if we're inserting to an index that
- // doesn't exist.
- static const size_t kMaxPaddingAllowed = 1500000;
-
- // Convenience type to hold equality matches at particular paths from a MatchExpression
- typedef std::map<StringData, const EqualityMatchExpression*> EqualityMatches;
-
- /**
- * Finds the longest portion of 'prefix' that exists in document rooted at 'root' and is
- * "viable." A viable path is one that, if fully created on a given doc, would not
- * change the existing types of any fields in that doc. (See examples below.)
- *
- * If a prefix indeed exists, 'idxFound' is set to indicate how many parts in common
- * 'prefix' and 'doc' have. 'elemFound' would point to the Element corresponding to
- * prefix[idxFound] in 'doc'. The call would return an OK status in this case.
- *
- * If a prefix is not viable, returns a status "PathNotViable". 'idxFound' is set to
- * indicate the part in the document that caused the path to be not viable. 'elemFound'
- * would point to the Element corresponding to prefix[idxFound] in 'doc'.
- *
- * If a prefix does not exist, the call returns "NonExistentPath". 'elemFound' and
- * 'idxFound' are indeterminate in this case.
- *
- * Definition of a "Viable Path":
- *
- * A field reference 'p_1.p_2.[...].p_n', where 'p_i' is a field part, is said to be
- * a viable path in a given document D if the creation of each part 'p_i', 0 <= i < n
- * in D does not force 'p_i' to change types. In other words, no existing 'p_i' in D
- * may have a different type, other than the 'p_n'.
- *
- * 'a.b.c' is a viable path in {a: {b: {c: 1}}}
- * 'a.b.c' is a viable path in {a: {b: {c: {d: 1}}}}
- * 'a.b.c' is NOT a viable path in {a: {b: 1}}, because b would have changed types
- * 'a.0.b' is a viable path in {a: [{b: 1}, {c: 1}]}
- * 'a.0.b' is a viable path in {a: {"0": {b: 1}}}
- * 'a.0.b' is NOT a viable path in {a: 1}, because a would have changed types
- * 'a.5.b' is a viable path in in {a: []} (padding would occur)
- */
- Status findLongestPrefix(const FieldRef& prefix,
- mutablebson::Element root,
- size_t* idxFound,
- mutablebson::Element* elemFound);
-
- /**
- * Creates the parts 'prefix[idxRoot]', 'prefix[idxRoot+1]', ...,
- * 'prefix[<numParts>-1]' under 'elemFound' and adds 'newElem' as a child of that
- * path. Returns OK, if successful, or an error code describing why not, otherwise.
- *
- * createPathAt is designed to work with 'findLongestPrefix' in that it can create the
- * field parts in 'prefix' that are missing from a given document. 'elemFound' points
- * to the element in the doc that is the parent of prefix[idxRoot].
- */
- Status createPathAt(const FieldRef& prefix,
- size_t idxRoot,
- mutablebson::Element elemFound,
- mutablebson::Element newElem);
-
- /**
- * Uses the above methods to set the given value at the specified path in a mutable
- * Document, creating parents of the path if necessary.
- *
- * Returns PathNotViable if the path cannot be created without modifying the type of another
- * element, see above.
- */
- Status setElementAtPath(const FieldRef& path,
- const BSONElement& value,
- mutablebson::Document* doc);
-
- /**
- * Finds and returns-by-path all the equality matches in a particular MatchExpression.
- *
- * This method is meant to be used with the methods below, which allow efficient use of the
- * equality matches without needing to serialize to a BSONObj.
- *
- * Returns NotSingleValueField if the match expression has equality operators for
- * conflicting paths - equality paths conflict if they are the same or one path is a prefix
- * of the other.
- *
- * Ex:
- * { a : 1, b : 1 } -> no conflict
- * { a : 1, a.b : 1 } -> conflict
- * { _id : { x : 1 }, _id.y : 1 } -> conflict
- * { a : 1, a : 1 } -> conflict
- */
- Status extractEqualityMatches(const MatchExpression& root, EqualityMatches* equalities);
-
- /**
- * Same as the above, but ignores all paths except for paths in a specified set.
- * Equality matches with paths completely distinct from these paths are ignored.
- *
- * For a full equality match, the path of an equality found must not be a suffix of one of
- * the specified path - otherwise it isn't clear how to construct a full value for a field
- * at that path.
- *
- * Generally this is useful for shard keys and _ids which need unambiguous extraction from
- * queries.
- *
- * Ex:
- * { a : 1 }, full path 'a' -> a $eq 1 extracted
- * { a : 1 }, full path 'a.b' -> a $eq 1 extracted
- * { 'a.b' : 1 }, full path 'a' -> NotExactValueField error
- * ('a.b' doesn't specify 'a' fully)
- * { 'a.b' : 1 }, full path 'a.b' -> 'a.b' $eq 1 extracted
- * { '_id' : 1 }, full path '_id' -> '_id' $eq 1 extracted
- * { '_id.x' : 1 }, full path '_id' -> NotExactValueFieldError
- */
- Status extractFullEqualityMatches(const MatchExpression& root,
- const FieldRefSet& fullPathsToExtract,
- EqualityMatches* equalities);
-
- /**
- * Returns the equality match which is at or a parent of the specified path string. The
- * path string must be a valid dotted path.
- *
- * If a parent equality is found, returns the BSONElement data from that equality (which
- * includes the BSON value), the path of the parent element (prefixStr), and the remainder
- * of the path (which may be empty).
- *
- * EOO() is returned if there were no equalities at any point along the path.
- *
- * Ex:
- * Given equality matches of:
- * 'a.b' : 1, 'c' : 2
- * Path 'a' has no equality match parent (EOO)
- * Path 'c' has an eqmatch parent of 'c' : 2
- * Path 'c.d' has an eqmatch parent of 'c' : 2
- * Path 'a.b' has an eqmatch parent of 'a.b' : 1
- * Path 'a.b.c' has an eqmatch parent of 'a.b' : 1
- *
- */
- const BSONElement& findParentEqualityElement(const EqualityMatches& equalities,
- const FieldRef& path,
- int* parentPathParts);
-
- /**
- * Adds the BSON values from equality matches into the given document at the equality match
- * paths.
- *
- * Returns PathNotViable similar to setElementAtPath above. If equality paths do not
- * conflict, as is enforced by extractEqualityMatches, this function should return OK.
- */
- Status addEqualitiesToDoc(const EqualityMatches& equalities,
- mutablebson::Document* doc);
-
- } // namespace pathsupport
-
-} // namespace mongo
+namespace pathsupport {
+
+// Cap on the number of nulls we'll add to an array if we're inserting to an index that
+// doesn't exist.
+static const size_t kMaxPaddingAllowed = 1500000;
+
+// Convenience type to hold equality matches at particular paths from a MatchExpression
+typedef std::map<StringData, const EqualityMatchExpression*> EqualityMatches;
+
+/**
+ * Finds the longest portion of 'prefix' that exists in document rooted at 'root' and is
+ * "viable." A viable path is one that, if fully created on a given doc, would not
+ * change the existing types of any fields in that doc. (See examples below.)
+ *
+ * If a prefix indeed exists, 'idxFound' is set to indicate how many parts in common
+ * 'prefix' and 'doc' have. 'elemFound' would point to the Element corresponding to
+ * prefix[idxFound] in 'doc'. The call would return an OK status in this case.
+ *
+ * If a prefix is not viable, returns a status "PathNotViable". 'idxFound' is set to
+ * indicate the part in the document that caused the path to be not viable. 'elemFound'
+ * would point to the Element corresponding to prefix[idxFound] in 'doc'.
+ *
+ * If a prefix does not exist, the call returns "NonExistentPath". 'elemFound' and
+ * 'idxFound' are indeterminate in this case.
+ *
+ * Definition of a "Viable Path":
+ *
+ * A field reference 'p_1.p_2.[...].p_n', where 'p_i' is a field part, is said to be
+ * a viable path in a given document D if the creation of each part 'p_i', 0 <= i < n
+ * in D does not force 'p_i' to change types. In other words, no existing 'p_i' in D
+ * may have a different type, other than the 'p_n'.
+ *
+ * 'a.b.c' is a viable path in {a: {b: {c: 1}}}
+ * 'a.b.c' is a viable path in {a: {b: {c: {d: 1}}}}
+ * 'a.b.c' is NOT a viable path in {a: {b: 1}}, because b would have changed types
+ * 'a.0.b' is a viable path in {a: [{b: 1}, {c: 1}]}
+ * 'a.0.b' is a viable path in {a: {"0": {b: 1}}}
+ * 'a.0.b' is NOT a viable path in {a: 1}, because a would have changed types
+ * 'a.5.b' is a viable path in in {a: []} (padding would occur)
+ */
+Status findLongestPrefix(const FieldRef& prefix,
+ mutablebson::Element root,
+ size_t* idxFound,
+ mutablebson::Element* elemFound);
+
+/**
+ * Creates the parts 'prefix[idxRoot]', 'prefix[idxRoot+1]', ...,
+ * 'prefix[<numParts>-1]' under 'elemFound' and adds 'newElem' as a child of that
+ * path. Returns OK, if successful, or an error code describing why not, otherwise.
+ *
+ * createPathAt is designed to work with 'findLongestPrefix' in that it can create the
+ * field parts in 'prefix' that are missing from a given document. 'elemFound' points
+ * to the element in the doc that is the parent of prefix[idxRoot].
+ */
+Status createPathAt(const FieldRef& prefix,
+ size_t idxRoot,
+ mutablebson::Element elemFound,
+ mutablebson::Element newElem);
+
+/**
+ * Uses the above methods to set the given value at the specified path in a mutable
+ * Document, creating parents of the path if necessary.
+ *
+ * Returns PathNotViable if the path cannot be created without modifying the type of another
+ * element, see above.
+ */
+Status setElementAtPath(const FieldRef& path, const BSONElement& value, mutablebson::Document* doc);
+
+/**
+ * Finds and returns-by-path all the equality matches in a particular MatchExpression.
+ *
+ * This method is meant to be used with the methods below, which allow efficient use of the
+ * equality matches without needing to serialize to a BSONObj.
+ *
+ * Returns NotSingleValueField if the match expression has equality operators for
+ * conflicting paths - equality paths conflict if they are the same or one path is a prefix
+ * of the other.
+ *
+ * Ex:
+ * { a : 1, b : 1 } -> no conflict
+ * { a : 1, a.b : 1 } -> conflict
+ * { _id : { x : 1 }, _id.y : 1 } -> conflict
+ * { a : 1, a : 1 } -> conflict
+ */
+Status extractEqualityMatches(const MatchExpression& root, EqualityMatches* equalities);
+
+/**
+ * Same as the above, but ignores all paths except for paths in a specified set.
+ * Equality matches with paths completely distinct from these paths are ignored.
+ *
+ * For a full equality match, the path of an equality found must not be a suffix of one of
+ * the specified path - otherwise it isn't clear how to construct a full value for a field
+ * at that path.
+ *
+ * Generally this is useful for shard keys and _ids which need unambiguous extraction from
+ * queries.
+ *
+ * Ex:
+ * { a : 1 }, full path 'a' -> a $eq 1 extracted
+ * { a : 1 }, full path 'a.b' -> a $eq 1 extracted
+ * { 'a.b' : 1 }, full path 'a' -> NotExactValueField error
+ * ('a.b' doesn't specify 'a' fully)
+ * { 'a.b' : 1 }, full path 'a.b' -> 'a.b' $eq 1 extracted
+ * { '_id' : 1 }, full path '_id' -> '_id' $eq 1 extracted
+ * { '_id.x' : 1 }, full path '_id' -> NotExactValueFieldError
+ */
+Status extractFullEqualityMatches(const MatchExpression& root,
+ const FieldRefSet& fullPathsToExtract,
+ EqualityMatches* equalities);
+
+/**
+ * Returns the equality match which is at or a parent of the specified path string. The
+ * path string must be a valid dotted path.
+ *
+ * If a parent equality is found, returns the BSONElement data from that equality (which
+ * includes the BSON value), the path of the parent element (prefixStr), and the remainder
+ * of the path (which may be empty).
+ *
+ * EOO() is returned if there were no equalities at any point along the path.
+ *
+ * Ex:
+ * Given equality matches of:
+ * 'a.b' : 1, 'c' : 2
+ * Path 'a' has no equality match parent (EOO)
+ * Path 'c' has an eqmatch parent of 'c' : 2
+ * Path 'c.d' has an eqmatch parent of 'c' : 2
+ * Path 'a.b' has an eqmatch parent of 'a.b' : 1
+ * Path 'a.b.c' has an eqmatch parent of 'a.b' : 1
+ *
+ */
+const BSONElement& findParentEqualityElement(const EqualityMatches& equalities,
+ const FieldRef& path,
+ int* parentPathParts);
+
+/**
+ * Adds the BSON values from equality matches into the given document at the equality match
+ * paths.
+ *
+ * Returns PathNotViable similar to setElementAtPath above. If equality paths do not
+ * conflict, as is enforced by extractEqualityMatches, this function should return OK.
+ */
+Status addEqualitiesToDoc(const EqualityMatches& equalities, mutablebson::Document* doc);
+
+} // namespace pathsupport
+
+} // namespace mongo
diff --git a/src/mongo/db/ops/path_support_test.cpp b/src/mongo/db/ops/path_support_test.cpp
index a502a39a0c3..62e6ded46c2 100644
--- a/src/mongo/db/ops/path_support_test.cpp
+++ b/src/mongo/db/ops/path_support_test.cpp
@@ -49,834 +49,852 @@
namespace {
- using namespace mongo;
- using namespace mutablebson;
- using namespace pathsupport;
- using mongoutils::str::stream;
- using std::unique_ptr;
- using std::string;
+using namespace mongo;
+using namespace mutablebson;
+using namespace pathsupport;
+using mongoutils::str::stream;
+using std::unique_ptr;
+using std::string;
- class EmptyDoc : public mongo::unittest::Test {
- public:
- EmptyDoc() : _doc() {}
+class EmptyDoc : public mongo::unittest::Test {
+public:
+ EmptyDoc() : _doc() {}
- Document& doc() { return _doc; }
-
- Element root() { return _doc.root(); }
-
- FieldRef& field() { return _field; }
-
- void setField(StringData str) { _field.parse(str); }
-
- private:
- Document _doc;
- FieldRef _field;
- };
-
- TEST_F(EmptyDoc, EmptyPath) {
- setField("");
-
- size_t idxFound;
- Element elemFound = root();
- Status status = findLongestPrefix(field(), root(), &idxFound, &elemFound);
- ASSERT_EQUALS(status, ErrorCodes::NonExistentPath);
- }
-
- TEST_F(EmptyDoc, NewField) {
- setField("a");
-
- size_t idxFound;
- Element elemFound = root();
- Status status = findLongestPrefix(field(), root(), &idxFound, &elemFound);
- ASSERT_EQUALS(status, ErrorCodes::NonExistentPath);
-
- Element newElem = doc().makeElementInt("a", 1);
- ASSERT_TRUE(newElem.ok());
- ASSERT_OK(createPathAt(field(), 0, root(), newElem));
- ASSERT_EQUALS(fromjson("{a: 1}"), doc());
- }
-
- class SimpleDoc : public mongo::unittest::Test {
- public:
- SimpleDoc() : _doc() {}
-
- virtual void setUp() {
- // {a: 1}
- ASSERT_OK(root().appendInt("a", 1));
- }
-
- Document& doc() { return _doc; }
-
- Element root() { return _doc.root(); }
-
- FieldRef& field() { return _field; }
- void setField(StringData str) { _field.parse(str); }
-
- private:
- Document _doc;
- FieldRef _field;
- };
-
- TEST_F(SimpleDoc, EmptyPath) {
- setField("");
-
- size_t idxFound;
- Element elemFound = root();
- Status status = findLongestPrefix(field(), root(), &idxFound, &elemFound);
- ASSERT_EQUALS(status, ErrorCodes::NonExistentPath);
- }
-
- TEST_F(SimpleDoc, SimplePath) {
- setField("a");
-
- size_t idxFound;
- Element elemFound = root();
- ASSERT_OK(findLongestPrefix(field(), root(), &idxFound, &elemFound));
- ASSERT_TRUE(elemFound.ok());
- ASSERT_EQUALS(idxFound, 0U);
- ASSERT_EQUALS(elemFound.compareWithElement(root()["a"]), 0);
- }
-
- TEST_F(SimpleDoc, LongerPath) {
- setField("a.b");
-
- size_t idxFound;
- Element elemFound = root();
- Status status = findLongestPrefix(field(), root(), &idxFound, &elemFound);
- ASSERT_EQUALS(status, ErrorCodes::PathNotViable);
- ASSERT_TRUE(elemFound.ok());
- ASSERT_EQUALS(idxFound, 0U);
- ASSERT_EQUALS(elemFound.compareWithElement(root()["a"]), 0);
+ Document& doc() {
+ return _doc;
}
- TEST_F(SimpleDoc, NotCommonPrefix) {
- setField("b");
-
- size_t idxFound;
- Element elemFound = root();
- Status status = findLongestPrefix(field(), root(), &idxFound, &elemFound);
- ASSERT_EQUALS(status, ErrorCodes::NonExistentPath);
-
- // From this point on, handles the creation of the '.b' part that wasn't found.
- Element newElem = doc().makeElementInt("b", 1);
- ASSERT_TRUE(newElem.ok());
- ASSERT_EQUALS(countChildren(root()), 1u);
-
- ASSERT_OK(createPathAt(field(), 0, root(), newElem));
- ASSERT_EQUALS(newElem.getFieldName(), "b");
- ASSERT_EQUALS(newElem.getType(), NumberInt);
- ASSERT_TRUE(newElem.hasValue());
- ASSERT_EQUALS(newElem.getValueInt(), 1);
-
- ASSERT_TRUE(newElem.parent().ok() /* root an ok parent */);
- ASSERT_EQUALS(countChildren(root()), 2u);
- ASSERT_EQUALS(root().leftChild().getFieldName(), "a");
- ASSERT_EQUALS(root().leftChild().rightSibling().getFieldName(), "b");
- ASSERT_EQUALS(root().rightChild().getFieldName(), "b");
- ASSERT_EQUALS(root().rightChild().leftSibling().getFieldName(), "a");
+ Element root() {
+ return _doc.root();
}
- class NestedDoc : public mongo::unittest::Test {
- public:
- NestedDoc() : _doc() {}
-
- virtual void setUp() {
- // {a: {b: {c: 1}}}
- Element elemA = _doc.makeElementObject("a");
- ASSERT_TRUE(elemA.ok());
- Element elemB = _doc.makeElementObject("b");
- ASSERT_TRUE(elemB.ok());
- Element elemC = _doc.makeElementInt("c", 1);
- ASSERT_TRUE(elemC.ok());
-
- ASSERT_OK(elemB.pushBack(elemC));
- ASSERT_OK(elemA.pushBack(elemB));
- ASSERT_OK(root().pushBack(elemA));
- }
-
- Document& doc() { return _doc; }
-
- Element root() { return _doc.root(); }
-
- FieldRef& field() { return _field; }
- void setField(StringData str) { _field.parse(str); }
-
- private:
- Document _doc;
- FieldRef _field;
- };
-
- TEST_F(NestedDoc, SimplePath) {
- setField("a");
-
- size_t idxFound;
- Element elemFound = root();
- ASSERT_OK(findLongestPrefix(field(), root(), &idxFound, &elemFound));
- ASSERT_TRUE(elemFound.ok());
- ASSERT_EQUALS(idxFound, 0U);
- ASSERT_EQUALS(elemFound.compareWithElement(root()["a"]), 0);
+ FieldRef& field() {
+ return _field;
}
- TEST_F(NestedDoc, ShorterPath) {
- setField("a.b");
-
- size_t idxFound;
- Element elemFound = root();
- ASSERT_OK(findLongestPrefix(field(), root(), &idxFound, &elemFound));
- ASSERT_EQUALS(idxFound, 1U);
- ASSERT_EQUALS(elemFound.compareWithElement(root()["a"]["b"]), 0);
+ void setField(StringData str) {
+ _field.parse(str);
}
- TEST_F(NestedDoc, ExactPath) {
- setField("a.b.c");
+private:
+ Document _doc;
+ FieldRef _field;
+};
- size_t idxFound;
- Element elemFound = root();
- ASSERT_OK(findLongestPrefix(field(), root(), &idxFound, &elemFound));
- ASSERT_TRUE(elemFound.ok());
- ASSERT_EQUALS(idxFound, 2U);
- ASSERT_EQUALS(elemFound.compareWithElement(root()["a"]["b"]["c"]), 0);
- }
-
- TEST_F(NestedDoc, LongerPath) {
- // This would for 'c' to change from NumberInt to Object, which is invalid.
- setField("a.b.c.d");
+TEST_F(EmptyDoc, EmptyPath) {
+ setField("");
- size_t idxFound;
- Element elemFound = root();
- Status status = findLongestPrefix(field(), root(), &idxFound, &elemFound);
- ASSERT_EQUALS(status.code(), ErrorCodes::PathNotViable);
- ASSERT_TRUE(elemFound.ok());
- ASSERT_EQUALS(idxFound, 2U);
- ASSERT_EQUALS(elemFound.compareWithElement(root()["a"]["b"]["c"]), 0);
+ size_t idxFound;
+ Element elemFound = root();
+ Status status = findLongestPrefix(field(), root(), &idxFound, &elemFound);
+ ASSERT_EQUALS(status, ErrorCodes::NonExistentPath);
+}
- }
+TEST_F(EmptyDoc, NewField) {
+ setField("a");
- TEST_F(NestedDoc, NewFieldNested) {
- setField("a.b.d");
+ size_t idxFound;
+ Element elemFound = root();
+ Status status = findLongestPrefix(field(), root(), &idxFound, &elemFound);
+ ASSERT_EQUALS(status, ErrorCodes::NonExistentPath);
- size_t idxFound;
- Element elemFound = root();
- ASSERT_OK(findLongestPrefix(field(), root(), &idxFound, &elemFound));
- ASSERT_EQUALS(idxFound, 1U);
- ASSERT_EQUALS(elemFound.compareWithElement(root()["a"]["b"]), 0);
+ Element newElem = doc().makeElementInt("a", 1);
+ ASSERT_TRUE(newElem.ok());
+ ASSERT_OK(createPathAt(field(), 0, root(), newElem));
+ ASSERT_EQUALS(fromjson("{a: 1}"), doc());
+}
- // From this point on, handles the creation of the '.d' part that wasn't found.
- Element newElem = doc().makeElementInt("d", 1);
- ASSERT_TRUE(newElem.ok());
- ASSERT_EQUALS(countChildren(elemFound), 1u); // 'c' is a child of 'b'
+class SimpleDoc : public mongo::unittest::Test {
+public:
+ SimpleDoc() : _doc() {}
- ASSERT_OK(createPathAt(field(), idxFound+1, elemFound, newElem));
- ASSERT_EQUALS(fromjson("{a: {b: {c: 1, d: 1}}}"), doc());
+ virtual void setUp() {
+ // {a: 1}
+ ASSERT_OK(root().appendInt("a", 1));
}
- TEST_F(NestedDoc, NotStartingFromRoot) {
- setField("b.c");
-
- size_t idxFound;
- Element elemFound = root();
- ASSERT_OK(findLongestPrefix(field(), root()["a"], &idxFound, &elemFound));
- ASSERT_EQUALS(idxFound, 1U);
- ASSERT_EQUALS(elemFound.compareWithElement(root()["a"]["b"]["c"]), 0);
+ Document& doc() {
+ return _doc;
}
- class ArrayDoc : public mongo::unittest::Test {
- public:
- ArrayDoc() : _doc() {}
-
- virtual void setUp() {
- // {a: []}
- Element elemA = _doc.makeElementArray("a");
- ASSERT_TRUE(elemA.ok());
- ASSERT_OK(root().pushBack(elemA));
-
- // {a: [], b: [{c: 1}]}
- Element elemB = _doc.makeElementArray("b");
- ASSERT_TRUE(elemB.ok());
- Element elemObj = _doc.makeElementObject("dummy" /* field name not used in array */);
- ASSERT_TRUE(elemObj.ok());
- ASSERT_OK(elemObj.appendInt("c",1));
- ASSERT_OK(elemB.pushBack(elemObj));
- ASSERT_OK(root().pushBack(elemB));
- }
-
- Document& doc() { return _doc; }
-
- Element root() { return _doc.root(); }
-
- FieldRef& field() { return _field; }
-
- void setField(StringData str) { _field.parse(str); }
-
- private:
- Document _doc;
- FieldRef _field;
- };
-
- TEST_F(ArrayDoc, PathOnEmptyArray) {
- setField("a.0");
-
- size_t idxFound;
- Element elemFound = root();
- ASSERT_OK(findLongestPrefix(field(), root(), &idxFound, &elemFound));
- ASSERT_TRUE(elemFound.ok());
- ASSERT_EQUALS(idxFound, 0U);
- ASSERT_EQUALS(elemFound.compareWithElement(root()["a"]), 0);
+ Element root() {
+ return _doc.root();
}
- TEST_F(ArrayDoc, PathOnPopulatedArray) {
- setField("b.0");
-
- size_t idxFound;
- Element elemFound = root();
- ASSERT_OK(findLongestPrefix(field(), root(), &idxFound, &elemFound));
- ASSERT_TRUE(elemFound.ok());
- ASSERT_EQUALS(idxFound, 1U);
- ASSERT_EQUALS(elemFound.compareWithElement(root()["b"][0]), 0);
+ FieldRef& field() {
+ return _field;
}
-
- TEST_F(ArrayDoc, MixedArrayAndObjectPath) {
- setField("b.0.c");
-
- size_t idxFound;
- Element elemFound = root();
- ASSERT_OK(findLongestPrefix(field(), root(), &idxFound, &elemFound));
- ASSERT_TRUE(elemFound.ok());
- ASSERT_EQUALS(idxFound, 2U);
- ASSERT_EQUALS(elemFound.compareWithElement(root()["b"][0]["c"]), 0);
+ void setField(StringData str) {
+ _field.parse(str);
}
- TEST_F(ArrayDoc, ExtendingExistingObject) {
- setField("b.0.d");
-
- size_t idxFound;
- Element elemFound = root();
- ASSERT_OK(findLongestPrefix(field(), root(), &idxFound, &elemFound));
- ASSERT_TRUE(elemFound.ok());
- ASSERT_EQUALS(idxFound, 1U);
- ASSERT_EQUALS(elemFound.compareWithElement(root()["b"][0]), 0);
+private:
+ Document _doc;
+ FieldRef _field;
+};
- // From this point on, handles the creation of the '.0.d' part that wasn't found.
- Element newElem = doc().makeElementInt("d", 1);
- ASSERT_TRUE(newElem.ok());
- ASSERT_EQUALS(countChildren(elemFound), 1u); // '{c:1}' is a child of b.0
+TEST_F(SimpleDoc, EmptyPath) {
+ setField("");
- ASSERT_OK(createPathAt(field(), idxFound+1, elemFound, newElem));
- ASSERT_EQUALS(fromjson("{a: [], b: [{c:1, d:1}]}"), doc());
- }
+ size_t idxFound;
+ Element elemFound = root();
+ Status status = findLongestPrefix(field(), root(), &idxFound, &elemFound);
+ ASSERT_EQUALS(status, ErrorCodes::NonExistentPath);
+}
- TEST_F(ArrayDoc, NewObjectInsideArray) {
- setField("b.1.c");
+TEST_F(SimpleDoc, SimplePath) {
+ setField("a");
- size_t idxFound;
- Element elemFound = root();
- ASSERT_OK(findLongestPrefix(field(), root(), &idxFound, &elemFound));
- ASSERT_TRUE(elemFound.ok());
- ASSERT_EQUALS(idxFound, 0U);
- ASSERT_EQUALS(elemFound.compareWithElement(root()["b"]), 0);
+ size_t idxFound;
+ Element elemFound = root();
+ ASSERT_OK(findLongestPrefix(field(), root(), &idxFound, &elemFound));
+ ASSERT_TRUE(elemFound.ok());
+ ASSERT_EQUALS(idxFound, 0U);
+ ASSERT_EQUALS(elemFound.compareWithElement(root()["a"]), 0);
+}
- // From this point on, handles the creation of the '.1.c' part that wasn't found.
- Element newElem = doc().makeElementInt("c", 2);
- ASSERT_TRUE(newElem.ok());
- ASSERT_EQUALS(countChildren(elemFound), 1u); // '{c:1}' is a child of 'b'
-
- ASSERT_OK(createPathAt(field(), idxFound+1, elemFound, newElem));
- ASSERT_EQUALS(fromjson("{a: [], b: [{c:1},{c:2}]}"), doc());
- }
+TEST_F(SimpleDoc, LongerPath) {
+ setField("a.b");
- TEST_F(ArrayDoc, NewNestedObjectInsideArray) {
- setField("b.1.c.d");
+ size_t idxFound;
+ Element elemFound = root();
+ Status status = findLongestPrefix(field(), root(), &idxFound, &elemFound);
+ ASSERT_EQUALS(status, ErrorCodes::PathNotViable);
+ ASSERT_TRUE(elemFound.ok());
+ ASSERT_EQUALS(idxFound, 0U);
+ ASSERT_EQUALS(elemFound.compareWithElement(root()["a"]), 0);
+}
- size_t idxFound;
- Element elemFound = root();
- ASSERT_OK(findLongestPrefix(field(), root(), &idxFound, &elemFound));
- ASSERT_TRUE(elemFound.ok());
- ASSERT_EQUALS(idxFound, 0U);
- ASSERT_EQUALS(elemFound.compareWithElement(root()["b"]), 0);
+TEST_F(SimpleDoc, NotCommonPrefix) {
+ setField("b");
- // From this point on, handles the creation of the '.1.c.d' part that wasn't found.
- Element newElem = doc().makeElementInt("d", 2);
- ASSERT_TRUE(newElem.ok());
- ASSERT_EQUALS(countChildren(elemFound), 1u); // '{c:1}' is a child of 'b'
+ size_t idxFound;
+ Element elemFound = root();
+ Status status = findLongestPrefix(field(), root(), &idxFound, &elemFound);
+ ASSERT_EQUALS(status, ErrorCodes::NonExistentPath);
+
+ // From this point on, handles the creation of the '.b' part that wasn't found.
+ Element newElem = doc().makeElementInt("b", 1);
+ ASSERT_TRUE(newElem.ok());
+ ASSERT_EQUALS(countChildren(root()), 1u);
+
+ ASSERT_OK(createPathAt(field(), 0, root(), newElem));
+ ASSERT_EQUALS(newElem.getFieldName(), "b");
+ ASSERT_EQUALS(newElem.getType(), NumberInt);
+ ASSERT_TRUE(newElem.hasValue());
+ ASSERT_EQUALS(newElem.getValueInt(), 1);
+
+ ASSERT_TRUE(newElem.parent().ok() /* root an ok parent */);
+ ASSERT_EQUALS(countChildren(root()), 2u);
+ ASSERT_EQUALS(root().leftChild().getFieldName(), "a");
+ ASSERT_EQUALS(root().leftChild().rightSibling().getFieldName(), "b");
+ ASSERT_EQUALS(root().rightChild().getFieldName(), "b");
+ ASSERT_EQUALS(root().rightChild().leftSibling().getFieldName(), "a");
+}
+
+class NestedDoc : public mongo::unittest::Test {
+public:
+ NestedDoc() : _doc() {}
+
+ virtual void setUp() {
+ // {a: {b: {c: 1}}}
+ Element elemA = _doc.makeElementObject("a");
+ ASSERT_TRUE(elemA.ok());
+ Element elemB = _doc.makeElementObject("b");
+ ASSERT_TRUE(elemB.ok());
+ Element elemC = _doc.makeElementInt("c", 1);
+ ASSERT_TRUE(elemC.ok());
+
+ ASSERT_OK(elemB.pushBack(elemC));
+ ASSERT_OK(elemA.pushBack(elemB));
+ ASSERT_OK(root().pushBack(elemA));
+ }
+
+ Document& doc() {
+ return _doc;
+ }
+
+ Element root() {
+ return _doc.root();
+ }
+
+ FieldRef& field() {
+ return _field;
+ }
+ void setField(StringData str) {
+ _field.parse(str);
+ }
+
+private:
+ Document _doc;
+ FieldRef _field;
+};
+
+TEST_F(NestedDoc, SimplePath) {
+ setField("a");
+
+ size_t idxFound;
+ Element elemFound = root();
+ ASSERT_OK(findLongestPrefix(field(), root(), &idxFound, &elemFound));
+ ASSERT_TRUE(elemFound.ok());
+ ASSERT_EQUALS(idxFound, 0U);
+ ASSERT_EQUALS(elemFound.compareWithElement(root()["a"]), 0);
+}
+
+TEST_F(NestedDoc, ShorterPath) {
+ setField("a.b");
+
+ size_t idxFound;
+ Element elemFound = root();
+ ASSERT_OK(findLongestPrefix(field(), root(), &idxFound, &elemFound));
+ ASSERT_EQUALS(idxFound, 1U);
+ ASSERT_EQUALS(elemFound.compareWithElement(root()["a"]["b"]), 0);
+}
+
+TEST_F(NestedDoc, ExactPath) {
+ setField("a.b.c");
+
+ size_t idxFound;
+ Element elemFound = root();
+ ASSERT_OK(findLongestPrefix(field(), root(), &idxFound, &elemFound));
+ ASSERT_TRUE(elemFound.ok());
+ ASSERT_EQUALS(idxFound, 2U);
+ ASSERT_EQUALS(elemFound.compareWithElement(root()["a"]["b"]["c"]), 0);
+}
+
+TEST_F(NestedDoc, LongerPath) {
+ // This would for 'c' to change from NumberInt to Object, which is invalid.
+ setField("a.b.c.d");
+
+ size_t idxFound;
+ Element elemFound = root();
+ Status status = findLongestPrefix(field(), root(), &idxFound, &elemFound);
+ ASSERT_EQUALS(status.code(), ErrorCodes::PathNotViable);
+ ASSERT_TRUE(elemFound.ok());
+ ASSERT_EQUALS(idxFound, 2U);
+ ASSERT_EQUALS(elemFound.compareWithElement(root()["a"]["b"]["c"]), 0);
+}
+
+TEST_F(NestedDoc, NewFieldNested) {
+ setField("a.b.d");
+
+ size_t idxFound;
+ Element elemFound = root();
+ ASSERT_OK(findLongestPrefix(field(), root(), &idxFound, &elemFound));
+ ASSERT_EQUALS(idxFound, 1U);
+ ASSERT_EQUALS(elemFound.compareWithElement(root()["a"]["b"]), 0);
+
+ // From this point on, handles the creation of the '.d' part that wasn't found.
+ Element newElem = doc().makeElementInt("d", 1);
+ ASSERT_TRUE(newElem.ok());
+ ASSERT_EQUALS(countChildren(elemFound), 1u); // 'c' is a child of 'b'
+
+ ASSERT_OK(createPathAt(field(), idxFound + 1, elemFound, newElem));
+ ASSERT_EQUALS(fromjson("{a: {b: {c: 1, d: 1}}}"), doc());
+}
+
+TEST_F(NestedDoc, NotStartingFromRoot) {
+ setField("b.c");
+
+ size_t idxFound;
+ Element elemFound = root();
+ ASSERT_OK(findLongestPrefix(field(), root()["a"], &idxFound, &elemFound));
+ ASSERT_EQUALS(idxFound, 1U);
+ ASSERT_EQUALS(elemFound.compareWithElement(root()["a"]["b"]["c"]), 0);
+}
+
+class ArrayDoc : public mongo::unittest::Test {
+public:
+ ArrayDoc() : _doc() {}
+
+ virtual void setUp() {
+ // {a: []}
+ Element elemA = _doc.makeElementArray("a");
+ ASSERT_TRUE(elemA.ok());
+ ASSERT_OK(root().pushBack(elemA));
+
+ // {a: [], b: [{c: 1}]}
+ Element elemB = _doc.makeElementArray("b");
+ ASSERT_TRUE(elemB.ok());
+ Element elemObj = _doc.makeElementObject("dummy" /* field name not used in array */);
+ ASSERT_TRUE(elemObj.ok());
+ ASSERT_OK(elemObj.appendInt("c", 1));
+ ASSERT_OK(elemB.pushBack(elemObj));
+ ASSERT_OK(root().pushBack(elemB));
+ }
+
+ Document& doc() {
+ return _doc;
+ }
+
+ Element root() {
+ return _doc.root();
+ }
+
+ FieldRef& field() {
+ return _field;
+ }
+
+ void setField(StringData str) {
+ _field.parse(str);
+ }
+
+private:
+ Document _doc;
+ FieldRef _field;
+};
+
+TEST_F(ArrayDoc, PathOnEmptyArray) {
+ setField("a.0");
+
+ size_t idxFound;
+ Element elemFound = root();
+ ASSERT_OK(findLongestPrefix(field(), root(), &idxFound, &elemFound));
+ ASSERT_TRUE(elemFound.ok());
+ ASSERT_EQUALS(idxFound, 0U);
+ ASSERT_EQUALS(elemFound.compareWithElement(root()["a"]), 0);
+}
+
+TEST_F(ArrayDoc, PathOnPopulatedArray) {
+ setField("b.0");
+
+ size_t idxFound;
+ Element elemFound = root();
+ ASSERT_OK(findLongestPrefix(field(), root(), &idxFound, &elemFound));
+ ASSERT_TRUE(elemFound.ok());
+ ASSERT_EQUALS(idxFound, 1U);
+ ASSERT_EQUALS(elemFound.compareWithElement(root()["b"][0]), 0);
+}
+
+TEST_F(ArrayDoc, MixedArrayAndObjectPath) {
+ setField("b.0.c");
+
+ size_t idxFound;
+ Element elemFound = root();
+ ASSERT_OK(findLongestPrefix(field(), root(), &idxFound, &elemFound));
+ ASSERT_TRUE(elemFound.ok());
+ ASSERT_EQUALS(idxFound, 2U);
+ ASSERT_EQUALS(elemFound.compareWithElement(root()["b"][0]["c"]), 0);
+}
+
+TEST_F(ArrayDoc, ExtendingExistingObject) {
+ setField("b.0.d");
+
+ size_t idxFound;
+ Element elemFound = root();
+ ASSERT_OK(findLongestPrefix(field(), root(), &idxFound, &elemFound));
+ ASSERT_TRUE(elemFound.ok());
+ ASSERT_EQUALS(idxFound, 1U);
+ ASSERT_EQUALS(elemFound.compareWithElement(root()["b"][0]), 0);
+
+ // From this point on, handles the creation of the '.0.d' part that wasn't found.
+ Element newElem = doc().makeElementInt("d", 1);
+ ASSERT_TRUE(newElem.ok());
+ ASSERT_EQUALS(countChildren(elemFound), 1u); // '{c:1}' is a child of b.0
+
+ ASSERT_OK(createPathAt(field(), idxFound + 1, elemFound, newElem));
+ ASSERT_EQUALS(fromjson("{a: [], b: [{c:1, d:1}]}"), doc());
+}
+
+TEST_F(ArrayDoc, NewObjectInsideArray) {
+ setField("b.1.c");
+
+ size_t idxFound;
+ Element elemFound = root();
+ ASSERT_OK(findLongestPrefix(field(), root(), &idxFound, &elemFound));
+ ASSERT_TRUE(elemFound.ok());
+ ASSERT_EQUALS(idxFound, 0U);
+ ASSERT_EQUALS(elemFound.compareWithElement(root()["b"]), 0);
+
+ // From this point on, handles the creation of the '.1.c' part that wasn't found.
+ Element newElem = doc().makeElementInt("c", 2);
+ ASSERT_TRUE(newElem.ok());
+ ASSERT_EQUALS(countChildren(elemFound), 1u); // '{c:1}' is a child of 'b'
+
+ ASSERT_OK(createPathAt(field(), idxFound + 1, elemFound, newElem));
+ ASSERT_EQUALS(fromjson("{a: [], b: [{c:1},{c:2}]}"), doc());
+}
+
+TEST_F(ArrayDoc, NewNestedObjectInsideArray) {
+ setField("b.1.c.d");
+
+ size_t idxFound;
+ Element elemFound = root();
+ ASSERT_OK(findLongestPrefix(field(), root(), &idxFound, &elemFound));
+ ASSERT_TRUE(elemFound.ok());
+ ASSERT_EQUALS(idxFound, 0U);
+ ASSERT_EQUALS(elemFound.compareWithElement(root()["b"]), 0);
+
+ // From this point on, handles the creation of the '.1.c.d' part that wasn't found.
+ Element newElem = doc().makeElementInt("d", 2);
+ ASSERT_TRUE(newElem.ok());
+ ASSERT_EQUALS(countChildren(elemFound), 1u); // '{c:1}' is a child of 'b'
+
+ ASSERT_OK(createPathAt(field(), idxFound + 1, elemFound, newElem));
+ ASSERT_EQUALS(fromjson("{a: [], b: [{c:1},{c:{d:2}}]}"), doc());
+}
+
+TEST_F(ArrayDoc, ArrayPaddingNecessary) {
+ setField("b.5");
+
+ size_t idxFound;
+ Element elemFound = root();
+ ASSERT_OK(findLongestPrefix(field(), root(), &idxFound, &elemFound));
+ ASSERT_TRUE(elemFound.ok());
+ ASSERT_EQUALS(idxFound, 0U);
+ ASSERT_EQUALS(elemFound.compareWithElement(root()["b"]), 0);
+
+ // From this point on, handles the creation of the '.5' part that wasn't found.
+ Element newElem = doc().makeElementInt("", 1);
+ ASSERT_TRUE(newElem.ok());
+ ASSERT_EQUALS(countChildren(elemFound), 1u); // '{c:1}' is a child of 'b'
+
+ ASSERT_OK(createPathAt(field(), idxFound + 1, elemFound, newElem));
+ ASSERT_EQUALS(fromjson("{a: [], b: [{c:1},null,null,null,null,1]}"), doc());
+}
+
+TEST_F(ArrayDoc, ExcessivePaddingRequested) {
+ // Try to create an array item beyond what we're allowed to pad.
+ string paddedField = stream() << "b." << mongo::pathsupport::kMaxPaddingAllowed + 1;
+ ;
+ setField(paddedField);
+
+ size_t idxFound;
+ Element elemFound = root();
+ ASSERT_OK(findLongestPrefix(field(), root(), &idxFound, &elemFound));
+
+ // From this point on, try to create the padded part that wasn't found.
+ Element newElem = doc().makeElementInt("", 1);
+ ASSERT_TRUE(newElem.ok());
+ ASSERT_EQUALS(countChildren(elemFound), 1u); // '{c:1}' is a child of 'b'
+
+ Status status = createPathAt(field(), idxFound + 1, elemFound, newElem);
+ ASSERT_EQUALS(status.code(), ErrorCodes::CannotBackfillArray);
+}
+
+TEST_F(ArrayDoc, NonNumericPathInArray) {
+ setField("b.z");
+
+ size_t idxFound;
+ Element elemFound = root();
+ Status status = findLongestPrefix(field(), root(), &idxFound, &elemFound);
+ ASSERT_EQUALS(status.code(), ErrorCodes::PathNotViable);
+ ASSERT_TRUE(elemFound.ok());
+ ASSERT_EQUALS(idxFound, 0U);
+ ASSERT_EQUALS(elemFound.compareWithElement(root()["b"]), 0);
+}
+
+//
+// Tests of equality extraction from MatchExpressions
+// NONGOAL: Testing query/match expression parsing and optimization
+//
+
+static MatchExpression* makeExpr(const BSONObj& exprBSON) {
+ static const WhereCallbackNoop callbackNoop;
+ return MatchExpressionParser::parse(exprBSON, callbackNoop).getValue();
+}
+
+static void assertContains(const EqualityMatches& equalities, const BSONObj& wrapped) {
+ BSONElement value = wrapped.firstElement();
+ StringData path = value.fieldNameStringData();
+
+ EqualityMatches::const_iterator it = equalities.find(path);
+ if (it == equalities.end()) {
+ FAIL(stream() << "Equality matches did not contain path \"" << path << "\"");
+ }
+ if (!it->second->getData().valuesEqual(value)) {
+ FAIL(stream() << "Equality match at path \"" << path << "\" contains value "
+ << it->second->getData() << ", not value " << value);
+ }
+}
+
+static void assertContains(const EqualityMatches& equalities, StringData path, int value) {
+ assertContains(equalities, BSON(path << value));
+}
+
+// NOTE: For tests below, BSONObj expr must exist for lifetime of MatchExpression
+
+TEST(ExtractEqualities, Basic) {
+ BSONObj exprBSON = fromjson("{a:1}");
+ unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
+
+ EqualityMatches equalities;
+ ASSERT_OK(extractEqualityMatches(*expr, &equalities));
+ ASSERT_EQUALS(equalities.size(), 1u);
+ assertContains(equalities, "a", 1);
+}
+
+TEST(ExtractEqualities, Multiple) {
+ BSONObj exprBSON = fromjson("{a:1, b:2}");
+ unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
+
+ EqualityMatches equalities;
+ ASSERT_OK(extractEqualityMatches(*expr, &equalities));
+ ASSERT_EQUALS(equalities.size(), 2u);
+ assertContains(equalities, "a", 1);
+ assertContains(equalities, "b", 2);
+}
+
+TEST(ExtractEqualities, EqOperator) {
+ BSONObj exprBSON = fromjson("{a:{$eq:1}}");
+ unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
+
+ EqualityMatches equalities;
+ ASSERT_OK(extractEqualityMatches(*expr, &equalities));
+ ASSERT_EQUALS(equalities.size(), 1u);
+ assertContains(equalities, "a", 1);
+}
+
+TEST(ExtractEqualities, AndOperator) {
+ BSONObj exprBSON = fromjson("{$and:[{a:{$eq:1}},{b:2}]}");
+ unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
+
+ EqualityMatches equalities;
+ ASSERT_OK(extractEqualityMatches(*expr, &equalities));
+ ASSERT_EQUALS(equalities.size(), 2u);
+ assertContains(equalities, "a", 1);
+ assertContains(equalities, "b", 2);
+}
+
+TEST(ExtractEqualities, NestedAndOperator) {
+ BSONObj exprBSON = fromjson("{$and:[{$and:[{a:{$eq:1}},{b:2}]},{c:3}]}");
+ unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
+
+ EqualityMatches equalities;
+ ASSERT_OK(extractEqualityMatches(*expr, &equalities));
+ ASSERT_EQUALS(equalities.size(), 3u);
+ assertContains(equalities, "a", 1);
+ assertContains(equalities, "b", 2);
+ assertContains(equalities, "c", 3);
+}
+
+TEST(ExtractEqualities, NestedPaths) {
+ BSONObj exprBSON = fromjson("{'a.a':1}");
+ unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
+
+ EqualityMatches equalities;
+ ASSERT_OK(extractEqualityMatches(*expr, &equalities));
+ ASSERT_EQUALS(equalities.size(), 1u);
+ assertContains(equalities, "a.a", 1);
+}
+
+TEST(ExtractEqualities, SiblingPaths) {
+ BSONObj exprBSON = fromjson("{'a.a':1,'a.b':{$eq:2}}");
+ unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
+
+ EqualityMatches equalities;
+ ASSERT_OK(extractEqualityMatches(*expr, &equalities));
+ ASSERT_EQUALS(equalities.size(), 2u);
+ assertContains(equalities, "a.a", 1);
+ assertContains(equalities, "a.b", 2);
+}
+
+TEST(ExtractEqualities, NestedAndNestedPaths) {
+ BSONObj exprBSON = fromjson("{$and:[{$and:[{'a.a':{$eq:1}},{'a.b':2}]},{'c.c.c':3}]}");
+ unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
+
+ EqualityMatches equalities;
+ ASSERT_OK(extractEqualityMatches(*expr, &equalities));
+ ASSERT_EQUALS(equalities.size(), 3u);
+ assertContains(equalities, "a.a", 1);
+ assertContains(equalities, "a.b", 2);
+ assertContains(equalities, "c.c.c", 3);
+}
+
+TEST(ExtractEqualities, IdOnly) {
+ BSONObj exprBSON = fromjson("{_id:1}");
+ unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
+
+ EqualityMatches equalities;
+ ASSERT_OK(extractEqualityMatches(*expr, &equalities));
+ ASSERT_EQUALS(equalities.size(), 1u);
+ assertContains(equalities, "_id", 1);
+}
- ASSERT_OK(createPathAt(field(), idxFound+1, elemFound, newElem));
- ASSERT_EQUALS(fromjson("{a: [], b: [{c:1},{c:{d:2}}]}"), doc());
- }
-
- TEST_F(ArrayDoc, ArrayPaddingNecessary) {
- setField("b.5");
-
- size_t idxFound;
- Element elemFound = root();
- ASSERT_OK(findLongestPrefix(field(), root(), &idxFound, &elemFound));
- ASSERT_TRUE(elemFound.ok());
- ASSERT_EQUALS(idxFound, 0U);
- ASSERT_EQUALS(elemFound.compareWithElement(root()["b"]), 0);
-
- // From this point on, handles the creation of the '.5' part that wasn't found.
- Element newElem = doc().makeElementInt("", 1);
- ASSERT_TRUE(newElem.ok());
- ASSERT_EQUALS(countChildren(elemFound), 1u); // '{c:1}' is a child of 'b'
-
- ASSERT_OK(createPathAt(field(), idxFound+1, elemFound, newElem));
- ASSERT_EQUALS(fromjson("{a: [], b: [{c:1},null,null,null,null,1]}"), doc());
- }
-
- TEST_F(ArrayDoc, ExcessivePaddingRequested) {
- // Try to create an array item beyond what we're allowed to pad.
- string paddedField = stream() << "b." << mongo::pathsupport::kMaxPaddingAllowed + 1;;
- setField(paddedField);
-
- size_t idxFound;
- Element elemFound = root();
- ASSERT_OK(findLongestPrefix(field(), root(), &idxFound, &elemFound));
-
- // From this point on, try to create the padded part that wasn't found.
- Element newElem = doc().makeElementInt("", 1);
- ASSERT_TRUE(newElem.ok());
- ASSERT_EQUALS(countChildren(elemFound), 1u); // '{c:1}' is a child of 'b'
-
- Status status = createPathAt(field(), idxFound+1, elemFound, newElem);
- ASSERT_EQUALS(status.code(), ErrorCodes::CannotBackfillArray);
- }
-
- TEST_F(ArrayDoc, NonNumericPathInArray) {
- setField("b.z");
-
- size_t idxFound;
- Element elemFound = root();
- Status status = findLongestPrefix(field(), root(), &idxFound, &elemFound);
- ASSERT_EQUALS(status.code(), ErrorCodes::PathNotViable);
- ASSERT_TRUE(elemFound.ok());
- ASSERT_EQUALS(idxFound, 0U);
- ASSERT_EQUALS(elemFound.compareWithElement(root()["b"]), 0);
- }
-
- //
- // Tests of equality extraction from MatchExpressions
- // NONGOAL: Testing query/match expression parsing and optimization
- //
-
- static MatchExpression* makeExpr(const BSONObj& exprBSON) {
- static const WhereCallbackNoop callbackNoop;
- return MatchExpressionParser::parse(exprBSON, callbackNoop).getValue();
- }
-
- static void assertContains(const EqualityMatches& equalities, const BSONObj& wrapped) {
-
- BSONElement value = wrapped.firstElement();
- StringData path = value.fieldNameStringData();
-
- EqualityMatches::const_iterator it = equalities.find(path);
- if (it == equalities.end()) {
- FAIL(stream() << "Equality matches did not contain path \"" << path << "\"");
- }
- if (!it->second->getData().valuesEqual(value)) {
- FAIL(stream() << "Equality match at path \"" << path << "\" contains value "
- << it->second->getData() << ", not value " << value);
- }
- }
-
- static void assertContains(const EqualityMatches& equalities,
- StringData path,
- int value) {
- assertContains(equalities, BSON(path << value));
- }
-
- // NOTE: For tests below, BSONObj expr must exist for lifetime of MatchExpression
-
- TEST(ExtractEqualities, Basic) {
- BSONObj exprBSON = fromjson("{a:1}");
- unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
-
- EqualityMatches equalities;
- ASSERT_OK(extractEqualityMatches(*expr, &equalities));
- ASSERT_EQUALS(equalities.size(), 1u);
- assertContains(equalities, "a", 1);
- }
-
- TEST(ExtractEqualities, Multiple) {
- BSONObj exprBSON = fromjson("{a:1, b:2}");
- unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
-
- EqualityMatches equalities;
- ASSERT_OK(extractEqualityMatches(*expr, &equalities));
- ASSERT_EQUALS(equalities.size(), 2u);
- assertContains(equalities, "a", 1);
- assertContains(equalities, "b", 2);
- }
-
- TEST(ExtractEqualities, EqOperator) {
- BSONObj exprBSON = fromjson("{a:{$eq:1}}");
- unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
-
- EqualityMatches equalities;
- ASSERT_OK(extractEqualityMatches(*expr, &equalities));
- ASSERT_EQUALS(equalities.size(), 1u);
- assertContains(equalities, "a", 1);
- }
-
- TEST(ExtractEqualities, AndOperator) {
- BSONObj exprBSON = fromjson("{$and:[{a:{$eq:1}},{b:2}]}");
- unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
-
- EqualityMatches equalities;
- ASSERT_OK(extractEqualityMatches(*expr, &equalities));
- ASSERT_EQUALS(equalities.size(), 2u);
- assertContains(equalities, "a", 1);
- assertContains(equalities, "b", 2);
- }
-
- TEST(ExtractEqualities, NestedAndOperator) {
- BSONObj exprBSON = fromjson("{$and:[{$and:[{a:{$eq:1}},{b:2}]},{c:3}]}");
- unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
-
- EqualityMatches equalities;
- ASSERT_OK(extractEqualityMatches(*expr, &equalities));
- ASSERT_EQUALS(equalities.size(), 3u);
- assertContains(equalities, "a", 1);
- assertContains(equalities, "b", 2);
- assertContains(equalities, "c", 3);
- }
-
- TEST(ExtractEqualities, NestedPaths) {
- BSONObj exprBSON = fromjson("{'a.a':1}");
- unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
-
- EqualityMatches equalities;
- ASSERT_OK(extractEqualityMatches(*expr, &equalities));
- ASSERT_EQUALS(equalities.size(), 1u);
- assertContains(equalities, "a.a", 1);
- }
-
- TEST(ExtractEqualities, SiblingPaths) {
- BSONObj exprBSON = fromjson("{'a.a':1,'a.b':{$eq:2}}");
- unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
-
- EqualityMatches equalities;
- ASSERT_OK(extractEqualityMatches(*expr, &equalities));
- ASSERT_EQUALS(equalities.size(), 2u);
- assertContains(equalities, "a.a", 1);
- assertContains(equalities, "a.b", 2);
- }
-
- TEST(ExtractEqualities, NestedAndNestedPaths) {
- BSONObj exprBSON = fromjson("{$and:[{$and:[{'a.a':{$eq:1}},{'a.b':2}]},{'c.c.c':3}]}");
- unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
-
- EqualityMatches equalities;
- ASSERT_OK(extractEqualityMatches(*expr, &equalities));
- ASSERT_EQUALS(equalities.size(), 3u);
- assertContains(equalities, "a.a", 1);
- assertContains(equalities, "a.b", 2);
- assertContains(equalities, "c.c.c", 3);
- }
-
- TEST(ExtractEqualities, IdOnly) {
- BSONObj exprBSON = fromjson("{_id:1}");
- unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
-
- EqualityMatches equalities;
- ASSERT_OK(extractEqualityMatches(*expr, &equalities));
- ASSERT_EQUALS(equalities.size(), 1u);
- assertContains(equalities, "_id", 1);
- }
-
- /**
- * Helper class to allow easy construction of immutable paths
- */
- class ImmutablePaths {
- public:
- ImmutablePaths() {}
-
- void addPath(const string& path) {
- _ownedPaths.mutableVector().push_back(new FieldRef(path));
- FieldRef const* conflictPath = NULL;
- ASSERT(_immutablePathSet.insert(_ownedPaths.vector().back(), &conflictPath));
- }
-
- const FieldRefSet& getPathSet() {
- return _immutablePathSet;
- }
-
- private:
-
- FieldRefSet _immutablePathSet;
- OwnedPointerVector<FieldRef> _ownedPaths;
- };
-
- TEST(ExtractEqualities, IdOnlyMulti) {
- BSONObj exprBSON = fromjson("{_id:{$eq:1},a:1}");
- unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
-
- ImmutablePaths immutablePaths;
- immutablePaths.addPath("_id");
-
- EqualityMatches equalities;
- ASSERT_OK(extractFullEqualityMatches(*expr, immutablePaths.getPathSet(), &equalities));
- ASSERT_EQUALS(equalities.size(), 1u);
- assertContains(equalities, "_id", 1);
- }
-
- TEST(ExtractEqualities, IdOnlyIgnoreConflict) {
- BSONObj exprBSON = fromjson("{_id:1,a:1,'a.b':1}");
- unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
-
- ImmutablePaths immutablePaths;
- immutablePaths.addPath("_id");
-
- EqualityMatches equalities;
- ASSERT_OK(extractFullEqualityMatches(*expr, immutablePaths.getPathSet(), &equalities));
- ASSERT_EQUALS(equalities.size(), 1u);
- assertContains(equalities, "_id", 1);
- }
-
- TEST(ExtractEqualities, IdOnlyNested) {
- BSONObj exprBSON = fromjson("{'_id.a':1,'_id.b':{$eq:2},c:3}");
- unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
-
- ImmutablePaths immutablePaths;
- immutablePaths.addPath("_id");
-
- EqualityMatches equalities;
- Status status = extractFullEqualityMatches(*expr, immutablePaths.getPathSet(), &equalities);
- ASSERT_EQUALS(status.code(), ErrorCodes::NotExactValueField);
- }
-
- TEST(ExtractEqualities, IdAndOtherImmutable) {
- BSONObj exprBSON = fromjson("{_id:1,a:1,b:2}");
- unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
-
- ImmutablePaths immutablePaths;
- immutablePaths.addPath("_id");
- immutablePaths.addPath("a");
-
- EqualityMatches equalities;
- ASSERT_OK(extractFullEqualityMatches(*expr, immutablePaths.getPathSet(), &equalities));
- ASSERT_EQUALS(equalities.size(), 2u);
- assertContains(equalities, "_id", 1);
- assertContains(equalities, "a", 1);
- }
-
- TEST(ExtractEqualities, IdAndNestedImmutable) {
- BSONObj exprBSON = fromjson("{_id:1,a:1,'c.d':3}");
- unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
-
- ImmutablePaths immutablePaths;
- immutablePaths.addPath("_id");
- immutablePaths.addPath("a.b");
- immutablePaths.addPath("c.d");
-
- EqualityMatches equalities;
- ASSERT_OK(extractFullEqualityMatches(*expr, immutablePaths.getPathSet(), &equalities));
- ASSERT_EQUALS(equalities.size(), 3u);
- assertContains(equalities, "_id", 1);
- assertContains(equalities, "a", 1);
- assertContains(equalities, "c.d", 3);
- }
-
- TEST(ExtractEqualities, NonFullImmutable) {
- BSONObj exprBSON = fromjson("{'a.b':1}");
- unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
-
- ImmutablePaths immutablePaths;
- immutablePaths.addPath("a");
-
- EqualityMatches equalities;
- Status status = extractFullEqualityMatches(*expr, immutablePaths.getPathSet(), &equalities);
- ASSERT_EQUALS(status.code(), ErrorCodes::NotExactValueField);
- }
-
- TEST(ExtractEqualities, Empty) {
- BSONObj exprBSON = fromjson("{'':0}");
- unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
-
- EqualityMatches equalities;
- ASSERT_OK(extractEqualityMatches(*expr, &equalities));
- ASSERT_EQUALS(equalities.size(), 1u);
- assertContains(equalities, "", 0);
- }
-
- TEST(ExtractEqualities, EmptyMulti) {
- BSONObj exprBSON = fromjson("{'':0,a:{$eq:1}}");
- unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
-
- EqualityMatches equalities;
- ASSERT_OK(extractEqualityMatches(*expr, &equalities));
- ASSERT_EQUALS(equalities.size(), 2u);
- assertContains(equalities, "", 0);
- assertContains(equalities, "a", 1);
- }
-
- TEST(ExtractEqualities, EqConflict) {
- BSONObj exprBSON = fromjson("{a:1,a:1}");
- unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
-
- EqualityMatches equalities;
- ASSERT_EQUALS(extractEqualityMatches(*expr, &equalities).code(),
- ErrorCodes::NotSingleValueField);
- }
-
- TEST(ExtractEqualities, PrefixConflict) {
- BSONObj exprBSON = fromjson("{a:1,'a.b':{$eq:1}}");
- unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
-
- EqualityMatches equalities;
- ASSERT_EQUALS(extractEqualityMatches(*expr, &equalities).code(),
- ErrorCodes::NotSingleValueField);
- }
-
- TEST(ExtractEqualities, AndPrefixConflict) {
- BSONObj exprBSON = fromjson("{$and:[{a:1},{'a.b':{$eq:1}}]}");
- unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
-
- EqualityMatches equalities;
- ASSERT_EQUALS(extractEqualityMatches(*expr, &equalities).code(),
- ErrorCodes::NotSingleValueField);
- }
-
- TEST(ExtractEqualities, EmptyConflict) {
- BSONObj exprBSON = fromjson("{'':0,'':{$eq:0}}");
- unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
-
- EqualityMatches equalities;
- ASSERT_EQUALS(extractEqualityMatches(*expr, &equalities).code(),
- ErrorCodes::NotSingleValueField);
- }
-
- //
- // Tests for finding parent equality from equalities found in expression
- // NONGOALS: Testing complex equality match extraction - tested above
- //
-
- static void assertParent(const EqualityMatches& equalities,
- StringData pathStr,
- const BSONObj& wrapped) {
-
- FieldRef path(pathStr);
- BSONElement value = wrapped.firstElement();
- StringData parentPath = value.fieldNameStringData();
-
- int parentPathPart;
- BSONElement parentEl = findParentEqualityElement(equalities, path, &parentPathPart);
-
- if (parentEl.eoo()) {
- FAIL(stream() << "Equality matches did not contain parent for \"" << pathStr
- << "\"");
- }
+/**
+ * Helper class to allow easy construction of immutable paths
+ */
+class ImmutablePaths {
+public:
+ ImmutablePaths() {}
+
+ void addPath(const string& path) {
+ _ownedPaths.mutableVector().push_back(new FieldRef(path));
+ FieldRef const* conflictPath = NULL;
+ ASSERT(_immutablePathSet.insert(_ownedPaths.vector().back(), &conflictPath));
+ }
+
+ const FieldRefSet& getPathSet() {
+ return _immutablePathSet;
+ }
+
+private:
+ FieldRefSet _immutablePathSet;
+ OwnedPointerVector<FieldRef> _ownedPaths;
+};
+
+TEST(ExtractEqualities, IdOnlyMulti) {
+ BSONObj exprBSON = fromjson("{_id:{$eq:1},a:1}");
+ unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
+
+ ImmutablePaths immutablePaths;
+ immutablePaths.addPath("_id");
+
+ EqualityMatches equalities;
+ ASSERT_OK(extractFullEqualityMatches(*expr, immutablePaths.getPathSet(), &equalities));
+ ASSERT_EQUALS(equalities.size(), 1u);
+ assertContains(equalities, "_id", 1);
+}
+
+TEST(ExtractEqualities, IdOnlyIgnoreConflict) {
+ BSONObj exprBSON = fromjson("{_id:1,a:1,'a.b':1}");
+ unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
+
+ ImmutablePaths immutablePaths;
+ immutablePaths.addPath("_id");
+
+ EqualityMatches equalities;
+ ASSERT_OK(extractFullEqualityMatches(*expr, immutablePaths.getPathSet(), &equalities));
+ ASSERT_EQUALS(equalities.size(), 1u);
+ assertContains(equalities, "_id", 1);
+}
+
+TEST(ExtractEqualities, IdOnlyNested) {
+ BSONObj exprBSON = fromjson("{'_id.a':1,'_id.b':{$eq:2},c:3}");
+ unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
+
+ ImmutablePaths immutablePaths;
+ immutablePaths.addPath("_id");
+
+ EqualityMatches equalities;
+ Status status = extractFullEqualityMatches(*expr, immutablePaths.getPathSet(), &equalities);
+ ASSERT_EQUALS(status.code(), ErrorCodes::NotExactValueField);
+}
+
+TEST(ExtractEqualities, IdAndOtherImmutable) {
+ BSONObj exprBSON = fromjson("{_id:1,a:1,b:2}");
+ unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
+
+ ImmutablePaths immutablePaths;
+ immutablePaths.addPath("_id");
+ immutablePaths.addPath("a");
+
+ EqualityMatches equalities;
+ ASSERT_OK(extractFullEqualityMatches(*expr, immutablePaths.getPathSet(), &equalities));
+ ASSERT_EQUALS(equalities.size(), 2u);
+ assertContains(equalities, "_id", 1);
+ assertContains(equalities, "a", 1);
+}
+
+TEST(ExtractEqualities, IdAndNestedImmutable) {
+ BSONObj exprBSON = fromjson("{_id:1,a:1,'c.d':3}");
+ unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
+
+ ImmutablePaths immutablePaths;
+ immutablePaths.addPath("_id");
+ immutablePaths.addPath("a.b");
+ immutablePaths.addPath("c.d");
+
+ EqualityMatches equalities;
+ ASSERT_OK(extractFullEqualityMatches(*expr, immutablePaths.getPathSet(), &equalities));
+ ASSERT_EQUALS(equalities.size(), 3u);
+ assertContains(equalities, "_id", 1);
+ assertContains(equalities, "a", 1);
+ assertContains(equalities, "c.d", 3);
+}
+
+TEST(ExtractEqualities, NonFullImmutable) {
+ BSONObj exprBSON = fromjson("{'a.b':1}");
+ unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
+
+ ImmutablePaths immutablePaths;
+ immutablePaths.addPath("a");
+
+ EqualityMatches equalities;
+ Status status = extractFullEqualityMatches(*expr, immutablePaths.getPathSet(), &equalities);
+ ASSERT_EQUALS(status.code(), ErrorCodes::NotExactValueField);
+}
+
+TEST(ExtractEqualities, Empty) {
+ BSONObj exprBSON = fromjson("{'':0}");
+ unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
+
+ EqualityMatches equalities;
+ ASSERT_OK(extractEqualityMatches(*expr, &equalities));
+ ASSERT_EQUALS(equalities.size(), 1u);
+ assertContains(equalities, "", 0);
+}
+
+TEST(ExtractEqualities, EmptyMulti) {
+ BSONObj exprBSON = fromjson("{'':0,a:{$eq:1}}");
+ unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
+
+ EqualityMatches equalities;
+ ASSERT_OK(extractEqualityMatches(*expr, &equalities));
+ ASSERT_EQUALS(equalities.size(), 2u);
+ assertContains(equalities, "", 0);
+ assertContains(equalities, "a", 1);
+}
+
+TEST(ExtractEqualities, EqConflict) {
+ BSONObj exprBSON = fromjson("{a:1,a:1}");
+ unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
+
+ EqualityMatches equalities;
+ ASSERT_EQUALS(extractEqualityMatches(*expr, &equalities).code(),
+ ErrorCodes::NotSingleValueField);
+}
+
+TEST(ExtractEqualities, PrefixConflict) {
+ BSONObj exprBSON = fromjson("{a:1,'a.b':{$eq:1}}");
+ unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
+
+ EqualityMatches equalities;
+ ASSERT_EQUALS(extractEqualityMatches(*expr, &equalities).code(),
+ ErrorCodes::NotSingleValueField);
+}
+
+TEST(ExtractEqualities, AndPrefixConflict) {
+ BSONObj exprBSON = fromjson("{$and:[{a:1},{'a.b':{$eq:1}}]}");
+ unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
+
+ EqualityMatches equalities;
+ ASSERT_EQUALS(extractEqualityMatches(*expr, &equalities).code(),
+ ErrorCodes::NotSingleValueField);
+}
+
+TEST(ExtractEqualities, EmptyConflict) {
+ BSONObj exprBSON = fromjson("{'':0,'':{$eq:0}}");
+ unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
+
+ EqualityMatches equalities;
+ ASSERT_EQUALS(extractEqualityMatches(*expr, &equalities).code(),
+ ErrorCodes::NotSingleValueField);
+}
+
+//
+// Tests for finding parent equality from equalities found in expression
+// NONGOALS: Testing complex equality match extraction - tested above
+//
+
+static void assertParent(const EqualityMatches& equalities,
+ StringData pathStr,
+ const BSONObj& wrapped) {
+ FieldRef path(pathStr);
+ BSONElement value = wrapped.firstElement();
+ StringData parentPath = value.fieldNameStringData();
+
+ int parentPathPart;
+ BSONElement parentEl = findParentEqualityElement(equalities, path, &parentPathPart);
+
+ if (parentEl.eoo()) {
+ FAIL(stream() << "Equality matches did not contain parent for \"" << pathStr << "\"");
+ }
+
+ StringData foundParentPath = path.dottedSubstring(0, parentPathPart);
+ if (foundParentPath != parentPath) {
+ FAIL(stream() << "Equality match parent at path \"" << foundParentPath
+ << "\" does not match \"" << parentPath << "\"");
+ }
+
+ if (!parentEl.valuesEqual(value)) {
+ FAIL(stream() << "Equality match parent for \"" << pathStr << "\" at path \"" << parentPath
+ << "\" contains value " << parentEl << ", not value " << value);
+ }
+}
+static void assertParent(const EqualityMatches& equalities,
+ StringData path,
+ StringData parentPath,
+ int value) {
+ assertParent(equalities, path, BSON(parentPath << value));
+}
+
+static void assertNoParent(const EqualityMatches& equalities, StringData pathStr) {
+ FieldRef path(pathStr);
+
+ int parentPathPart;
+ BSONElement parentEl = findParentEqualityElement(equalities, path, &parentPathPart);
+
+ if (!parentEl.eoo()) {
StringData foundParentPath = path.dottedSubstring(0, parentPathPart);
- if (foundParentPath != parentPath) {
- FAIL(stream() << "Equality match parent at path \"" << foundParentPath
- << "\" does not match \"" << parentPath << "\"");
- }
-
- if (!parentEl.valuesEqual(value)) {
- FAIL(stream() << "Equality match parent for \"" << pathStr << "\" at path \""
- << parentPath << "\" contains value " << parentEl << ", not value "
- << value);
- }
- }
-
- static void assertParent(const EqualityMatches& equalities,
- StringData path,
- StringData parentPath,
- int value) {
- assertParent(equalities, path, BSON(parentPath << value));
- }
-
- static void assertNoParent(const EqualityMatches& equalities, StringData pathStr) {
-
- FieldRef path(pathStr);
-
- int parentPathPart;
- BSONElement parentEl = findParentEqualityElement(equalities, path, &parentPathPart);
-
- if (!parentEl.eoo()) {
- StringData foundParentPath = path.dottedSubstring(0, parentPathPart);
- FAIL(stream() << "Equality matches contained parent for \"" << pathStr << "\" at \""
- << foundParentPath << "\"");
- }
- }
-
-
- TEST(FindParentEquality, Basic) {
-
- BSONObj exprBSON = fromjson("{a:1}");
- unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
- EqualityMatches equalities;
- ASSERT_OK(extractEqualityMatches(*expr, &equalities));
-
- assertNoParent(equalities, "");
- assertParent(equalities, "a", "a", 1);
- assertParent(equalities, "a.b", "a", 1);
- }
-
- TEST(FindParentEquality, Multi) {
-
- BSONObj exprBSON = fromjson("{a:1,b:2}");
- unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
- EqualityMatches equalities;
- ASSERT_OK(extractEqualityMatches(*expr, &equalities));
-
- assertNoParent(equalities, "");
- assertParent(equalities, "a", "a", 1);
- assertParent(equalities, "a.b", "a", 1);
- assertParent(equalities, "b", "b", 2);
- assertParent(equalities, "b.b", "b", 2);
- }
-
- TEST(FindParentEquality, Nested) {
-
- BSONObj exprBSON = fromjson("{'a.a':1}");
- unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
- EqualityMatches equalities;
- ASSERT_OK(extractEqualityMatches(*expr, &equalities));
-
- assertNoParent(equalities, "");
- assertNoParent(equalities, "a");
- assertParent(equalities, "a.a", "a.a", 1);
- assertParent(equalities, "a.a.b", "a.a", 1);
- }
-
- TEST(FindParentEquality, NestedMulti) {
-
- BSONObj exprBSON = fromjson("{'a.a':1,'a.b':2,'c.c':3}");
- unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
- EqualityMatches equalities;
- ASSERT_OK(extractEqualityMatches(*expr, &equalities));
-
- assertNoParent(equalities, "");
- assertNoParent(equalities, "a");
- assertNoParent(equalities, "c");
- assertParent(equalities, "a.a", "a.a", 1);
- assertParent(equalities, "a.a.a", "a.a", 1);
- assertParent(equalities, "a.b", "a.b", 2);
- assertParent(equalities, "a.b.b", "a.b", 2);
- assertParent(equalities, "c.c", "c.c", 3);
- assertParent(equalities, "c.c.c", "c.c", 3);
- }
-
- TEST(FindParentEquality, Empty) {
-
- BSONObj exprBSON = fromjson("{'':0}");
- unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
- EqualityMatches equalities;
- ASSERT_OK(extractEqualityMatches(*expr, &equalities));
-
- assertParent(equalities, "", "", 0);
- }
-
- TEST(FindParentEquality, EmptyMulti) {
-
- BSONObj exprBSON = fromjson("{'':0,a:1}");
- unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
- EqualityMatches equalities;
- ASSERT_OK(extractEqualityMatches(*expr, &equalities));
-
- assertParent(equalities, "", "", 0);
- assertParent(equalities, "a", "a", 1);
- assertParent(equalities, "a.b", "a", 1);
- }
-
-} // unnamed namespace
+ FAIL(stream() << "Equality matches contained parent for \"" << pathStr << "\" at \""
+ << foundParentPath << "\"");
+ }
+}
+
+
+TEST(FindParentEquality, Basic) {
+ BSONObj exprBSON = fromjson("{a:1}");
+ unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
+ EqualityMatches equalities;
+ ASSERT_OK(extractEqualityMatches(*expr, &equalities));
+
+ assertNoParent(equalities, "");
+ assertParent(equalities, "a", "a", 1);
+ assertParent(equalities, "a.b", "a", 1);
+}
+
+TEST(FindParentEquality, Multi) {
+ BSONObj exprBSON = fromjson("{a:1,b:2}");
+ unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
+ EqualityMatches equalities;
+ ASSERT_OK(extractEqualityMatches(*expr, &equalities));
+
+ assertNoParent(equalities, "");
+ assertParent(equalities, "a", "a", 1);
+ assertParent(equalities, "a.b", "a", 1);
+ assertParent(equalities, "b", "b", 2);
+ assertParent(equalities, "b.b", "b", 2);
+}
+
+TEST(FindParentEquality, Nested) {
+ BSONObj exprBSON = fromjson("{'a.a':1}");
+ unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
+ EqualityMatches equalities;
+ ASSERT_OK(extractEqualityMatches(*expr, &equalities));
+
+ assertNoParent(equalities, "");
+ assertNoParent(equalities, "a");
+ assertParent(equalities, "a.a", "a.a", 1);
+ assertParent(equalities, "a.a.b", "a.a", 1);
+}
+
+TEST(FindParentEquality, NestedMulti) {
+ BSONObj exprBSON = fromjson("{'a.a':1,'a.b':2,'c.c':3}");
+ unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
+ EqualityMatches equalities;
+ ASSERT_OK(extractEqualityMatches(*expr, &equalities));
+
+ assertNoParent(equalities, "");
+ assertNoParent(equalities, "a");
+ assertNoParent(equalities, "c");
+ assertParent(equalities, "a.a", "a.a", 1);
+ assertParent(equalities, "a.a.a", "a.a", 1);
+ assertParent(equalities, "a.b", "a.b", 2);
+ assertParent(equalities, "a.b.b", "a.b", 2);
+ assertParent(equalities, "c.c", "c.c", 3);
+ assertParent(equalities, "c.c.c", "c.c", 3);
+}
+
+TEST(FindParentEquality, Empty) {
+ BSONObj exprBSON = fromjson("{'':0}");
+ unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
+ EqualityMatches equalities;
+ ASSERT_OK(extractEqualityMatches(*expr, &equalities));
+
+ assertParent(equalities, "", "", 0);
+}
+
+TEST(FindParentEquality, EmptyMulti) {
+ BSONObj exprBSON = fromjson("{'':0,a:1}");
+ unique_ptr<MatchExpression> expr(makeExpr(exprBSON));
+ EqualityMatches equalities;
+ ASSERT_OK(extractEqualityMatches(*expr, &equalities));
+
+ assertParent(equalities, "", "", 0);
+ assertParent(equalities, "a", "a", 1);
+ assertParent(equalities, "a.b", "a", 1);
+}
+
+} // unnamed namespace
diff --git a/src/mongo/db/ops/update.cpp b/src/mongo/db/ops/update.cpp
index cf60978b4b8..43c9be211ce 100644
--- a/src/mongo/db/ops/update.cpp
+++ b/src/mongo/db/ops/update.cpp
@@ -54,74 +54,74 @@
namespace mongo {
- UpdateResult update(OperationContext* txn,
- Database* db,
- const UpdateRequest& request,
- OpDebug* opDebug) {
- invariant(db);
-
- // Explain should never use this helper.
- invariant(!request.isExplain());
-
- const NamespaceString& nsString = request.getNamespaceString();
- Collection* collection = db->getCollection(nsString.ns());
-
- // The update stage does not create its own collection. As such, if the update is
- // an upsert, create the collection that the update stage inserts into beforehand.
- if (!collection && request.isUpsert()) {
- // We have to have an exclusive lock on the db to be allowed to create the collection.
- // Callers should either get an X or create the collection.
- const Locker* locker = txn->lockState();
- invariant(locker->isW() ||
- locker->isLockHeldForMode(ResourceId(RESOURCE_DATABASE, nsString.db()),
- MODE_X));
-
- MONGO_WRITE_CONFLICT_RETRY_LOOP_BEGIN {
- ScopedTransaction transaction(txn, MODE_IX);
- Lock::DBLock lk(txn->lockState(), nsString.db(), MODE_X);
-
- const bool userInitiatedWritesAndNotPrimary = txn->writesAreReplicated() &&
- !repl::getGlobalReplicationCoordinator()->canAcceptWritesFor(nsString);
-
- if (userInitiatedWritesAndNotPrimary) {
- uassertStatusOK(Status(ErrorCodes::NotMaster, str::stream()
- << "Not primary while creating collection " << nsString.ns()
- << " during upsert"));
- }
- WriteUnitOfWork wuow(txn);
- collection = db->createCollection(txn, nsString.ns(), CollectionOptions());
- invariant(collection);
- wuow.commit();
- } MONGO_WRITE_CONFLICT_RETRY_LOOP_END(txn, "createCollection", nsString.ns());
+UpdateResult update(OperationContext* txn,
+ Database* db,
+ const UpdateRequest& request,
+ OpDebug* opDebug) {
+ invariant(db);
+
+ // Explain should never use this helper.
+ invariant(!request.isExplain());
+
+ const NamespaceString& nsString = request.getNamespaceString();
+ Collection* collection = db->getCollection(nsString.ns());
+
+ // The update stage does not create its own collection. As such, if the update is
+ // an upsert, create the collection that the update stage inserts into beforehand.
+ if (!collection && request.isUpsert()) {
+ // We have to have an exclusive lock on the db to be allowed to create the collection.
+ // Callers should either get an X or create the collection.
+ const Locker* locker = txn->lockState();
+ invariant(locker->isW() ||
+ locker->isLockHeldForMode(ResourceId(RESOURCE_DATABASE, nsString.db()), MODE_X));
+
+ MONGO_WRITE_CONFLICT_RETRY_LOOP_BEGIN {
+ ScopedTransaction transaction(txn, MODE_IX);
+ Lock::DBLock lk(txn->lockState(), nsString.db(), MODE_X);
+
+ const bool userInitiatedWritesAndNotPrimary = txn->writesAreReplicated() &&
+ !repl::getGlobalReplicationCoordinator()->canAcceptWritesFor(nsString);
+
+ if (userInitiatedWritesAndNotPrimary) {
+ uassertStatusOK(Status(ErrorCodes::NotMaster,
+ str::stream() << "Not primary while creating collection "
+ << nsString.ns() << " during upsert"));
+ }
+ WriteUnitOfWork wuow(txn);
+ collection = db->createCollection(txn, nsString.ns(), CollectionOptions());
+ invariant(collection);
+ wuow.commit();
}
+ MONGO_WRITE_CONFLICT_RETRY_LOOP_END(txn, "createCollection", nsString.ns());
+ }
- // Parse the update, get an executor for it, run the executor, get stats out.
- ParsedUpdate parsedUpdate(txn, &request);
- uassertStatusOK(parsedUpdate.parseRequest());
-
- PlanExecutor* rawExec;
- uassertStatusOK(getExecutorUpdate(txn, collection, &parsedUpdate, opDebug, &rawExec));
- std::unique_ptr<PlanExecutor> exec(rawExec);
+ // Parse the update, get an executor for it, run the executor, get stats out.
+ ParsedUpdate parsedUpdate(txn, &request);
+ uassertStatusOK(parsedUpdate.parseRequest());
- uassertStatusOK(exec->executePlan());
- return UpdateStage::makeUpdateResult(exec.get(), opDebug);
- }
+ PlanExecutor* rawExec;
+ uassertStatusOK(getExecutorUpdate(txn, collection, &parsedUpdate, opDebug, &rawExec));
+ std::unique_ptr<PlanExecutor> exec(rawExec);
- BSONObj applyUpdateOperators(const BSONObj& from, const BSONObj& operators) {
- UpdateDriver::Options opts;
- UpdateDriver driver(opts);
- Status status = driver.parse(operators);
- if (!status.isOK()) {
- uasserted(16838, status.reason());
- }
+ uassertStatusOK(exec->executePlan());
+ return UpdateStage::makeUpdateResult(exec.get(), opDebug);
+}
- mutablebson::Document doc(from, mutablebson::Document::kInPlaceDisabled);
- status = driver.update(StringData(), &doc);
- if (!status.isOK()) {
- uasserted(16839, status.reason());
- }
+BSONObj applyUpdateOperators(const BSONObj& from, const BSONObj& operators) {
+ UpdateDriver::Options opts;
+ UpdateDriver driver(opts);
+ Status status = driver.parse(operators);
+ if (!status.isOK()) {
+ uasserted(16838, status.reason());
+ }
- return doc.getObject();
+ mutablebson::Document doc(from, mutablebson::Document::kInPlaceDisabled);
+ status = driver.update(StringData(), &doc);
+ if (!status.isOK()) {
+ uasserted(16839, status.reason());
}
+ return doc.getObject();
+}
+
} // namespace mongo
diff --git a/src/mongo/db/ops/update.h b/src/mongo/db/ops/update.h
index c4c5e71edff..06cec7fa90f 100644
--- a/src/mongo/db/ops/update.h
+++ b/src/mongo/db/ops/update.h
@@ -37,27 +37,27 @@
namespace mongo {
- class CanonicalQuery;
- class Database;
- class OperationContext;
- class UpdateDriver;
+class CanonicalQuery;
+class Database;
+class OperationContext;
+class UpdateDriver;
- /**
- * Utility method to execute an update described by "request".
- *
- * Caller must hold the appropriate database locks.
- */
- UpdateResult update(OperationContext* txn,
- Database* db,
- const UpdateRequest& request,
- OpDebug* opDebug);
+/**
+ * Utility method to execute an update described by "request".
+ *
+ * Caller must hold the appropriate database locks.
+ */
+UpdateResult update(OperationContext* txn,
+ Database* db,
+ const UpdateRequest& request,
+ OpDebug* opDebug);
- /**
- * takes the from document and returns a new document
- * after apply all the operators
- * e.g.
- * applyUpdateOperators( BSON( "x" << 1 ) , BSON( "$inc" << BSON( "x" << 1 ) ) );
- * returns: { x : 2 }
- */
- BSONObj applyUpdateOperators( const BSONObj& from, const BSONObj& operators );
+/**
+ * takes the from document and returns a new document
+ * after apply all the operators
+ * e.g.
+ * applyUpdateOperators( BSON( "x" << 1 ) , BSON( "$inc" << BSON( "x" << 1 ) ) );
+ * returns: { x : 2 }
+ */
+BSONObj applyUpdateOperators(const BSONObj& from, const BSONObj& operators);
} // namespace mongo
diff --git a/src/mongo/db/ops/update_driver.cpp b/src/mongo/db/ops/update_driver.cpp
index 28ae06215ae..ccd966dbb81 100644
--- a/src/mongo/db/ops/update_driver.cpp
+++ b/src/mongo/db/ops/update_driver.cpp
@@ -44,353 +44,341 @@
namespace mongo {
- namespace str = mongoutils::str;
- namespace mb = mongo::mutablebson;
+namespace str = mongoutils::str;
+namespace mb = mongo::mutablebson;
- using std::unique_ptr;
- using std::vector;
+using std::unique_ptr;
+using std::vector;
- using pathsupport::EqualityMatches;
+using pathsupport::EqualityMatches;
- UpdateDriver::UpdateDriver(const Options& opts)
- : _replacementMode(false)
- , _indexedFields(NULL)
- , _logOp(opts.logOp)
- , _modOptions(opts.modOptions)
- , _affectIndices(false)
- , _positional(false) {
- }
-
- UpdateDriver::~UpdateDriver() {
- clear();
- }
+UpdateDriver::UpdateDriver(const Options& opts)
+ : _replacementMode(false),
+ _indexedFields(NULL),
+ _logOp(opts.logOp),
+ _modOptions(opts.modOptions),
+ _affectIndices(false),
+ _positional(false) {}
- Status UpdateDriver::parse(const BSONObj& updateExpr, const bool multi) {
- clear();
+UpdateDriver::~UpdateDriver() {
+ clear();
+}
- // Check if the update expression is a full object replacement.
- if (*updateExpr.firstElementFieldName() != '$') {
- if (multi) {
- return Status(ErrorCodes::FailedToParse,
- "multi update only works with $ operators");
- }
+Status UpdateDriver::parse(const BSONObj& updateExpr, const bool multi) {
+ clear();
- // Modifiers expect BSONElements as input. But the input to object replace is, by
- // definition, an object. We wrap the 'updateExpr' as the mod is expecting. Note
- // that the wrapper is temporary so the object replace mod should make a copy of
- // the object.
- unique_ptr<ModifierObjectReplace> mod(new ModifierObjectReplace);
- BSONObj wrapper = BSON( "dummy" << updateExpr );
- Status status = mod->init(wrapper.firstElement(), _modOptions);
- if (!status.isOK()) {
- return status;
- }
-
- _mods.push_back(mod.release());
-
- // Register the fact that this driver will only do full object replacements.
- _replacementMode = true;
+ // Check if the update expression is a full object replacement.
+ if (*updateExpr.firstElementFieldName() != '$') {
+ if (multi) {
+ return Status(ErrorCodes::FailedToParse, "multi update only works with $ operators");
+ }
- return Status::OK();
+ // Modifiers expect BSONElements as input. But the input to object replace is, by
+ // definition, an object. We wrap the 'updateExpr' as the mod is expecting. Note
+ // that the wrapper is temporary so the object replace mod should make a copy of
+ // the object.
+ unique_ptr<ModifierObjectReplace> mod(new ModifierObjectReplace);
+ BSONObj wrapper = BSON("dummy" << updateExpr);
+ Status status = mod->init(wrapper.firstElement(), _modOptions);
+ if (!status.isOK()) {
+ return status;
}
- // The update expression is made of mod operators, that is
- // { <$mod>: {...}, <$mod>: {...}, ... }
- BSONObjIterator outerIter(updateExpr);
- while (outerIter.more()) {
- BSONElement outerModElem = outerIter.next();
-
- // Check whether this is a valid mod type.
- modifiertable::ModifierType modType = modifiertable::getType(outerModElem.fieldName());
- if (modType == modifiertable::MOD_UNKNOWN) {
- return Status(ErrorCodes::FailedToParse,
- str::stream() << "Unknown modifier: " << outerModElem.fieldName());
- }
+ _mods.push_back(mod.release());
- // Check whether there is indeed a list of mods under this modifier.
- if (outerModElem.type() != Object) {
- return Status(ErrorCodes::FailedToParse,
- str::stream() << "Modifiers operate on fields but we found a "
- << typeName(outerModElem.type())
- << " instead. For example: {$mod: {<field>: ...}}"
- << " not {" << outerModElem.toString() << "}");
- }
+ // Register the fact that this driver will only do full object replacements.
+ _replacementMode = true;
- // Check whether there are indeed mods under this modifier.
- if (outerModElem.embeddedObject().isEmpty()) {
- return Status(ErrorCodes::FailedToParse,
- str::stream() << "'" << outerModElem.fieldName()
- << "' is empty. You must specify a field like so: "
- "{" << outerModElem.fieldName() << ": {<field>: ...}}");
- }
+ return Status::OK();
+ }
- BSONObjIterator innerIter(outerModElem.embeddedObject());
- while (innerIter.more()) {
- BSONElement innerModElem = innerIter.next();
+ // The update expression is made of mod operators, that is
+ // { <$mod>: {...}, <$mod>: {...}, ... }
+ BSONObjIterator outerIter(updateExpr);
+ while (outerIter.more()) {
+ BSONElement outerModElem = outerIter.next();
- Status status = addAndParse(modType, innerModElem);
- if (!status.isOK()) {
- return status;
- }
- }
+ // Check whether this is a valid mod type.
+ modifiertable::ModifierType modType = modifiertable::getType(outerModElem.fieldName());
+ if (modType == modifiertable::MOD_UNKNOWN) {
+ return Status(ErrorCodes::FailedToParse,
+ str::stream() << "Unknown modifier: " << outerModElem.fieldName());
}
- // Register the fact that there will be only $mod's in this driver -- no object
- // replacement.
- _replacementMode = false;
-
- return Status::OK();
- }
+ // Check whether there is indeed a list of mods under this modifier.
+ if (outerModElem.type() != Object) {
+ return Status(ErrorCodes::FailedToParse,
+ str::stream() << "Modifiers operate on fields but we found a "
+ << typeName(outerModElem.type())
+ << " instead. For example: {$mod: {<field>: ...}}"
+ << " not {" << outerModElem.toString() << "}");
+ }
- inline Status UpdateDriver::addAndParse(const modifiertable::ModifierType type,
- const BSONElement& elem) {
- if (elem.eoo()) {
+ // Check whether there are indeed mods under this modifier.
+ if (outerModElem.embeddedObject().isEmpty()) {
return Status(ErrorCodes::FailedToParse,
- str::stream() << "'" << elem.fieldName()
- << "' has no value in : " << elem
- << " which is not allowed for any $" << type << " mod.");
+ str::stream() << "'" << outerModElem.fieldName()
+ << "' is empty. You must specify a field like so: "
+ "{" << outerModElem.fieldName() << ": {<field>: ...}}");
}
- unique_ptr<ModifierInterface> mod(modifiertable::makeUpdateMod(type));
- dassert(mod.get());
+ BSONObjIterator innerIter(outerModElem.embeddedObject());
+ while (innerIter.more()) {
+ BSONElement innerModElem = innerIter.next();
- bool positional = false;
- Status status = mod->init(elem, _modOptions, &positional);
- if (!status.isOK()) {
- return status;
+ Status status = addAndParse(modType, innerModElem);
+ if (!status.isOK()) {
+ return status;
+ }
}
+ }
- // If any modifier indicates that it requires a positional match, toggle the
- // _positional flag to true.
- _positional = _positional || positional;
+ // Register the fact that there will be only $mod's in this driver -- no object
+ // replacement.
+ _replacementMode = false;
- _mods.push_back(mod.release());
+ return Status::OK();
+}
- return Status::OK();
+inline Status UpdateDriver::addAndParse(const modifiertable::ModifierType type,
+ const BSONElement& elem) {
+ if (elem.eoo()) {
+ return Status(ErrorCodes::FailedToParse,
+ str::stream() << "'" << elem.fieldName() << "' has no value in : " << elem
+ << " which is not allowed for any $" << type << " mod.");
}
- Status UpdateDriver::populateDocumentWithQueryFields(const BSONObj& query,
- const vector<FieldRef*>* immutablePaths,
- mutablebson::Document& doc) const {
- CanonicalQuery* rawCG;
- // We canonicalize the query to collapse $and/$or, and the first arg (ns) is not needed
- // Also, because this is for the upsert case, where we insert a new document if one was
- // not found, the $where clause does not make sense, hence empty WhereCallback.
- Status s = CanonicalQuery::canonicalize("", query, &rawCG, WhereCallbackNoop());
- if (!s.isOK())
- return s;
- unique_ptr<CanonicalQuery> cq(rawCG);
- return populateDocumentWithQueryFields(rawCG, immutablePaths, doc);
- }
+ unique_ptr<ModifierInterface> mod(modifiertable::makeUpdateMod(type));
+ dassert(mod.get());
- Status UpdateDriver::populateDocumentWithQueryFields(const CanonicalQuery* query,
- const vector<FieldRef*>* immutablePathsPtr,
- mutablebson::Document& doc) const {
- EqualityMatches equalities;
- Status status = Status::OK();
+ bool positional = false;
+ Status status = mod->init(elem, _modOptions, &positional);
+ if (!status.isOK()) {
+ return status;
+ }
- if (isDocReplacement()) {
+ // If any modifier indicates that it requires a positional match, toggle the
+ // _positional flag to true.
+ _positional = _positional || positional;
+
+ _mods.push_back(mod.release());
+
+ return Status::OK();
+}
+
+Status UpdateDriver::populateDocumentWithQueryFields(const BSONObj& query,
+ const vector<FieldRef*>* immutablePaths,
+ mutablebson::Document& doc) const {
+ CanonicalQuery* rawCG;
+ // We canonicalize the query to collapse $and/$or, and the first arg (ns) is not needed
+ // Also, because this is for the upsert case, where we insert a new document if one was
+ // not found, the $where clause does not make sense, hence empty WhereCallback.
+ Status s = CanonicalQuery::canonicalize("", query, &rawCG, WhereCallbackNoop());
+ if (!s.isOK())
+ return s;
+ unique_ptr<CanonicalQuery> cq(rawCG);
+ return populateDocumentWithQueryFields(rawCG, immutablePaths, doc);
+}
+
+Status UpdateDriver::populateDocumentWithQueryFields(const CanonicalQuery* query,
+ const vector<FieldRef*>* immutablePathsPtr,
+ mutablebson::Document& doc) const {
+ EqualityMatches equalities;
+ Status status = Status::OK();
+
+ if (isDocReplacement()) {
+ FieldRefSet pathsToExtract;
+
+ // TODO: Refactor update logic, make _id just another immutable field
+ static const FieldRef idPath("_id");
+ static const vector<FieldRef*> emptyImmutablePaths;
+ const vector<FieldRef*>& immutablePaths =
+ immutablePathsPtr ? *immutablePathsPtr : emptyImmutablePaths;
+
+ pathsToExtract.fillFrom(immutablePaths);
+ pathsToExtract.insert(&idPath);
+
+ // Extract only immutable fields from replacement-style
+ status =
+ pathsupport::extractFullEqualityMatches(*query->root(), pathsToExtract, &equalities);
+ } else {
+ // Extract all fields from op-style
+ status = pathsupport::extractEqualityMatches(*query->root(), &equalities);
+ }
- FieldRefSet pathsToExtract;
+ if (!status.isOK())
+ return status;
- // TODO: Refactor update logic, make _id just another immutable field
- static const FieldRef idPath("_id");
- static const vector<FieldRef*> emptyImmutablePaths;
- const vector<FieldRef*>& immutablePaths =
- immutablePathsPtr ? *immutablePathsPtr : emptyImmutablePaths;
+ status = pathsupport::addEqualitiesToDoc(equalities, &doc);
+ return status;
+}
+
+Status UpdateDriver::update(StringData matchedField,
+ mutablebson::Document* doc,
+ BSONObj* logOpRec,
+ FieldRefSet* updatedFields,
+ bool* docWasModified) {
+ // TODO: assert that update() is called at most once in a !_multi case.
+
+ // Use the passed in FieldRefSet
+ FieldRefSet* targetFields = updatedFields;
+
+ // If we didn't get a FieldRefSet* from the caller, allocate storage and use
+ // the unique_ptr for lifecycle management
+ unique_ptr<FieldRefSet> targetFieldScopedPtr;
+ if (!targetFields) {
+ targetFieldScopedPtr.reset(new FieldRefSet());
+ targetFields = targetFieldScopedPtr.get();
+ }
- pathsToExtract.fillFrom(immutablePaths);
- pathsToExtract.insert(&idPath);
+ _affectIndices = (isDocReplacement() && (_indexedFields != NULL));
- // Extract only immutable fields from replacement-style
- status = pathsupport::extractFullEqualityMatches(*query->root(),
- pathsToExtract,
- &equalities);
- }
- else {
- // Extract all fields from op-style
- status = pathsupport::extractEqualityMatches(*query->root(), &equalities);
- }
+ _logDoc.reset();
+ LogBuilder logBuilder(_logDoc.root());
- if (!status.isOK())
+ // Ask each of the mods to type check whether they can operate over the current document
+ // and, if so, to change that document accordingly.
+ for (vector<ModifierInterface*>::iterator it = _mods.begin(); it != _mods.end(); ++it) {
+ ModifierInterface::ExecInfo execInfo;
+ Status status = (*it)->prepare(doc->root(), matchedField, &execInfo);
+ if (!status.isOK()) {
return status;
+ }
- status = pathsupport::addEqualitiesToDoc(equalities, &doc);
- return status;
- }
+ // If a mod wants to be applied only if this is an upsert (or only if this is a
+ // strict update), we should respect that. If a mod doesn't care, it would state
+ // it is fine with ANY update context.
+ const bool validContext = (execInfo.context == ModifierInterface::ExecInfo::ANY_CONTEXT ||
+ execInfo.context == _context);
- Status UpdateDriver::update(StringData matchedField,
- mutablebson::Document* doc,
- BSONObj* logOpRec,
- FieldRefSet* updatedFields,
- bool* docWasModified) {
- // TODO: assert that update() is called at most once in a !_multi case.
-
- // Use the passed in FieldRefSet
- FieldRefSet* targetFields = updatedFields;
-
- // If we didn't get a FieldRefSet* from the caller, allocate storage and use
- // the unique_ptr for lifecycle management
- unique_ptr<FieldRefSet> targetFieldScopedPtr;
- if (!targetFields) {
- targetFieldScopedPtr.reset(new FieldRefSet());
- targetFields = targetFieldScopedPtr.get();
+ // Nothing to do if not in a valid context.
+ if (!validContext) {
+ continue;
}
- _affectIndices = (isDocReplacement() && (_indexedFields != NULL));
- _logDoc.reset();
- LogBuilder logBuilder(_logDoc.root());
-
- // Ask each of the mods to type check whether they can operate over the current document
- // and, if so, to change that document accordingly.
- for (vector<ModifierInterface*>::iterator it = _mods.begin(); it != _mods.end(); ++it) {
- ModifierInterface::ExecInfo execInfo;
- Status status = (*it)->prepare(doc->root(), matchedField, &execInfo);
- if (!status.isOK()) {
- return status;
+ // Gather which fields this mod is interested on and whether these fields were
+ // "taken" by previous mods. Note that not all mods are multi-field mods. When we
+ // see an empty field, we may stop looking for others.
+ for (int i = 0; i < ModifierInterface::ExecInfo::MAX_NUM_FIELDS; i++) {
+ if (execInfo.fieldRef[i] == 0) {
+ break;
}
- // If a mod wants to be applied only if this is an upsert (or only if this is a
- // strict update), we should respect that. If a mod doesn't care, it would state
- // it is fine with ANY update context.
- const bool validContext = (execInfo.context == ModifierInterface::ExecInfo::ANY_CONTEXT ||
- execInfo.context == _context);
-
- // Nothing to do if not in a valid context.
- if (!validContext) {
- continue;
+ // Record each field being updated but check for conflicts first
+ const FieldRef* other;
+ if (!targetFields->insert(execInfo.fieldRef[i], &other)) {
+ return Status(ErrorCodes::ConflictingUpdateOperators,
+ str::stream() << "Cannot update '" << other->dottedField()
+ << "' and '" << execInfo.fieldRef[i]->dottedField()
+ << "' at the same time");
}
-
- // Gather which fields this mod is interested on and whether these fields were
- // "taken" by previous mods. Note that not all mods are multi-field mods. When we
- // see an empty field, we may stop looking for others.
- for (int i = 0; i < ModifierInterface::ExecInfo::MAX_NUM_FIELDS; i++) {
- if (execInfo.fieldRef[i] == 0) {
- break;
- }
-
- // Record each field being updated but check for conflicts first
- const FieldRef* other;
- if (!targetFields->insert(execInfo.fieldRef[i], &other)) {
- return Status(ErrorCodes::ConflictingUpdateOperators,
- str::stream() << "Cannot update '"
- << other->dottedField()
- << "' and '"
- << execInfo.fieldRef[i]->dottedField()
- << "' at the same time");
- }
-
- // We start with the expectation that a mod will be in-place. But if the mod
- // touched an indexed field and the mod will indeed be executed -- that is, it
- // is not a no-op and it is in a valid context -- then we switch back to a
- // non-in-place mode.
- //
- // TODO: make mightBeIndexed and fieldRef like each other.
- if (!_affectIndices &&
- !execInfo.noOp &&
- _indexedFields &&
- _indexedFields->mightBeIndexed(execInfo.fieldRef[i]->dottedField())) {
- _affectIndices = true;
- doc->disableInPlaceUpdates();
- }
+ // We start with the expectation that a mod will be in-place. But if the mod
+ // touched an indexed field and the mod will indeed be executed -- that is, it
+ // is not a no-op and it is in a valid context -- then we switch back to a
+ // non-in-place mode.
+ //
+ // TODO: make mightBeIndexed and fieldRef like each other.
+ if (!_affectIndices && !execInfo.noOp && _indexedFields &&
+ _indexedFields->mightBeIndexed(execInfo.fieldRef[i]->dottedField())) {
+ _affectIndices = true;
+ doc->disableInPlaceUpdates();
}
+ }
- if (!execInfo.noOp) {
- status = (*it)->apply();
+ if (!execInfo.noOp) {
+ status = (*it)->apply();
- if (docWasModified)
- *docWasModified = true;
+ if (docWasModified)
+ *docWasModified = true;
- if (!status.isOK()) {
- return status;
- }
+ if (!status.isOK()) {
+ return status;
}
+ }
- // If we require a replication oplog entry for this update, go ahead and generate one.
- if (!execInfo.noOp && _logOp && logOpRec) {
- status = (*it)->log(&logBuilder);
- if (!status.isOK()) {
- return status;
- }
+ // If we require a replication oplog entry for this update, go ahead and generate one.
+ if (!execInfo.noOp && _logOp && logOpRec) {
+ status = (*it)->log(&logBuilder);
+ if (!status.isOK()) {
+ return status;
}
-
}
-
- if (_logOp && logOpRec)
- *logOpRec = _logDoc.getObject();
-
- return Status::OK();
}
- size_t UpdateDriver::numMods() const {
- return _mods.size();
+ if (_logOp && logOpRec)
+ *logOpRec = _logDoc.getObject();
+
+ return Status::OK();
+}
+
+size_t UpdateDriver::numMods() const {
+ return _mods.size();
+}
+
+bool UpdateDriver::isDocReplacement() const {
+ return _replacementMode;
+}
+
+bool UpdateDriver::modsAffectIndices() const {
+ return _affectIndices;
+}
+
+void UpdateDriver::refreshIndexKeys(const UpdateIndexData* indexedFields) {
+ _indexedFields = indexedFields;
+}
+
+bool UpdateDriver::logOp() const {
+ return _logOp;
+}
+
+void UpdateDriver::setLogOp(bool logOp) {
+ _logOp = logOp;
+}
+
+ModifierInterface::Options UpdateDriver::modOptions() const {
+ return _modOptions;
+}
+
+void UpdateDriver::setModOptions(ModifierInterface::Options modOpts) {
+ _modOptions = modOpts;
+}
+
+ModifierInterface::ExecInfo::UpdateContext UpdateDriver::context() const {
+ return _context;
+}
+
+void UpdateDriver::setContext(ModifierInterface::ExecInfo::UpdateContext context) {
+ _context = context;
+}
+
+BSONObj UpdateDriver::makeOplogEntryQuery(const BSONObj& doc, bool multi) const {
+ BSONObjBuilder idPattern;
+ BSONElement id;
+ // NOTE: If the matching object lacks an id, we'll log
+ // with the original pattern. This isn't replay-safe.
+ // It might make sense to suppress the log instead
+ // if there's no id.
+ if (doc.getObjectID(id)) {
+ idPattern.append(id);
+ return idPattern.obj();
+ } else {
+ uassert(16980,
+ str::stream() << "Multi-update operations require all documents to "
+ "have an '_id' field. " << doc.toString(false, false),
+ !multi);
+ return doc;
}
-
- bool UpdateDriver::isDocReplacement() const {
- return _replacementMode;
- }
-
- bool UpdateDriver::modsAffectIndices() const {
- return _affectIndices;
- }
-
- void UpdateDriver::refreshIndexKeys(const UpdateIndexData* indexedFields) {
- _indexedFields = indexedFields;
- }
-
- bool UpdateDriver::logOp() const {
- return _logOp;
- }
-
- void UpdateDriver::setLogOp(bool logOp) {
- _logOp = logOp;
- }
-
- ModifierInterface::Options UpdateDriver::modOptions() const {
- return _modOptions;
- }
-
- void UpdateDriver::setModOptions(ModifierInterface::Options modOpts) {
- _modOptions = modOpts;
- }
-
- ModifierInterface::ExecInfo::UpdateContext UpdateDriver::context() const {
- return _context;
- }
-
- void UpdateDriver::setContext(ModifierInterface::ExecInfo::UpdateContext context) {
- _context = context;
- }
-
- BSONObj UpdateDriver::makeOplogEntryQuery(const BSONObj& doc, bool multi) const {
- BSONObjBuilder idPattern;
- BSONElement id;
- // NOTE: If the matching object lacks an id, we'll log
- // with the original pattern. This isn't replay-safe.
- // It might make sense to suppress the log instead
- // if there's no id.
- if ( doc.getObjectID( id ) ) {
- idPattern.append( id );
- return idPattern.obj();
- }
- else {
- uassert( 16980,
- str::stream() << "Multi-update operations require all documents to "
- "have an '_id' field. " << doc.toString(false, false),
- ! multi );
- return doc;
- }
- }
- void UpdateDriver::clear() {
- for (vector<ModifierInterface*>::iterator it = _mods.begin(); it != _mods.end(); ++it) {
- delete *it;
- }
- _mods.clear();
- _indexedFields = NULL;
- _replacementMode = false;
- _positional = false;
+}
+void UpdateDriver::clear() {
+ for (vector<ModifierInterface*>::iterator it = _mods.begin(); it != _mods.end(); ++it) {
+ delete *it;
}
+ _mods.clear();
+ _indexedFields = NULL;
+ _replacementMode = false;
+ _positional = false;
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/db/ops/update_driver.h b/src/mongo/db/ops/update_driver.h
index 067c432ebe2..00e3e2eb10f 100644
--- a/src/mongo/db/ops/update_driver.h
+++ b/src/mongo/db/ops/update_driver.h
@@ -43,153 +43,150 @@
namespace mongo {
- class UpdateDriver {
- public:
-
- struct Options;
- UpdateDriver(const Options& opts);
-
- ~UpdateDriver();
-
- /**
- * Returns OK and fills in '_mods' if 'updateExpr' is correct. Otherwise returns an
- * error status with a corresponding description.
- */
- Status parse(const BSONObj& updateExpr, const bool multi = false);
-
- /**
- * Fills in document with any fields in the query which are valid.
- *
- * Valid fields include equality matches like "a":1, or "a.b":false
- *
- * Each valid field will be expanded (from dot notation) and conflicts will be
- * checked for all fields added to the underlying document.
- *
- * Returns Status::OK() if the document can be used. If there are any error or
- * conflicts along the way then those errors will be returned.
- */
- Status populateDocumentWithQueryFields(const BSONObj& query,
- const std::vector<FieldRef*>* immutablePaths,
- mutablebson::Document& doc) const;
-
- Status populateDocumentWithQueryFields(const CanonicalQuery* query,
- const std::vector<FieldRef*>* immutablePaths,
- mutablebson::Document& doc) const;
-
- /**
- * return a BSONObj with the _id field of the doc passed in, or the doc itself.
- * If no _id and multi, error.
- */
- BSONObj makeOplogEntryQuery(const BSONObj& doc, bool multi) const;
-
- /**
- * Returns OK and executes '_mods' over 'doc', generating 'newObj'. If any mod is
- * positional, use 'matchedField' (index of the array item matched). If doc allows
- * mods to be applied in place and no index updating is involved, then the mods may
- * be applied "in place" over 'doc'.
- *
- * If the driver's '_logOp' mode is turned on, and if 'logOpRec' is not NULL, fills in
- * the latter with the oplog entry corresponding to the update. If '_mods' can't be
- * applied, returns an error status with a corresponding description.
- *
- * If a non-NULL updatedField vector* is supplied,
- * then all updated fields will be added to it.
- */
- Status update(StringData matchedField,
- mutablebson::Document* doc,
- BSONObj* logOpRec = NULL,
- FieldRefSet* updatedFields = NULL,
- bool* docWasModified = NULL);
-
- //
- // Accessors
- //
-
- size_t numMods() const;
-
- bool isDocReplacement() const;
-
- bool modsAffectIndices() const;
- void refreshIndexKeys(const UpdateIndexData* indexedFields);
-
- bool logOp() const;
- void setLogOp(bool logOp);
-
- ModifierInterface::Options modOptions() const;
- void setModOptions(ModifierInterface::Options modOpts);
-
- ModifierInterface::ExecInfo::UpdateContext context() const;
- void setContext(ModifierInterface::ExecInfo::UpdateContext context);
-
- mutablebson::Document& getDocument() {
- return _objDoc;
- }
-
- const mutablebson::Document& getDocument() const {
- return _objDoc;
- }
-
- bool needMatchDetails() const {
- return _positional;
- }
-
- private:
-
- /** Resets the state of the class associated with mods (not the error state) */
- void clear();
-
- /** Create the modifier and add it to the back of the modifiers vector */
- inline Status addAndParse(const modifiertable::ModifierType type,
- const BSONElement& elem);
-
- //
- // immutable properties after parsing
- //
-
- // Is there a list of $mod's on '_mods' or is it just full object replacement?
- bool _replacementMode;
-
- // Collection of update mod instances. Owned here.
- std::vector<ModifierInterface*> _mods;
-
- // What are the list of fields in the collection over which the update is going to be
- // applied that participate in indices?
- //
- // NOTE: Owned by the collection's info cache!.
- const UpdateIndexData* _indexedFields;
-
- //
- // mutable properties after parsing
- //
-
- // Should this driver generate an oplog record when it applies the update?
- bool _logOp;
-
- // The options to initiate the mods with
- ModifierInterface::Options _modOptions;
+class UpdateDriver {
+public:
+ struct Options;
+ UpdateDriver(const Options& opts);
+
+ ~UpdateDriver();
+
+ /**
+ * Returns OK and fills in '_mods' if 'updateExpr' is correct. Otherwise returns an
+ * error status with a corresponding description.
+ */
+ Status parse(const BSONObj& updateExpr, const bool multi = false);
+
+ /**
+ * Fills in document with any fields in the query which are valid.
+ *
+ * Valid fields include equality matches like "a":1, or "a.b":false
+ *
+ * Each valid field will be expanded (from dot notation) and conflicts will be
+ * checked for all fields added to the underlying document.
+ *
+ * Returns Status::OK() if the document can be used. If there are any error or
+ * conflicts along the way then those errors will be returned.
+ */
+ Status populateDocumentWithQueryFields(const BSONObj& query,
+ const std::vector<FieldRef*>* immutablePaths,
+ mutablebson::Document& doc) const;
+
+ Status populateDocumentWithQueryFields(const CanonicalQuery* query,
+ const std::vector<FieldRef*>* immutablePaths,
+ mutablebson::Document& doc) const;
+
+ /**
+ * return a BSONObj with the _id field of the doc passed in, or the doc itself.
+ * If no _id and multi, error.
+ */
+ BSONObj makeOplogEntryQuery(const BSONObj& doc, bool multi) const;
+
+ /**
+ * Returns OK and executes '_mods' over 'doc', generating 'newObj'. If any mod is
+ * positional, use 'matchedField' (index of the array item matched). If doc allows
+ * mods to be applied in place and no index updating is involved, then the mods may
+ * be applied "in place" over 'doc'.
+ *
+ * If the driver's '_logOp' mode is turned on, and if 'logOpRec' is not NULL, fills in
+ * the latter with the oplog entry corresponding to the update. If '_mods' can't be
+ * applied, returns an error status with a corresponding description.
+ *
+ * If a non-NULL updatedField vector* is supplied,
+ * then all updated fields will be added to it.
+ */
+ Status update(StringData matchedField,
+ mutablebson::Document* doc,
+ BSONObj* logOpRec = NULL,
+ FieldRefSet* updatedFields = NULL,
+ bool* docWasModified = NULL);
+
+ //
+ // Accessors
+ //
+
+ size_t numMods() const;
+
+ bool isDocReplacement() const;
+
+ bool modsAffectIndices() const;
+ void refreshIndexKeys(const UpdateIndexData* indexedFields);
+
+ bool logOp() const;
+ void setLogOp(bool logOp);
+
+ ModifierInterface::Options modOptions() const;
+ void setModOptions(ModifierInterface::Options modOpts);
+
+ ModifierInterface::ExecInfo::UpdateContext context() const;
+ void setContext(ModifierInterface::ExecInfo::UpdateContext context);
+
+ mutablebson::Document& getDocument() {
+ return _objDoc;
+ }
+
+ const mutablebson::Document& getDocument() const {
+ return _objDoc;
+ }
+
+ bool needMatchDetails() const {
+ return _positional;
+ }
+
+private:
+ /** Resets the state of the class associated with mods (not the error state) */
+ void clear();
+
+ /** Create the modifier and add it to the back of the modifiers vector */
+ inline Status addAndParse(const modifiertable::ModifierType type, const BSONElement& elem);
+
+ //
+ // immutable properties after parsing
+ //
+
+ // Is there a list of $mod's on '_mods' or is it just full object replacement?
+ bool _replacementMode;
+
+ // Collection of update mod instances. Owned here.
+ std::vector<ModifierInterface*> _mods;
+
+ // What are the list of fields in the collection over which the update is going to be
+ // applied that participate in indices?
+ //
+ // NOTE: Owned by the collection's info cache!.
+ const UpdateIndexData* _indexedFields;
+
+ //
+ // mutable properties after parsing
+ //
+
+ // Should this driver generate an oplog record when it applies the update?
+ bool _logOp;
+
+ // The options to initiate the mods with
+ ModifierInterface::Options _modOptions;
+
+ // Are any of the fields mentioned in the mods participating in any index? Is set anew
+ // at each call to update.
+ bool _affectIndices;
- // Are any of the fields mentioned in the mods participating in any index? Is set anew
- // at each call to update.
- bool _affectIndices;
+ // Do any of the mods require positional match details when calling 'prepare'?
+ bool _positional;
- // Do any of the mods require positional match details when calling 'prepare'?
- bool _positional;
+ // Is this update going to be an upsert?
+ ModifierInterface::ExecInfo::UpdateContext _context;
- // Is this update going to be an upsert?
- ModifierInterface::ExecInfo::UpdateContext _context;
+ // The document used to represent or store the object being updated.
+ mutablebson::Document _objDoc;
- // The document used to represent or store the object being updated.
- mutablebson::Document _objDoc;
+ // The document used to build the oplog entry for the update.
+ mutablebson::Document _logDoc;
+};
- // The document used to build the oplog entry for the update.
- mutablebson::Document _logDoc;
- };
+struct UpdateDriver::Options {
+ bool logOp;
+ ModifierInterface::Options modOptions;
- struct UpdateDriver::Options {
- bool logOp;
- ModifierInterface::Options modOptions;
+ Options() : logOp(false), modOptions() {}
+};
- Options() : logOp(false), modOptions() {}
- };
-
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/db/ops/update_driver_test.cpp b/src/mongo/db/ops/update_driver_test.cpp
index 1fd93aa27c1..1a778b231a9 100644
--- a/src/mongo/db/ops/update_driver_test.cpp
+++ b/src/mongo/db/ops/update_driver_test.cpp
@@ -42,362 +42,351 @@
namespace {
- using mongo::BSONObj;
- using mongo::BSONElement;
- using mongo::BSONObjIterator;
- using mongo::FieldRef;
- using mongo::fromjson;
- using mongo::OwnedPointerVector;
- using mongo::UpdateIndexData;
- using mongo::mutablebson::Document;
- using mongo::StringData;
- using mongo::UpdateDriver;
- using mongoutils::str::stream;
-
- TEST(Parse, Normal) {
- UpdateDriver::Options opts;
- UpdateDriver driver(opts);
- ASSERT_OK(driver.parse(fromjson("{$set:{a:1}}")));
- ASSERT_EQUALS(driver.numMods(), 1U);
- ASSERT_FALSE(driver.isDocReplacement());
- }
-
- TEST(Parse, MultiMods) {
- UpdateDriver::Options opts;
- UpdateDriver driver(opts);
- ASSERT_OK(driver.parse(fromjson("{$set:{a:1, b:1}}")));
- ASSERT_EQUALS(driver.numMods(), 2U);
- ASSERT_FALSE(driver.isDocReplacement());
- }
-
- TEST(Parse, MixingMods) {
- UpdateDriver::Options opts;
- UpdateDriver driver(opts);
- ASSERT_OK(driver.parse(fromjson("{$set:{a:1}, $unset:{b:1}}")));
- ASSERT_EQUALS(driver.numMods(), 2U);
- ASSERT_FALSE(driver.isDocReplacement());
- }
-
- TEST(Parse, ObjectReplacment) {
- UpdateDriver::Options opts;
- UpdateDriver driver(opts);
- ASSERT_OK(driver.parse(fromjson("{obj: \"obj replacement\"}")));
- ASSERT_TRUE(driver.isDocReplacement());
- }
-
- TEST(Parse, EmptyMod) {
- UpdateDriver::Options opts;
- UpdateDriver driver(opts);
- ASSERT_NOT_OK(driver.parse(fromjson("{$set:{}}")));
- }
-
- TEST(Parse, WrongMod) {
- UpdateDriver::Options opts;
- UpdateDriver driver(opts);
- ASSERT_NOT_OK(driver.parse(fromjson("{$xyz:{a:1}}")));
- }
-
- TEST(Parse, WrongType) {
- UpdateDriver::Options opts;
- UpdateDriver driver(opts);
- ASSERT_NOT_OK(driver.parse(fromjson("{$set:[{a:1}]}")));
- }
-
- TEST(Parse, ModsWithLaterObjReplacement) {
- UpdateDriver::Options opts;
- UpdateDriver driver(opts);
- ASSERT_NOT_OK(driver.parse(fromjson("{$set:{a:1}, obj: \"obj replacement\"}")));
- }
-
- TEST(Parse, PushAll) {
- UpdateDriver::Options opts;
- UpdateDriver driver(opts);
- ASSERT_OK(driver.parse(fromjson("{$pushAll:{a:[1,2,3]}}")));
- ASSERT_EQUALS(driver.numMods(), 1U);
- ASSERT_FALSE(driver.isDocReplacement());
- }
-
- TEST(Parse, SetOnInsert) {
- UpdateDriver::Options opts;
- UpdateDriver driver(opts);
- ASSERT_OK(driver.parse(fromjson("{$setOnInsert:{a:1}}")));
- ASSERT_EQUALS(driver.numMods(), 1U);
- ASSERT_FALSE(driver.isDocReplacement());
- }
-
- //
- // Tests of creating a base for an upsert from a query document
- // $or, $and, $all get special handling, as does the _id field
- //
- // NONGOAL: Testing all query parsing and nesting combinations
- //
-
- class CreateFromQueryFixture : public mongo::unittest::Test {
- public:
-
- CreateFromQueryFixture()
- : _driverOps(new UpdateDriver(UpdateDriver::Options())),
- _driverRepl(new UpdateDriver(UpdateDriver::Options())) {
- _driverOps->parse(fromjson("{$set:{'_':1}}"));
- _driverRepl->parse(fromjson("{}"));
- }
-
- Document& doc() {
- return _doc;
- }
-
- UpdateDriver& driverOps() {
- return *_driverOps;
- }
+using mongo::BSONObj;
+using mongo::BSONElement;
+using mongo::BSONObjIterator;
+using mongo::FieldRef;
+using mongo::fromjson;
+using mongo::OwnedPointerVector;
+using mongo::UpdateIndexData;
+using mongo::mutablebson::Document;
+using mongo::StringData;
+using mongo::UpdateDriver;
+using mongoutils::str::stream;
+
+TEST(Parse, Normal) {
+ UpdateDriver::Options opts;
+ UpdateDriver driver(opts);
+ ASSERT_OK(driver.parse(fromjson("{$set:{a:1}}")));
+ ASSERT_EQUALS(driver.numMods(), 1U);
+ ASSERT_FALSE(driver.isDocReplacement());
+}
+
+TEST(Parse, MultiMods) {
+ UpdateDriver::Options opts;
+ UpdateDriver driver(opts);
+ ASSERT_OK(driver.parse(fromjson("{$set:{a:1, b:1}}")));
+ ASSERT_EQUALS(driver.numMods(), 2U);
+ ASSERT_FALSE(driver.isDocReplacement());
+}
+
+TEST(Parse, MixingMods) {
+ UpdateDriver::Options opts;
+ UpdateDriver driver(opts);
+ ASSERT_OK(driver.parse(fromjson("{$set:{a:1}, $unset:{b:1}}")));
+ ASSERT_EQUALS(driver.numMods(), 2U);
+ ASSERT_FALSE(driver.isDocReplacement());
+}
+
+TEST(Parse, ObjectReplacment) {
+ UpdateDriver::Options opts;
+ UpdateDriver driver(opts);
+ ASSERT_OK(driver.parse(fromjson("{obj: \"obj replacement\"}")));
+ ASSERT_TRUE(driver.isDocReplacement());
+}
+
+TEST(Parse, EmptyMod) {
+ UpdateDriver::Options opts;
+ UpdateDriver driver(opts);
+ ASSERT_NOT_OK(driver.parse(fromjson("{$set:{}}")));
+}
+
+TEST(Parse, WrongMod) {
+ UpdateDriver::Options opts;
+ UpdateDriver driver(opts);
+ ASSERT_NOT_OK(driver.parse(fromjson("{$xyz:{a:1}}")));
+}
+
+TEST(Parse, WrongType) {
+ UpdateDriver::Options opts;
+ UpdateDriver driver(opts);
+ ASSERT_NOT_OK(driver.parse(fromjson("{$set:[{a:1}]}")));
+}
+
+TEST(Parse, ModsWithLaterObjReplacement) {
+ UpdateDriver::Options opts;
+ UpdateDriver driver(opts);
+ ASSERT_NOT_OK(driver.parse(fromjson("{$set:{a:1}, obj: \"obj replacement\"}")));
+}
+
+TEST(Parse, PushAll) {
+ UpdateDriver::Options opts;
+ UpdateDriver driver(opts);
+ ASSERT_OK(driver.parse(fromjson("{$pushAll:{a:[1,2,3]}}")));
+ ASSERT_EQUALS(driver.numMods(), 1U);
+ ASSERT_FALSE(driver.isDocReplacement());
+}
+
+TEST(Parse, SetOnInsert) {
+ UpdateDriver::Options opts;
+ UpdateDriver driver(opts);
+ ASSERT_OK(driver.parse(fromjson("{$setOnInsert:{a:1}}")));
+ ASSERT_EQUALS(driver.numMods(), 1U);
+ ASSERT_FALSE(driver.isDocReplacement());
+}
+
+//
+// Tests of creating a base for an upsert from a query document
+// $or, $and, $all get special handling, as does the _id field
+//
+// NONGOAL: Testing all query parsing and nesting combinations
+//
+
+class CreateFromQueryFixture : public mongo::unittest::Test {
+public:
+ CreateFromQueryFixture()
+ : _driverOps(new UpdateDriver(UpdateDriver::Options())),
+ _driverRepl(new UpdateDriver(UpdateDriver::Options())) {
+ _driverOps->parse(fromjson("{$set:{'_':1}}"));
+ _driverRepl->parse(fromjson("{}"));
+ }
+
+ Document& doc() {
+ return _doc;
+ }
+
+ UpdateDriver& driverOps() {
+ return *_driverOps;
+ }
+
+ UpdateDriver& driverRepl() {
+ return *_driverRepl;
+ }
+
+private:
+ std::unique_ptr<UpdateDriver> _driverOps;
+ std::unique_ptr<UpdateDriver> _driverRepl;
+ Document _doc;
+};
+
+// Make name nicer to report
+typedef CreateFromQueryFixture CreateFromQuery;
+
+static void assertSameFields(const BSONObj& docA, const BSONObj& docB);
- UpdateDriver& driverRepl() {
- return *_driverRepl;
- }
-
- private:
- std::unique_ptr<UpdateDriver> _driverOps;
- std::unique_ptr<UpdateDriver> _driverRepl;
- Document _doc;
- };
-
- // Make name nicer to report
- typedef CreateFromQueryFixture CreateFromQuery;
-
- static void assertSameFields(const BSONObj& docA, const BSONObj& docB);
-
- /**
- * Recursively asserts that two BSONElements contain the same data or sub-elements,
- * ignoring element order.
- */
- static void assertSameElements(const BSONElement& elA, const BSONElement& elB) {
- if (elA.type() != elB.type() || (!elA.isABSONObj() && !elA.valuesEqual(elB))) {
+/**
+ * Recursively asserts that two BSONElements contain the same data or sub-elements,
+ * ignoring element order.
+ */
+static void assertSameElements(const BSONElement& elA, const BSONElement& elB) {
+ if (elA.type() != elB.type() || (!elA.isABSONObj() && !elA.valuesEqual(elB))) {
+ FAIL(stream() << "element " << elA << " not equal to " << elB);
+ } else if (elA.type() == mongo::Array) {
+ std::vector<BSONElement> elsA = elA.Array();
+ std::vector<BSONElement> elsB = elB.Array();
+ if (elsA.size() != elsB.size())
FAIL(stream() << "element " << elA << " not equal to " << elB);
- }
- else if (elA.type() == mongo::Array) {
- std::vector<BSONElement> elsA = elA.Array();
- std::vector<BSONElement> elsB = elB.Array();
- if (elsA.size() != elsB.size())
- FAIL(stream() << "element " << elA << " not equal to " << elB);
-
- std::vector<BSONElement>::iterator arrItA = elsA.begin();
- std::vector<BSONElement>::iterator arrItB = elsB.begin();
- for (; arrItA != elsA.end(); ++arrItA, ++arrItB) {
- assertSameElements(*arrItA, *arrItB);
- }
- }
- else if (elA.type() == mongo::Object) {
- assertSameFields(elA.Obj(), elB.Obj());
- }
- }
-
- /**
- * Recursively asserts that two BSONObjects contain the same elements,
- * ignoring element order.
- */
- static void assertSameFields(const BSONObj& docA, const BSONObj& docB) {
-
- if (docA.nFields() != docB.nFields())
- FAIL(stream() << "document " << docA << " has different fields than " << docB);
- std::map<StringData, BSONElement> docAMap;
- BSONObjIterator itA(docA);
- while (itA.more()) {
- BSONElement elA = itA.next();
- docAMap.insert(std::make_pair(elA.fieldNameStringData(), elA));
+ std::vector<BSONElement>::iterator arrItA = elsA.begin();
+ std::vector<BSONElement>::iterator arrItB = elsB.begin();
+ for (; arrItA != elsA.end(); ++arrItA, ++arrItB) {
+ assertSameElements(*arrItA, *arrItB);
}
-
- BSONObjIterator itB(docB);
- while (itB.more()) {
- BSONElement elB = itB.next();
-
- std::map<StringData, BSONElement>::iterator seenIt = docAMap.find(elB
- .fieldNameStringData());
- if (seenIt == docAMap.end())
- FAIL(stream() << "element " << elB << " not found in " << docA);
-
- BSONElement elA = seenIt->second;
- assertSameElements(elA, elB);
- }
- }
-
- TEST_F(CreateFromQuery, BasicOp) {
- BSONObj query = fromjson("{a:1,b:2}");
- ASSERT_OK(driverOps().populateDocumentWithQueryFields(query, NULL, doc()));
- assertSameFields(query, doc().getObject());
- }
-
- TEST_F(CreateFromQuery, BasicOpEq) {
- BSONObj query = fromjson("{a:{$eq:1}}");
- ASSERT_OK(driverOps().populateDocumentWithQueryFields(query, NULL, doc()));
- assertSameFields(fromjson("{a:1}"), doc().getObject());
- }
-
- TEST_F(CreateFromQuery, BasicOpWithId) {
- BSONObj query = fromjson("{_id:1,a:1,b:2}");
- ASSERT_OK(driverOps().populateDocumentWithQueryFields(query, NULL, doc()));
- assertSameFields(query, doc().getObject());
- }
-
- TEST_F(CreateFromQuery, BasicRepl) {
- BSONObj query = fromjson("{a:1,b:2}");
- ASSERT_OK(driverRepl().populateDocumentWithQueryFields(query, NULL, doc()));
- assertSameFields(fromjson("{}"), doc().getObject());
+ } else if (elA.type() == mongo::Object) {
+ assertSameFields(elA.Obj(), elB.Obj());
}
+}
- TEST_F(CreateFromQuery, BasicReplWithId) {
- BSONObj query = fromjson("{_id:1,a:1,b:2}");
- ASSERT_OK(driverRepl().populateDocumentWithQueryFields(query, NULL, doc()));
- assertSameFields(fromjson("{_id:1}"), doc().getObject());
- }
-
- TEST_F(CreateFromQuery, BasicReplWithIdEq) {
- BSONObj query = fromjson("{_id:{$eq:1},a:1,b:2}");
- ASSERT_OK(driverRepl().populateDocumentWithQueryFields(query, NULL, doc()));
- assertSameFields(fromjson("{_id:1}"), doc().getObject());
- }
-
- TEST_F(CreateFromQuery, NoRootIdOp) {
- BSONObj query = fromjson("{'_id.a':1,'_id.b':2}");
- ASSERT_OK(driverOps().populateDocumentWithQueryFields(query, NULL, doc()));
- assertSameFields(fromjson("{_id:{a:1,b:2}}"), doc().getObject());
- }
-
- TEST_F(CreateFromQuery, NoRootIdRepl) {
- BSONObj query = fromjson("{'_id.a':1,'_id.b':2}");
- ASSERT_NOT_OK(driverRepl().populateDocumentWithQueryFields(query, NULL, doc()));
- }
-
- TEST_F(CreateFromQuery, NestedSharedRootOp) {
- BSONObj query = fromjson("{'a.c':1,'a.b':{$eq:2}}");
- ASSERT_OK(driverOps().populateDocumentWithQueryFields(query, NULL, doc()));
- assertSameFields(fromjson("{a:{c:1,b:2}}"), doc().getObject());
- }
-
- TEST_F(CreateFromQuery, OrQueryOp) {
- BSONObj query = fromjson("{$or:[{a:1}]}");
- ASSERT_OK(driverOps().populateDocumentWithQueryFields(query, NULL, doc()));
- assertSameFields(fromjson("{a:1}"), doc().getObject());
- }
-
- TEST_F(CreateFromQuery, OrQueryIdRepl) {
- BSONObj query = fromjson("{$or:[{_id:1}]}");
- ASSERT_OK(driverRepl().populateDocumentWithQueryFields(query, NULL, doc()));
- assertSameFields(fromjson("{_id:1}"), doc().getObject());
- }
-
- TEST_F(CreateFromQuery, OrQueryNoExtractOps) {
- BSONObj query = fromjson("{$or:[{a:1}, {b:2}]}");
- ASSERT_OK(driverOps().populateDocumentWithQueryFields(query, NULL, doc()));
- assertSameFields(BSONObj(), doc().getObject());
- }
-
- TEST_F(CreateFromQuery, OrQueryNoExtractIdRepl) {
- BSONObj query = fromjson("{$or:[{_id:1}, {_id:2}]}");
- ASSERT_OK(driverRepl().populateDocumentWithQueryFields(query, NULL, doc()));
- assertSameFields(BSONObj(), doc().getObject());
- }
-
- TEST_F(CreateFromQuery, AndQueryOp) {
- BSONObj query = fromjson("{$and:[{'a.c':1},{'a.b':{$eq:2}}]}");
- ASSERT_OK(driverOps().populateDocumentWithQueryFields(query, NULL, doc()));
- assertSameFields(fromjson("{a:{c:1,b:2}}"), doc().getObject());
- }
-
- TEST_F(CreateFromQuery, AndQueryIdRepl) {
- BSONObj query = fromjson("{$and:[{_id:1},{a:{$eq:2}}]}");
- ASSERT_OK(driverRepl().populateDocumentWithQueryFields(query, NULL, doc()));
- assertSameFields(fromjson("{_id:1}"), doc().getObject());
- }
-
- TEST_F(CreateFromQuery, AllArrayOp) {
- BSONObj query = fromjson("{a:{$all:[1]}}");
- ASSERT_OK(driverOps().populateDocumentWithQueryFields(query, NULL, doc()));
- assertSameFields(fromjson("{a:1}"), doc().getObject());
- }
-
- TEST_F(CreateFromQuery, AllArrayIdRepl) {
- BSONObj query = fromjson("{_id:{$all:[1]}, b:2}");
- ASSERT_OK(driverRepl().populateDocumentWithQueryFields(query, NULL, doc()));
- assertSameFields(fromjson("{_id:1}"), doc().getObject());
- }
-
- TEST_F(CreateFromQuery, ConflictFieldsFailOp) {
- BSONObj query = fromjson("{a:1,'a.b':1}");
- ASSERT_NOT_OK(driverOps().populateDocumentWithQueryFields(query, NULL, doc()));
- }
-
- TEST_F(CreateFromQuery, ConflictFieldsFailSameValueOp) {
- BSONObj query = fromjson("{a:{b:1},'a.b':1}");
- ASSERT_NOT_OK(driverOps().populateDocumentWithQueryFields(query, NULL, doc()));
- }
-
- TEST_F(CreateFromQuery, ConflictWithIdRepl) {
- BSONObj query = fromjson("{_id:1,'_id.a':1}");
- ASSERT_NOT_OK(driverRepl().populateDocumentWithQueryFields(query, NULL, doc()));
- }
-
- TEST_F(CreateFromQuery, ConflictAndQueryOp) {
- BSONObj query = fromjson("{$and:[{a:{b:1}},{'a.b':{$eq:1}}]}");
- ASSERT_NOT_OK(driverOps().populateDocumentWithQueryFields(query, NULL, doc()));
- }
-
- TEST_F(CreateFromQuery, ConflictAllMultipleValsOp) {
- BSONObj query = fromjson("{a:{$all:[1, 2]}}");
- ASSERT_NOT_OK(driverOps().populateDocumentWithQueryFields(query, NULL, doc()));
- }
-
- TEST_F(CreateFromQuery, NoConflictOrQueryOp) {
- BSONObj query = fromjson("{$or:[{a:{b:1}},{'a.b':{$eq:1}}]}");
- ASSERT_OK(driverOps().populateDocumentWithQueryFields(query, NULL, doc()));
- assertSameFields(BSONObj(), doc().getObject());
- }
-
- TEST_F(CreateFromQuery, ImmutableFieldsOp) {
- BSONObj query = fromjson("{$or:[{a:{b:1}},{'a.b':{$eq:1}}]}");
- ASSERT_OK(driverOps().populateDocumentWithQueryFields(query, NULL, doc()));
- assertSameFields(BSONObj(), doc().getObject());
- }
-
- TEST_F(CreateFromQuery, ShardKeyRepl) {
- BSONObj query = fromjson("{a:{$eq:1}}, b:2}");
- OwnedPointerVector<FieldRef> immutablePaths;
- immutablePaths.push_back(new FieldRef("a"));
- ASSERT_OK(driverRepl().populateDocumentWithQueryFields(query,
- &immutablePaths.vector(),
- doc()));
- assertSameFields(fromjson("{a:1}"), doc().getObject());
- }
-
- TEST_F(CreateFromQuery, NestedShardKeyRepl) {
- BSONObj query = fromjson("{a:{$eq:1},'b.c':2},d:2}");
- OwnedPointerVector<FieldRef> immutablePaths;
- immutablePaths.push_back(new FieldRef("a"));
- immutablePaths.push_back(new FieldRef("b.c"));
- ASSERT_OK(driverRepl().populateDocumentWithQueryFields(query,
- &immutablePaths.vector(),
- doc()));
- assertSameFields(fromjson("{a:1,b:{c:2}}"), doc().getObject());
- }
-
- TEST_F(CreateFromQuery, NestedShardKeyOp) {
- BSONObj query = fromjson("{a:{$eq:1},'b.c':2,d:{$all:[3]}},e:2}");
- OwnedPointerVector<FieldRef> immutablePaths;
- immutablePaths.push_back(new FieldRef("a"));
- immutablePaths.push_back(new FieldRef("b.c"));
- ASSERT_OK(driverOps().populateDocumentWithQueryFields(query,
- &immutablePaths.vector(),
- doc()));
- assertSameFields(fromjson("{a:1,b:{c:2},d:3}"), doc().getObject());
- }
-
- TEST_F(CreateFromQuery, NotFullShardKeyRepl) {
- BSONObj query = fromjson("{a:{$eq:1}, 'b.c':2}, d:2}");
- OwnedPointerVector<FieldRef> immutablePaths;
- immutablePaths.push_back(new FieldRef("a"));
- immutablePaths.push_back(new FieldRef("b"));
- ASSERT_NOT_OK(driverRepl().populateDocumentWithQueryFields(query,
- &immutablePaths.vector(),
- doc()));
- }
-
-} // unnamed namespace
+/**
+ * Recursively asserts that two BSONObjects contain the same elements,
+ * ignoring element order.
+ */
+static void assertSameFields(const BSONObj& docA, const BSONObj& docB) {
+ if (docA.nFields() != docB.nFields())
+ FAIL(stream() << "document " << docA << " has different fields than " << docB);
+
+ std::map<StringData, BSONElement> docAMap;
+ BSONObjIterator itA(docA);
+ while (itA.more()) {
+ BSONElement elA = itA.next();
+ docAMap.insert(std::make_pair(elA.fieldNameStringData(), elA));
+ }
+
+ BSONObjIterator itB(docB);
+ while (itB.more()) {
+ BSONElement elB = itB.next();
+
+ std::map<StringData, BSONElement>::iterator seenIt =
+ docAMap.find(elB.fieldNameStringData());
+ if (seenIt == docAMap.end())
+ FAIL(stream() << "element " << elB << " not found in " << docA);
+
+ BSONElement elA = seenIt->second;
+ assertSameElements(elA, elB);
+ }
+}
+
+TEST_F(CreateFromQuery, BasicOp) {
+ BSONObj query = fromjson("{a:1,b:2}");
+ ASSERT_OK(driverOps().populateDocumentWithQueryFields(query, NULL, doc()));
+ assertSameFields(query, doc().getObject());
+}
+
+TEST_F(CreateFromQuery, BasicOpEq) {
+ BSONObj query = fromjson("{a:{$eq:1}}");
+ ASSERT_OK(driverOps().populateDocumentWithQueryFields(query, NULL, doc()));
+ assertSameFields(fromjson("{a:1}"), doc().getObject());
+}
+
+TEST_F(CreateFromQuery, BasicOpWithId) {
+ BSONObj query = fromjson("{_id:1,a:1,b:2}");
+ ASSERT_OK(driverOps().populateDocumentWithQueryFields(query, NULL, doc()));
+ assertSameFields(query, doc().getObject());
+}
+
+TEST_F(CreateFromQuery, BasicRepl) {
+ BSONObj query = fromjson("{a:1,b:2}");
+ ASSERT_OK(driverRepl().populateDocumentWithQueryFields(query, NULL, doc()));
+ assertSameFields(fromjson("{}"), doc().getObject());
+}
+
+TEST_F(CreateFromQuery, BasicReplWithId) {
+ BSONObj query = fromjson("{_id:1,a:1,b:2}");
+ ASSERT_OK(driverRepl().populateDocumentWithQueryFields(query, NULL, doc()));
+ assertSameFields(fromjson("{_id:1}"), doc().getObject());
+}
+
+TEST_F(CreateFromQuery, BasicReplWithIdEq) {
+ BSONObj query = fromjson("{_id:{$eq:1},a:1,b:2}");
+ ASSERT_OK(driverRepl().populateDocumentWithQueryFields(query, NULL, doc()));
+ assertSameFields(fromjson("{_id:1}"), doc().getObject());
+}
+
+TEST_F(CreateFromQuery, NoRootIdOp) {
+ BSONObj query = fromjson("{'_id.a':1,'_id.b':2}");
+ ASSERT_OK(driverOps().populateDocumentWithQueryFields(query, NULL, doc()));
+ assertSameFields(fromjson("{_id:{a:1,b:2}}"), doc().getObject());
+}
+
+TEST_F(CreateFromQuery, NoRootIdRepl) {
+ BSONObj query = fromjson("{'_id.a':1,'_id.b':2}");
+ ASSERT_NOT_OK(driverRepl().populateDocumentWithQueryFields(query, NULL, doc()));
+}
+
+TEST_F(CreateFromQuery, NestedSharedRootOp) {
+ BSONObj query = fromjson("{'a.c':1,'a.b':{$eq:2}}");
+ ASSERT_OK(driverOps().populateDocumentWithQueryFields(query, NULL, doc()));
+ assertSameFields(fromjson("{a:{c:1,b:2}}"), doc().getObject());
+}
+
+TEST_F(CreateFromQuery, OrQueryOp) {
+ BSONObj query = fromjson("{$or:[{a:1}]}");
+ ASSERT_OK(driverOps().populateDocumentWithQueryFields(query, NULL, doc()));
+ assertSameFields(fromjson("{a:1}"), doc().getObject());
+}
+
+TEST_F(CreateFromQuery, OrQueryIdRepl) {
+ BSONObj query = fromjson("{$or:[{_id:1}]}");
+ ASSERT_OK(driverRepl().populateDocumentWithQueryFields(query, NULL, doc()));
+ assertSameFields(fromjson("{_id:1}"), doc().getObject());
+}
+
+TEST_F(CreateFromQuery, OrQueryNoExtractOps) {
+ BSONObj query = fromjson("{$or:[{a:1}, {b:2}]}");
+ ASSERT_OK(driverOps().populateDocumentWithQueryFields(query, NULL, doc()));
+ assertSameFields(BSONObj(), doc().getObject());
+}
+
+TEST_F(CreateFromQuery, OrQueryNoExtractIdRepl) {
+ BSONObj query = fromjson("{$or:[{_id:1}, {_id:2}]}");
+ ASSERT_OK(driverRepl().populateDocumentWithQueryFields(query, NULL, doc()));
+ assertSameFields(BSONObj(), doc().getObject());
+}
+
+TEST_F(CreateFromQuery, AndQueryOp) {
+ BSONObj query = fromjson("{$and:[{'a.c':1},{'a.b':{$eq:2}}]}");
+ ASSERT_OK(driverOps().populateDocumentWithQueryFields(query, NULL, doc()));
+ assertSameFields(fromjson("{a:{c:1,b:2}}"), doc().getObject());
+}
+
+TEST_F(CreateFromQuery, AndQueryIdRepl) {
+ BSONObj query = fromjson("{$and:[{_id:1},{a:{$eq:2}}]}");
+ ASSERT_OK(driverRepl().populateDocumentWithQueryFields(query, NULL, doc()));
+ assertSameFields(fromjson("{_id:1}"), doc().getObject());
+}
+
+TEST_F(CreateFromQuery, AllArrayOp) {
+ BSONObj query = fromjson("{a:{$all:[1]}}");
+ ASSERT_OK(driverOps().populateDocumentWithQueryFields(query, NULL, doc()));
+ assertSameFields(fromjson("{a:1}"), doc().getObject());
+}
+
+TEST_F(CreateFromQuery, AllArrayIdRepl) {
+ BSONObj query = fromjson("{_id:{$all:[1]}, b:2}");
+ ASSERT_OK(driverRepl().populateDocumentWithQueryFields(query, NULL, doc()));
+ assertSameFields(fromjson("{_id:1}"), doc().getObject());
+}
+
+TEST_F(CreateFromQuery, ConflictFieldsFailOp) {
+ BSONObj query = fromjson("{a:1,'a.b':1}");
+ ASSERT_NOT_OK(driverOps().populateDocumentWithQueryFields(query, NULL, doc()));
+}
+
+TEST_F(CreateFromQuery, ConflictFieldsFailSameValueOp) {
+ BSONObj query = fromjson("{a:{b:1},'a.b':1}");
+ ASSERT_NOT_OK(driverOps().populateDocumentWithQueryFields(query, NULL, doc()));
+}
+
+TEST_F(CreateFromQuery, ConflictWithIdRepl) {
+ BSONObj query = fromjson("{_id:1,'_id.a':1}");
+ ASSERT_NOT_OK(driverRepl().populateDocumentWithQueryFields(query, NULL, doc()));
+}
+
+TEST_F(CreateFromQuery, ConflictAndQueryOp) {
+ BSONObj query = fromjson("{$and:[{a:{b:1}},{'a.b':{$eq:1}}]}");
+ ASSERT_NOT_OK(driverOps().populateDocumentWithQueryFields(query, NULL, doc()));
+}
+
+TEST_F(CreateFromQuery, ConflictAllMultipleValsOp) {
+ BSONObj query = fromjson("{a:{$all:[1, 2]}}");
+ ASSERT_NOT_OK(driverOps().populateDocumentWithQueryFields(query, NULL, doc()));
+}
+
+TEST_F(CreateFromQuery, NoConflictOrQueryOp) {
+ BSONObj query = fromjson("{$or:[{a:{b:1}},{'a.b':{$eq:1}}]}");
+ ASSERT_OK(driverOps().populateDocumentWithQueryFields(query, NULL, doc()));
+ assertSameFields(BSONObj(), doc().getObject());
+}
+
+TEST_F(CreateFromQuery, ImmutableFieldsOp) {
+ BSONObj query = fromjson("{$or:[{a:{b:1}},{'a.b':{$eq:1}}]}");
+ ASSERT_OK(driverOps().populateDocumentWithQueryFields(query, NULL, doc()));
+ assertSameFields(BSONObj(), doc().getObject());
+}
+
+TEST_F(CreateFromQuery, ShardKeyRepl) {
+ BSONObj query = fromjson("{a:{$eq:1}}, b:2}");
+ OwnedPointerVector<FieldRef> immutablePaths;
+ immutablePaths.push_back(new FieldRef("a"));
+ ASSERT_OK(driverRepl().populateDocumentWithQueryFields(query, &immutablePaths.vector(), doc()));
+ assertSameFields(fromjson("{a:1}"), doc().getObject());
+}
+
+TEST_F(CreateFromQuery, NestedShardKeyRepl) {
+ BSONObj query = fromjson("{a:{$eq:1},'b.c':2},d:2}");
+ OwnedPointerVector<FieldRef> immutablePaths;
+ immutablePaths.push_back(new FieldRef("a"));
+ immutablePaths.push_back(new FieldRef("b.c"));
+ ASSERT_OK(driverRepl().populateDocumentWithQueryFields(query, &immutablePaths.vector(), doc()));
+ assertSameFields(fromjson("{a:1,b:{c:2}}"), doc().getObject());
+}
+
+TEST_F(CreateFromQuery, NestedShardKeyOp) {
+ BSONObj query = fromjson("{a:{$eq:1},'b.c':2,d:{$all:[3]}},e:2}");
+ OwnedPointerVector<FieldRef> immutablePaths;
+ immutablePaths.push_back(new FieldRef("a"));
+ immutablePaths.push_back(new FieldRef("b.c"));
+ ASSERT_OK(driverOps().populateDocumentWithQueryFields(query, &immutablePaths.vector(), doc()));
+ assertSameFields(fromjson("{a:1,b:{c:2},d:3}"), doc().getObject());
+}
+
+TEST_F(CreateFromQuery, NotFullShardKeyRepl) {
+ BSONObj query = fromjson("{a:{$eq:1}, 'b.c':2}, d:2}");
+ OwnedPointerVector<FieldRef> immutablePaths;
+ immutablePaths.push_back(new FieldRef("a"));
+ immutablePaths.push_back(new FieldRef("b"));
+ ASSERT_NOT_OK(
+ driverRepl().populateDocumentWithQueryFields(query, &immutablePaths.vector(), doc()));
+}
+
+} // unnamed namespace
diff --git a/src/mongo/db/ops/update_lifecycle.h b/src/mongo/db/ops/update_lifecycle.h
index 93811f28cff..9454aef1c1e 100644
--- a/src/mongo/db/ops/update_lifecycle.h
+++ b/src/mongo/db/ops/update_lifecycle.h
@@ -32,42 +32,41 @@
namespace mongo {
- class Collection;
- class FieldRef;
- class OperationContext;
- class UpdateIndexData;
+class Collection;
+class FieldRef;
+class OperationContext;
+class UpdateIndexData;
- class UpdateLifecycle {
- public:
+class UpdateLifecycle {
+public:
+ virtual ~UpdateLifecycle() {}
- virtual ~UpdateLifecycle() {}
+ /**
+ * Update the cached collection pointer that this lifecycle object uses.
+ */
+ virtual void setCollection(Collection* collection) = 0;
- /**
- * Update the cached collection pointer that this lifecycle object uses.
- */
- virtual void setCollection(Collection* collection) = 0;
+ /**
+ * Can the update continue?
+ *
+ * The (only) implementation will check the following:
+ * 1.) Collection still exists
+ * 2.) Shard version has not changed (indicating that the query/update is not valid
+ */
+ virtual bool canContinue() const = 0;
- /**
- * Can the update continue?
- *
- * The (only) implementation will check the following:
- * 1.) Collection still exists
- * 2.) Shard version has not changed (indicating that the query/update is not valid
- */
- virtual bool canContinue() const = 0;
+ /**
+ * Return a pointer to any indexes if there is a collection.
+ */
+ virtual const UpdateIndexData* getIndexKeys(OperationContext* opCtx) const = 0;
- /**
- * Return a pointer to any indexes if there is a collection.
- */
- virtual const UpdateIndexData* getIndexKeys( OperationContext* opCtx ) const = 0;
+ /**
+ * Returns the shard keys as immutable fields
+ * Immutable fields in this case mean that they are required to exist, cannot change values
+ * and must not be multi-valued (in an array, or an array)
+ */
+ virtual const std::vector<FieldRef*>* getImmutableFields() const = 0;
+};
- /**
- * Returns the shard keys as immutable fields
- * Immutable fields in this case mean that they are required to exist, cannot change values
- * and must not be multi-valued (in an array, or an array)
- */
- virtual const std::vector<FieldRef*>* getImmutableFields() const = 0;
- };
-
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/db/ops/update_lifecycle_impl.cpp b/src/mongo/db/ops/update_lifecycle_impl.cpp
index fed352c4fa4..95c15f2d04b 100644
--- a/src/mongo/db/ops/update_lifecycle_impl.cpp
+++ b/src/mongo/db/ops/update_lifecycle_impl.cpp
@@ -36,46 +36,45 @@
#include "mongo/s/d_state.h"
namespace mongo {
- namespace {
- CollectionMetadataPtr getMetadata(const NamespaceString& nsString) {
- if (shardingState.enabled()) {
- return shardingState.getCollectionMetadata(nsString.ns());
- }
-
- return CollectionMetadataPtr();
- }
+namespace {
+CollectionMetadataPtr getMetadata(const NamespaceString& nsString) {
+ if (shardingState.enabled()) {
+ return shardingState.getCollectionMetadata(nsString.ns());
}
- UpdateLifecycleImpl::UpdateLifecycleImpl(bool ignoreVersion, const NamespaceString& nsStr)
- : _nsString(nsStr)
- , _shardVersion((!ignoreVersion && getMetadata(_nsString)) ?
- getMetadata(_nsString)->getShardVersion() :
- ChunkVersion::IGNORED()) {
- }
+ return CollectionMetadataPtr();
+}
+}
- void UpdateLifecycleImpl::setCollection(Collection* collection) {
- _collection = collection;
- }
+UpdateLifecycleImpl::UpdateLifecycleImpl(bool ignoreVersion, const NamespaceString& nsStr)
+ : _nsString(nsStr),
+ _shardVersion((!ignoreVersion && getMetadata(_nsString))
+ ? getMetadata(_nsString)->getShardVersion()
+ : ChunkVersion::IGNORED()) {}
- bool UpdateLifecycleImpl::canContinue() const {
- // Collection needs to exist to continue
- return _collection;
- }
+void UpdateLifecycleImpl::setCollection(Collection* collection) {
+ _collection = collection;
+}
- const UpdateIndexData* UpdateLifecycleImpl::getIndexKeys(OperationContext* opCtx) const {
- if (_collection)
- return &_collection->infoCache()->indexKeys(opCtx);
- return NULL;
- }
+bool UpdateLifecycleImpl::canContinue() const {
+ // Collection needs to exist to continue
+ return _collection;
+}
+
+const UpdateIndexData* UpdateLifecycleImpl::getIndexKeys(OperationContext* opCtx) const {
+ if (_collection)
+ return &_collection->infoCache()->indexKeys(opCtx);
+ return NULL;
+}
- const std::vector<FieldRef*>* UpdateLifecycleImpl::getImmutableFields() const {
- CollectionMetadataPtr metadata = getMetadata(_nsString);
- if (metadata) {
- const std::vector<FieldRef*>& fields = metadata->getKeyPatternFields();
- // Return shard-keys as immutable for the update system.
- return &fields;
- }
- return NULL;
+const std::vector<FieldRef*>* UpdateLifecycleImpl::getImmutableFields() const {
+ CollectionMetadataPtr metadata = getMetadata(_nsString);
+ if (metadata) {
+ const std::vector<FieldRef*>& fields = metadata->getKeyPatternFields();
+ // Return shard-keys as immutable for the update system.
+ return &fields;
}
+ return NULL;
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/db/ops/update_lifecycle_impl.h b/src/mongo/db/ops/update_lifecycle_impl.h
index c321b43912d..114ebd72ba2 100644
--- a/src/mongo/db/ops/update_lifecycle_impl.h
+++ b/src/mongo/db/ops/update_lifecycle_impl.h
@@ -35,31 +35,30 @@
namespace mongo {
- class UpdateLifecycleImpl : public UpdateLifecycle {
- MONGO_DISALLOW_COPYING(UpdateLifecycleImpl);
+class UpdateLifecycleImpl : public UpdateLifecycle {
+ MONGO_DISALLOW_COPYING(UpdateLifecycleImpl);
- public:
+public:
+ /**
+ * ignoreVersion is for shard version checking and
+ * means that version checks will not be done
+ *
+ * nsString represents the namespace for the
+ */
+ UpdateLifecycleImpl(bool ignoreVersion, const NamespaceString& nsString);
- /**
- * ignoreVersion is for shard version checking and
- * means that version checks will not be done
- *
- * nsString represents the namespace for the
- */
- UpdateLifecycleImpl(bool ignoreVersion, const NamespaceString& nsString);
+ virtual void setCollection(Collection* collection);
- virtual void setCollection(Collection* collection);
+ virtual bool canContinue() const;
- virtual bool canContinue() const;
+ virtual const UpdateIndexData* getIndexKeys(OperationContext* opCtx) const;
- virtual const UpdateIndexData* getIndexKeys(OperationContext* opCtx) const;
+ virtual const std::vector<FieldRef*>* getImmutableFields() const;
- virtual const std::vector<FieldRef*>* getImmutableFields() const;
-
- private:
- Collection* _collection;
- const NamespaceString& _nsString;
- ChunkVersion _shardVersion;
- };
+private:
+ Collection* _collection;
+ const NamespaceString& _nsString;
+ ChunkVersion _shardVersion;
+};
} /* namespace mongo */
diff --git a/src/mongo/db/ops/update_request.h b/src/mongo/db/ops/update_request.h
index d300a8fc4f2..589be047046 100644
--- a/src/mongo/db/ops/update_request.h
+++ b/src/mongo/db/ops/update_request.h
@@ -36,212 +36,206 @@
namespace mongo {
- namespace str = mongoutils::str;
-
- class FieldRef;
- class UpdateLifecycle;
-
- class UpdateRequest {
- public:
- enum ReturnDocOption {
- // Return no document.
- RETURN_NONE,
-
- // Return the document as it was before the update. If the update results in an insert,
- // no document will be returned.
- RETURN_OLD,
-
- // Return the document as it is after the update.
- RETURN_NEW
- };
- inline UpdateRequest(const NamespaceString& nsString)
- : _nsString(nsString)
- , _god(false)
- , _upsert(false)
- , _multi(false)
- , _fromMigration(false)
- , _lifecycle(NULL)
- , _isExplain(false)
- , _returnDocs(ReturnDocOption::RETURN_NONE)
- , _yieldPolicy(PlanExecutor::YIELD_MANUAL) {}
-
- const NamespaceString& getNamespaceString() const {
- return _nsString;
- }
-
- inline void setQuery(const BSONObj& query) {
- _query = query;
- }
-
- inline const BSONObj& getQuery() const {
- return _query;
- }
-
- inline void setProj(const BSONObj& proj) {
- _proj = proj;
- }
-
- inline const BSONObj& getProj() const {
- return _proj;
- }
-
- inline void setSort(const BSONObj& sort) {
- _sort = sort;
- }
-
- inline const BSONObj& getSort() const {
- return _sort;
- }
-
- inline void setUpdates(const BSONObj& updates) {
- _updates = updates;
- }
-
- inline const BSONObj& getUpdates() const {
- return _updates;
- }
-
- // Please see documentation on the private members matching these names for
- // explanations of the following fields.
-
- inline void setGod(bool value = true) {
- _god = value;
- }
-
- bool isGod() const {
- return _god;
- }
-
- inline void setUpsert(bool value = true) {
- _upsert = value;
- }
-
- bool isUpsert() const {
- return _upsert;
- }
-
- inline void setMulti(bool value = true) {
- _multi = value;
- }
-
- bool isMulti() const {
- return _multi;
- }
-
- inline void setFromMigration(bool value = true) {
- _fromMigration = value;
- }
-
- bool isFromMigration() const {
- return _fromMigration;
- }
-
- inline void setLifecycle(UpdateLifecycle* value) {
- _lifecycle = value;
- }
-
- inline UpdateLifecycle* getLifecycle() const {
- return _lifecycle;
- }
-
- inline void setExplain(bool value = true) {
- _isExplain = value;
- }
-
- inline bool isExplain() const {
- return _isExplain;
- }
-
- inline void setReturnDocs(ReturnDocOption value) {
- _returnDocs = value;
- }
-
- inline bool shouldReturnOldDocs() const {
- return _returnDocs == ReturnDocOption::RETURN_OLD;
- }
-
- inline bool shouldReturnNewDocs() const {
- return _returnDocs == ReturnDocOption::RETURN_NEW;
- }
+namespace str = mongoutils::str;
- inline bool shouldReturnAnyDocs() const {
- return shouldReturnOldDocs() || shouldReturnNewDocs();
- }
+class FieldRef;
+class UpdateLifecycle;
- inline void setYieldPolicy(PlanExecutor::YieldPolicy yieldPolicy) {
- _yieldPolicy = yieldPolicy;
- }
+class UpdateRequest {
+public:
+ enum ReturnDocOption {
+ // Return no document.
+ RETURN_NONE,
- inline PlanExecutor::YieldPolicy getYieldPolicy() const {
- return _yieldPolicy;
- }
+ // Return the document as it was before the update. If the update results in an insert,
+ // no document will be returned.
+ RETURN_OLD,
- const std::string toString() const {
- return str::stream()
- << " query: " << _query
- << " projection: " << _proj
- << " sort: " << _sort
- << " updated: " << _updates
- << " god: " << _god
- << " upsert: " << _upsert
- << " multi: " << _multi
- << " fromMigration: " << _fromMigration
- << " isExplain: " << _isExplain;
- }
- private:
-
- const NamespaceString& _nsString;
+ // Return the document as it is after the update.
+ RETURN_NEW
+ };
+ inline UpdateRequest(const NamespaceString& nsString)
+ : _nsString(nsString),
+ _god(false),
+ _upsert(false),
+ _multi(false),
+ _fromMigration(false),
+ _lifecycle(NULL),
+ _isExplain(false),
+ _returnDocs(ReturnDocOption::RETURN_NONE),
+ _yieldPolicy(PlanExecutor::YIELD_MANUAL) {}
+
+ const NamespaceString& getNamespaceString() const {
+ return _nsString;
+ }
+
+ inline void setQuery(const BSONObj& query) {
+ _query = query;
+ }
+
+ inline const BSONObj& getQuery() const {
+ return _query;
+ }
+
+ inline void setProj(const BSONObj& proj) {
+ _proj = proj;
+ }
+
+ inline const BSONObj& getProj() const {
+ return _proj;
+ }
+
+ inline void setSort(const BSONObj& sort) {
+ _sort = sort;
+ }
+
+ inline const BSONObj& getSort() const {
+ return _sort;
+ }
+
+ inline void setUpdates(const BSONObj& updates) {
+ _updates = updates;
+ }
+
+ inline const BSONObj& getUpdates() const {
+ return _updates;
+ }
- // Contains the query that selects documents to update.
- BSONObj _query;
-
- // Contains the projection information.
- BSONObj _proj;
-
- // Contains the sort order information.
- BSONObj _sort;
+ // Please see documentation on the private members matching these names for
+ // explanations of the following fields.
+
+ inline void setGod(bool value = true) {
+ _god = value;
+ }
- // Contains the modifiers to apply to matched objects, or a replacement document.
- BSONObj _updates;
+ bool isGod() const {
+ return _god;
+ }
- // Flags controlling the update.
-
- // God bypasses _id checking and index generation. It is only used on behalf of system
- // updates, never user updates.
- bool _god;
-
- // True if this should insert if no matching document is found.
- bool _upsert;
-
- // True if this update is allowed to affect more than one document.
- bool _multi;
+ inline void setUpsert(bool value = true) {
+ _upsert = value;
+ }
- // True if this update is on behalf of a chunk migration.
- bool _fromMigration;
-
- // The lifecycle data, and events used during the update request.
- UpdateLifecycle* _lifecycle;
-
- // Whether or not we are requesting an explained update. Explained updates are read-only.
- bool _isExplain;
-
- // Specifies which version of the documents to return, if any.
- //
- // RETURN_NONE (default): Never return any documents, old or new.
- // RETURN_OLD: Return ADVANCED when a matching document is encountered, and the value of
- // the document before it was updated. If there were no matches, return
- // IS_EOF instead (even in case of an upsert).
- // RETURN_NEW: Return ADVANCED when a matching document is encountered, and the value of
- // the document after being updated. If an upsert was specified and it
- // resulted in an insert, return the inserted document.
- //
- // This allows findAndModify to execute an update and retrieve the resulting document
- // without another query before or after the update.
- ReturnDocOption _returnDocs;
-
- // Whether or not the update should yield. Defaults to YIELD_MANUAL.
- PlanExecutor::YieldPolicy _yieldPolicy;
+ bool isUpsert() const {
+ return _upsert;
+ }
- };
+ inline void setMulti(bool value = true) {
+ _multi = value;
+ }
+
+ bool isMulti() const {
+ return _multi;
+ }
+
+ inline void setFromMigration(bool value = true) {
+ _fromMigration = value;
+ }
+
+ bool isFromMigration() const {
+ return _fromMigration;
+ }
+
+ inline void setLifecycle(UpdateLifecycle* value) {
+ _lifecycle = value;
+ }
+
+ inline UpdateLifecycle* getLifecycle() const {
+ return _lifecycle;
+ }
+
+ inline void setExplain(bool value = true) {
+ _isExplain = value;
+ }
+
+ inline bool isExplain() const {
+ return _isExplain;
+ }
+
+ inline void setReturnDocs(ReturnDocOption value) {
+ _returnDocs = value;
+ }
+
+ inline bool shouldReturnOldDocs() const {
+ return _returnDocs == ReturnDocOption::RETURN_OLD;
+ }
+
+ inline bool shouldReturnNewDocs() const {
+ return _returnDocs == ReturnDocOption::RETURN_NEW;
+ }
+
+ inline bool shouldReturnAnyDocs() const {
+ return shouldReturnOldDocs() || shouldReturnNewDocs();
+ }
+
+ inline void setYieldPolicy(PlanExecutor::YieldPolicy yieldPolicy) {
+ _yieldPolicy = yieldPolicy;
+ }
+
+ inline PlanExecutor::YieldPolicy getYieldPolicy() const {
+ return _yieldPolicy;
+ }
+
+ const std::string toString() const {
+ return str::stream() << " query: " << _query << " projection: " << _proj
+ << " sort: " << _sort << " updated: " << _updates << " god: " << _god
+ << " upsert: " << _upsert << " multi: " << _multi
+ << " fromMigration: " << _fromMigration
+ << " isExplain: " << _isExplain;
+ }
+
+private:
+ const NamespaceString& _nsString;
+
+ // Contains the query that selects documents to update.
+ BSONObj _query;
+
+ // Contains the projection information.
+ BSONObj _proj;
+
+ // Contains the sort order information.
+ BSONObj _sort;
+
+ // Contains the modifiers to apply to matched objects, or a replacement document.
+ BSONObj _updates;
+
+ // Flags controlling the update.
+
+ // God bypasses _id checking and index generation. It is only used on behalf of system
+ // updates, never user updates.
+ bool _god;
+
+ // True if this should insert if no matching document is found.
+ bool _upsert;
+
+ // True if this update is allowed to affect more than one document.
+ bool _multi;
+
+ // True if this update is on behalf of a chunk migration.
+ bool _fromMigration;
+
+ // The lifecycle data, and events used during the update request.
+ UpdateLifecycle* _lifecycle;
+
+ // Whether or not we are requesting an explained update. Explained updates are read-only.
+ bool _isExplain;
+
+ // Specifies which version of the documents to return, if any.
+ //
+ // RETURN_NONE (default): Never return any documents, old or new.
+ // RETURN_OLD: Return ADVANCED when a matching document is encountered, and the value of
+ // the document before it was updated. If there were no matches, return
+ // IS_EOF instead (even in case of an upsert).
+ // RETURN_NEW: Return ADVANCED when a matching document is encountered, and the value of
+ // the document after being updated. If an upsert was specified and it
+ // resulted in an insert, return the inserted document.
+ //
+ // This allows findAndModify to execute an update and retrieve the resulting document
+ // without another query before or after the update.
+ ReturnDocOption _returnDocs;
+
+ // Whether or not the update should yield. Defaults to YIELD_MANUAL.
+ PlanExecutor::YieldPolicy _yieldPolicy;
+};
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/db/ops/update_result.cpp b/src/mongo/db/ops/update_result.cpp
index 64b33243131..93f48fae668 100644
--- a/src/mongo/db/ops/update_result.cpp
+++ b/src/mongo/db/ops/update_result.cpp
@@ -39,21 +39,20 @@
namespace mongo {
- UpdateResult::UpdateResult(bool existing_,
- bool modifiers_,
- unsigned long long numDocsModified_,
- unsigned long long numMatched_,
- const BSONObj& upsertedObject_)
- : existing(existing_),
- modifiers(modifiers_),
- numDocsModified(numDocsModified_),
- numMatched(numMatched_) {
-
- BSONElement id = upsertedObject_["_id"];
- if ( ! existing && numMatched == 1 && !id.eoo() ) {
- upserted = id.wrap(kUpsertedFieldName);
- }
- LOG(4) << "UpdateResult -- " << toString();
+UpdateResult::UpdateResult(bool existing_,
+ bool modifiers_,
+ unsigned long long numDocsModified_,
+ unsigned long long numMatched_,
+ const BSONObj& upsertedObject_)
+ : existing(existing_),
+ modifiers(modifiers_),
+ numDocsModified(numDocsModified_),
+ numMatched(numMatched_) {
+ BSONElement id = upsertedObject_["_id"];
+ if (!existing && numMatched == 1 && !id.eoo()) {
+ upserted = id.wrap(kUpsertedFieldName);
}
+ LOG(4) << "UpdateResult -- " << toString();
+}
} // namespace mongo
diff --git a/src/mongo/db/ops/update_result.h b/src/mongo/db/ops/update_result.h
index 568da353554..9c1c27c5a93 100644
--- a/src/mongo/db/ops/update_result.h
+++ b/src/mongo/db/ops/update_result.h
@@ -35,40 +35,36 @@
namespace mongo {
- namespace str = mongoutils::str;
+namespace str = mongoutils::str;
- struct UpdateResult {
+struct UpdateResult {
+ UpdateResult(bool existing_,
+ bool modifiers_,
+ unsigned long long numDocsModified_,
+ unsigned long long numMatched_,
+ const BSONObj& upsertedObject_);
- UpdateResult( bool existing_,
- bool modifiers_,
- unsigned long long numDocsModified_,
- unsigned long long numMatched_,
- const BSONObj& upsertedObject_);
+ // if existing objects were modified
+ const bool existing;
- // if existing objects were modified
- const bool existing;
+ // was this a $ mod
+ const bool modifiers;
- // was this a $ mod
- const bool modifiers;
+ // how many docs updated
+ const long long numDocsModified;
- // how many docs updated
- const long long numDocsModified;
+ // how many docs seen by update
+ const long long numMatched;
- // how many docs seen by update
- const long long numMatched;
+ // if something was upserted, the new _id of the object
+ BSONObj upserted;
- // if something was upserted, the new _id of the object
- BSONObj upserted;
+ const std::string toString() const {
+ return str::stream() << " upserted: " << upserted << " modifiers: " << modifiers
+ << " existing: " << existing << " numDocsModified: " << numDocsModified
+ << " numMatched: " << numMatched;
+ }
+};
- const std::string toString() const {
- return str::stream()
- << " upserted: " << upserted
- << " modifiers: " << modifiers
- << " existing: " << existing
- << " numDocsModified: " << numDocsModified
- << " numMatched: " << numMatched;
- }
- };
-
-} // namespace mongo
+} // namespace mongo