diff options
author | Mark Benvenuto <mark.benvenuto@mongodb.com> | 2015-06-20 00:22:50 -0400 |
---|---|---|
committer | Mark Benvenuto <mark.benvenuto@mongodb.com> | 2015-06-20 10:56:02 -0400 |
commit | 9c2ed42daa8fbbef4a919c21ec564e2db55e8d60 (patch) | |
tree | 3814f79c10d7b490948d8cb7b112ac1dd41ceff1 /src/mongo/db/exec/update.cpp | |
parent | 01965cf52bce6976637ecb8f4a622aeb05ab256a (diff) | |
download | mongo-9c2ed42daa8fbbef4a919c21ec564e2db55e8d60.tar.gz |
SERVER-18579: Clang-Format - reformat code, no comment reflow
Diffstat (limited to 'src/mongo/db/exec/update.cpp')
-rw-r--r-- | src/mongo/db/exec/update.cpp | 1819 |
1 files changed, 887 insertions, 932 deletions
diff --git a/src/mongo/db/exec/update.cpp b/src/mongo/db/exec/update.cpp index 8ba566cd7e0..7d2a768664f 100644 --- a/src/mongo/db/exec/update.cpp +++ b/src/mongo/db/exec/update.cpp @@ -46,1070 +46,1025 @@ namespace mongo { - using std::unique_ptr; - using std::string; - using std::vector; +using std::unique_ptr; +using std::string; +using std::vector; - namespace mb = mutablebson; +namespace mb = mutablebson; - namespace { +namespace { - const char idFieldName[] = "_id"; - const FieldRef idFieldRef(idFieldName); +const char idFieldName[] = "_id"; +const FieldRef idFieldRef(idFieldName); - Status storageValid(const mb::Document&, const bool = true); - Status storageValid(const mb::ConstElement&, const bool = true); - Status storageValidChildren(const mb::ConstElement&, const bool = true); +Status storageValid(const mb::Document&, const bool = true); +Status storageValid(const mb::ConstElement&, const bool = true); +Status storageValidChildren(const mb::ConstElement&, const bool = true); - /** - * mutable::document storageValid check -- like BSONObj::_okForStorage - */ - Status storageValid(const mb::Document& doc, const bool deep) { - mb::ConstElement currElem = doc.root().leftChild(); - while (currElem.ok()) { - if (currElem.getFieldName() == idFieldName) { - switch (currElem.getType()) { - case RegEx: - case Array: - case Undefined: - return Status(ErrorCodes::InvalidIdField, - str::stream() << "The '_id' value cannot be of type " - << typeName(currElem.getType())); - default: - break; - } - } - Status s = storageValid(currElem, deep); - if (!s.isOK()) - return s; - currElem = currElem.rightSibling(); +/** + * mutable::document storageValid check -- like BSONObj::_okForStorage + */ +Status storageValid(const mb::Document& doc, const bool deep) { + mb::ConstElement currElem = doc.root().leftChild(); + while (currElem.ok()) { + if (currElem.getFieldName() == idFieldName) { + switch (currElem.getType()) { + case RegEx: + case Array: + case Undefined: + return Status(ErrorCodes::InvalidIdField, + str::stream() << "The '_id' value cannot be of type " + << typeName(currElem.getType())); + default: + break; } + } + Status s = storageValid(currElem, deep); + if (!s.isOK()) + return s; + currElem = currElem.rightSibling(); + } + + return Status::OK(); +} - return Status::OK(); +/** + * Validates an element that has a field name which starts with a dollar sign ($). + * In the case of a DBRef field ($id, $ref, [$db]) these fields may be valid in + * the correct order/context only. + */ +Status validateDollarPrefixElement(const mb::ConstElement elem, const bool deep) { + mb::ConstElement curr = elem; + StringData currName = elem.getFieldName(); + LOG(5) << "validateDollarPrefixElement -- validating field '" << currName << "'"; + // Found a $db field + if (currName == "$db") { + if (curr.getType() != String) { + return Status(ErrorCodes::InvalidDBRef, + str::stream() << "The DBRef $db field must be a String, not a " + << typeName(curr.getType())); } + curr = curr.leftSibling(); - /** - * Validates an element that has a field name which starts with a dollar sign ($). - * In the case of a DBRef field ($id, $ref, [$db]) these fields may be valid in - * the correct order/context only. - */ - Status validateDollarPrefixElement(const mb::ConstElement elem, const bool deep) { - mb::ConstElement curr = elem; - StringData currName = elem.getFieldName(); - LOG(5) << "validateDollarPrefixElement -- validating field '" << currName << "'"; - // Found a $db field - if (currName == "$db") { - if (curr.getType() != String) { - return Status(ErrorCodes::InvalidDBRef, - str::stream() << "The DBRef $db field must be a String, not a " - << typeName(curr.getType())); - } - curr = curr.leftSibling(); + if (!curr.ok() || (curr.getFieldName() != "$id")) + return Status(ErrorCodes::InvalidDBRef, + "Found $db field without a $id before it, which is invalid."); - if (!curr.ok() || (curr.getFieldName() != "$id")) - return Status(ErrorCodes::InvalidDBRef, - "Found $db field without a $id before it, which is invalid."); + currName = curr.getFieldName(); + } - currName = curr.getFieldName(); - } + // Found a $id field + if (currName == "$id") { + Status s = storageValidChildren(curr, deep); + if (!s.isOK()) + return s; - // Found a $id field - if (currName == "$id") { - Status s = storageValidChildren(curr, deep); - if (!s.isOK()) - return s; + curr = curr.leftSibling(); + if (!curr.ok() || (curr.getFieldName() != "$ref")) { + return Status(ErrorCodes::InvalidDBRef, + "Found $id field without a $ref before it, which is invalid."); + } - curr = curr.leftSibling(); - if (!curr.ok() || (curr.getFieldName() != "$ref")) { - return Status(ErrorCodes::InvalidDBRef, - "Found $id field without a $ref before it, which is invalid."); - } + currName = curr.getFieldName(); + } - currName = curr.getFieldName(); - } + if (currName == "$ref") { + if (curr.getType() != String) { + return Status(ErrorCodes::InvalidDBRef, + str::stream() << "The DBRef $ref field must be a String, not a " + << typeName(curr.getType())); + } - if (currName == "$ref") { - if (curr.getType() != String) { - return Status(ErrorCodes::InvalidDBRef, - str::stream() << "The DBRef $ref field must be a String, not a " - << typeName(curr.getType())); - } + if (!curr.rightSibling().ok() || curr.rightSibling().getFieldName() != "$id") + return Status(ErrorCodes::InvalidDBRef, + str::stream() << "The DBRef $ref field must be " + "following by a $id field"); + } else { + // not an okay, $ prefixed field name. + return Status(ErrorCodes::DollarPrefixedFieldName, + str::stream() << "The dollar ($) prefixed field '" << elem.getFieldName() + << "' in '" << mb::getFullName(elem) + << "' is not valid for storage."); + } - if (!curr.rightSibling().ok() || curr.rightSibling().getFieldName() != "$id") - return Status(ErrorCodes::InvalidDBRef, - str::stream() << "The DBRef $ref field must be " - "following by a $id field"); - } - else { - // not an okay, $ prefixed field name. - return Status(ErrorCodes::DollarPrefixedFieldName, - str::stream() << "The dollar ($) prefixed field '" - << elem.getFieldName() << "' in '" - << mb::getFullName(elem) - << "' is not valid for storage."); + return Status::OK(); +} +/** + * Checks that all parents, of the element passed in, are valid for storage + * + * Note: The elem argument must be in a valid state when using this function + */ +Status storageValidParents(const mb::ConstElement& elem) { + const mb::ConstElement& root = elem.getDocument().root(); + if (elem != root) { + const mb::ConstElement& parent = elem.parent(); + if (parent.ok() && parent != root) { + Status s = storageValid(parent, false); + if (s.isOK()) { + s = storageValidParents(parent); } - return Status::OK(); + return s; + } + } + return Status::OK(); +} + +Status storageValid(const mb::ConstElement& elem, const bool deep) { + if (!elem.ok()) + return Status(ErrorCodes::BadValue, "Invalid elements cannot be stored."); + + // Field names of elements inside arrays are not meaningful in mutable bson, + // so we do not want to validate them. + // + // TODO: Revisit how mutable handles array field names. We going to need to make + // this better if we ever want to support ordered updates that can alter the same + // element repeatedly; see SERVER-12848. + const mb::ConstElement& parent = elem.parent(); + const bool childOfArray = parent.ok() ? (parent.getType() == mongo::Array) : false; + + if (!childOfArray) { + StringData fieldName = elem.getFieldName(); + // Cannot start with "$", unless dbref + if (fieldName[0] == '$') { + Status status = validateDollarPrefixElement(elem, deep); + if (!status.isOK()) + return status; + } else if (fieldName.find(".") != string::npos) { + // Field name cannot have a "." in it. + return Status(ErrorCodes::DottedFieldName, + str::stream() << "The dotted field '" << elem.getFieldName() << "' in '" + << mb::getFullName(elem) << "' is not valid for storage."); } + } - /** - * Checks that all parents, of the element passed in, are valid for storage - * - * Note: The elem argument must be in a valid state when using this function - */ - Status storageValidParents(const mb::ConstElement& elem) { - const mb::ConstElement& root = elem.getDocument().root(); - if (elem != root) { - const mb::ConstElement& parent = elem.parent(); - if (parent.ok() && parent != root) { - Status s = storageValid(parent, false); - if (s.isOK()) { - s = storageValidParents(parent); - } + if (deep) { + // Check children if there are any. + Status s = storageValidChildren(elem, deep); + if (!s.isOK()) + return s; + } - return s; - } - } - return Status::OK(); - } + return Status::OK(); +} - Status storageValid(const mb::ConstElement& elem, const bool deep) { - if (!elem.ok()) - return Status(ErrorCodes::BadValue, "Invalid elements cannot be stored."); - - // Field names of elements inside arrays are not meaningful in mutable bson, - // so we do not want to validate them. - // - // TODO: Revisit how mutable handles array field names. We going to need to make - // this better if we ever want to support ordered updates that can alter the same - // element repeatedly; see SERVER-12848. - const mb::ConstElement& parent = elem.parent(); - const bool childOfArray = parent.ok() ? (parent.getType() == mongo::Array) : false; - - if (!childOfArray) { - StringData fieldName = elem.getFieldName(); - // Cannot start with "$", unless dbref - if (fieldName[0] == '$') { - Status status = validateDollarPrefixElement(elem, deep); - if (!status.isOK()) - return status; - } - else if (fieldName.find(".") != string::npos) { - // Field name cannot have a "." in it. - return Status(ErrorCodes::DottedFieldName, - str::stream() << "The dotted field '" - << elem.getFieldName() << "' in '" - << mb::getFullName(elem) - << "' is not valid for storage."); - } - } +Status storageValidChildren(const mb::ConstElement& elem, const bool deep) { + if (!elem.hasChildren()) + return Status::OK(); - if (deep) { - // Check children if there are any. - Status s = storageValidChildren(elem, deep); - if (!s.isOK()) - return s; - } + mb::ConstElement curr = elem.leftChild(); + while (curr.ok()) { + Status s = storageValid(curr, deep); + if (!s.isOK()) + return s; + curr = curr.rightSibling(); + } + + return Status::OK(); +} - return Status::OK(); +/** + * This will verify that all updated fields are + * 1.) Valid for storage (checking parent to make sure things like DBRefs are valid) + * 2.) Compare updated immutable fields do not change values + * + * If updateFields is empty then it was replacement and/or we need to check all fields + */ +inline Status validate(const BSONObj& original, + const FieldRefSet& updatedFields, + const mb::Document& updated, + const std::vector<FieldRef*>* immutableAndSingleValueFields, + const ModifierInterface::Options& opts) { + LOG(3) << "update validate options -- " + << " updatedFields: " << updatedFields << " immutableAndSingleValueFields.size:" + << (immutableAndSingleValueFields ? immutableAndSingleValueFields->size() : 0) + << " validate:" << opts.enforceOkForStorage; + + // 1.) Loop through each updated field and validate for storage + // and detect immutable field updates + + // The set of possibly changed immutable fields -- we will need to check their vals + FieldRefSet changedImmutableFields; + + // Check to see if there were no fields specified or if we are not validating + // The case if a range query, or query that didn't result in saved fields + if (updatedFields.empty() || !opts.enforceOkForStorage) { + if (opts.enforceOkForStorage) { + // No specific fields were updated so the whole doc must be checked + Status s = storageValid(updated, true); + if (!s.isOK()) + return s; } - Status storageValidChildren(const mb::ConstElement& elem, const bool deep) { - if (!elem.hasChildren()) - return Status::OK(); + // Check all immutable fields + if (immutableAndSingleValueFields) + changedImmutableFields.fillFrom(*immutableAndSingleValueFields); + } else { + // TODO: Change impl so we don't need to create a new FieldRefSet + // -- move all conflict logic into static function on FieldRefSet? + FieldRefSet immutableFieldRef; + if (immutableAndSingleValueFields) + immutableFieldRef.fillFrom(*immutableAndSingleValueFields); + + FieldRefSet::const_iterator where = updatedFields.begin(); + const FieldRefSet::const_iterator end = updatedFields.end(); + for (; where != end; ++where) { + const FieldRef& current = **where; + + // Find the updated field in the updated document. + mutablebson::ConstElement newElem = updated.root(); + size_t currentPart = 0; + while (newElem.ok() && currentPart < current.numParts()) + newElem = newElem[current.getPart(currentPart++)]; + + // newElem might be missing if $unset/$renamed-away + if (newElem.ok()) { + // Check element, and its children + Status s = storageValid(newElem, true); + if (!s.isOK()) + return s; - mb::ConstElement curr = elem.leftChild(); - while (curr.ok()) { - Status s = storageValid(curr, deep); + // Check parents to make sure they are valid as well. + s = storageValidParents(newElem); if (!s.isOK()) return s; - curr = curr.rightSibling(); } - - return Status::OK(); + // Check if the updated field conflicts with immutable fields + immutableFieldRef.findConflicts(¤t, &changedImmutableFields); } + } - /** - * This will verify that all updated fields are - * 1.) Valid for storage (checking parent to make sure things like DBRefs are valid) - * 2.) Compare updated immutable fields do not change values - * - * If updateFields is empty then it was replacement and/or we need to check all fields - */ - inline Status validate(const BSONObj& original, - const FieldRefSet& updatedFields, - const mb::Document& updated, - const std::vector<FieldRef*>* immutableAndSingleValueFields, - const ModifierInterface::Options& opts) { - - LOG(3) << "update validate options -- " - << " updatedFields: " << updatedFields - << " immutableAndSingleValueFields.size:" - << (immutableAndSingleValueFields ? immutableAndSingleValueFields->size() : 0) - << " validate:" << opts.enforceOkForStorage; - - // 1.) Loop through each updated field and validate for storage - // and detect immutable field updates - - // The set of possibly changed immutable fields -- we will need to check their vals - FieldRefSet changedImmutableFields; - - // Check to see if there were no fields specified or if we are not validating - // The case if a range query, or query that didn't result in saved fields - if (updatedFields.empty() || !opts.enforceOkForStorage) { - if (opts.enforceOkForStorage) { - // No specific fields were updated so the whole doc must be checked - Status s = storageValid(updated, true); - if (!s.isOK()) - return s; - } - - // Check all immutable fields - if (immutableAndSingleValueFields) - changedImmutableFields.fillFrom(*immutableAndSingleValueFields); - } - else { - - // TODO: Change impl so we don't need to create a new FieldRefSet - // -- move all conflict logic into static function on FieldRefSet? - FieldRefSet immutableFieldRef; - if (immutableAndSingleValueFields) - immutableFieldRef.fillFrom(*immutableAndSingleValueFields); - - FieldRefSet::const_iterator where = updatedFields.begin(); - const FieldRefSet::const_iterator end = updatedFields.end(); - for( ; where != end; ++where) { - const FieldRef& current = **where; - - // Find the updated field in the updated document. - mutablebson::ConstElement newElem = updated.root(); - size_t currentPart = 0; - while (newElem.ok() && currentPart < current.numParts()) - newElem = newElem[current.getPart(currentPart++)]; - - // newElem might be missing if $unset/$renamed-away - if (newElem.ok()) { - - // Check element, and its children - Status s = storageValid(newElem, true); - if (!s.isOK()) - return s; - - // Check parents to make sure they are valid as well. - s = storageValidParents(newElem); - if (!s.isOK()) - return s; - - } - // Check if the updated field conflicts with immutable fields - immutableFieldRef.findConflicts(¤t, &changedImmutableFields); - } - } + const bool checkIdField = (updatedFields.empty() && !original.isEmpty()) || + updatedFields.findConflicts(&idFieldRef, NULL); - const bool checkIdField = (updatedFields.empty() && !original.isEmpty()) || - updatedFields.findConflicts(&idFieldRef, NULL); + // Add _id to fields to check since it too is immutable + if (checkIdField) + changedImmutableFields.keepShortest(&idFieldRef); + else if (changedImmutableFields.empty()) { + // Return early if nothing changed which is immutable + return Status::OK(); + } - // Add _id to fields to check since it too is immutable - if (checkIdField) - changedImmutableFields.keepShortest(&idFieldRef); - else if (changedImmutableFields.empty()) { - // Return early if nothing changed which is immutable - return Status::OK(); + LOG(4) << "Changed immutable fields: " << changedImmutableFields; + // 2.) Now compare values of the changed immutable fields (to make sure they haven't) + + const mutablebson::ConstElement newIdElem = updated.root()[idFieldName]; + + FieldRefSet::const_iterator where = changedImmutableFields.begin(); + const FieldRefSet::const_iterator end = changedImmutableFields.end(); + for (; where != end; ++where) { + const FieldRef& current = **where; + + // Find the updated field in the updated document. + mutablebson::ConstElement newElem = updated.root(); + size_t currentPart = 0; + while (newElem.ok() && currentPart < current.numParts()) + newElem = newElem[current.getPart(currentPart++)]; + + if (!newElem.ok()) { + if (original.isEmpty()) { + // If the _id is missing and not required, then skip this check + if (!(current.dottedField() == idFieldName)) + return Status(ErrorCodes::NoSuchKey, + mongoutils::str::stream() << "After applying the update, the new" + << " document was missing the '" + << current.dottedField() + << "' (required and immutable) field."); + + } else { + if (current.dottedField() != idFieldName) + return Status(ErrorCodes::ImmutableField, + mongoutils::str::stream() + << "After applying the update to the document with " + << newIdElem.toString() << ", the '" << current.dottedField() + << "' (required and immutable) field was " + "found to have been removed --" << original); } + } else { + // Find the potentially affected field in the original document. + const BSONElement oldElem = original.getFieldDotted(current.dottedField()); + const BSONElement oldIdElem = original.getField(idFieldName); - LOG(4) << "Changed immutable fields: " << changedImmutableFields; - // 2.) Now compare values of the changed immutable fields (to make sure they haven't) - - const mutablebson::ConstElement newIdElem = updated.root()[idFieldName]; - - FieldRefSet::const_iterator where = changedImmutableFields.begin(); - const FieldRefSet::const_iterator end = changedImmutableFields.end(); - for( ; where != end; ++where ) { - const FieldRef& current = **where; - - // Find the updated field in the updated document. - mutablebson::ConstElement newElem = updated.root(); - size_t currentPart = 0; - while (newElem.ok() && currentPart < current.numParts()) - newElem = newElem[current.getPart(currentPart++)]; - - if (!newElem.ok()) { - if (original.isEmpty()) { - // If the _id is missing and not required, then skip this check - if (!(current.dottedField() == idFieldName)) - return Status(ErrorCodes::NoSuchKey, - mongoutils::str::stream() - << "After applying the update, the new" - << " document was missing the '" - << current.dottedField() - << "' (required and immutable) field."); - - } - else { - if (current.dottedField() != idFieldName) - return Status(ErrorCodes::ImmutableField, - mongoutils::str::stream() - << "After applying the update to the document with " - << newIdElem.toString() - << ", the '" << current.dottedField() - << "' (required and immutable) field was " - "found to have been removed --" - << original); - } - } - else { - - // Find the potentially affected field in the original document. - const BSONElement oldElem = original.getFieldDotted(current.dottedField()); - const BSONElement oldIdElem = original.getField(idFieldName); - - // Ensure no arrays since neither _id nor shard keys can be in an array, or one. - mb::ConstElement currElem = newElem; - while (currElem.ok()) { - if (currElem.getType() == Array) { - return Status(ErrorCodes::NotSingleValueField, - mongoutils::str::stream() - << "After applying the update to the document {" - << (oldIdElem.ok() ? oldIdElem.toString() : - newIdElem.toString()) - << " , ...}, the (immutable) field '" - << current.dottedField() - << "' was found to be an array or array descendant."); - } - currElem = currElem.parent(); - } - - // If we have both (old and new), compare them. If we just have new we are good - if (oldElem.ok() && newElem.compareWithBSONElement(oldElem, false) != 0) { - return Status(ErrorCodes::ImmutableField, - mongoutils::str::stream() - << "After applying the update to the document {" - << oldElem.toString() - << " , ...}, the (immutable) field '" << current.dottedField() - << "' was found to have been altered to " - << newElem.toString()); - } + // Ensure no arrays since neither _id nor shard keys can be in an array, or one. + mb::ConstElement currElem = newElem; + while (currElem.ok()) { + if (currElem.getType() == Array) { + return Status( + ErrorCodes::NotSingleValueField, + mongoutils::str::stream() + << "After applying the update to the document {" + << (oldIdElem.ok() ? oldIdElem.toString() : newIdElem.toString()) + << " , ...}, the (immutable) field '" << current.dottedField() + << "' was found to be an array or array descendant."); } + currElem = currElem.parent(); } - return Status::OK(); - } - - Status ensureIdAndFirst(mb::Document& doc) { - mb::Element idElem = mb::findFirstChildNamed(doc.root(), idFieldName); - - // Move _id as first element if it exists - if (idElem.ok()) { - if (idElem.leftSibling().ok()) { - Status s = idElem.remove(); - if (!s.isOK()) - return s; - s = doc.root().pushFront(idElem); - if (!s.isOK()) - return s; - } - } - else { - // Create _id if the document does not currently have one. - idElem = doc.makeElementNewOID(idFieldName); - if (!idElem.ok()) - return Status(ErrorCodes::BadValue, - "Could not create new _id ObjectId element.", - 17268); - Status s = doc.root().pushFront(idElem); - if (!s.isOK()) - return s; + // If we have both (old and new), compare them. If we just have new we are good + if (oldElem.ok() && newElem.compareWithBSONElement(oldElem, false) != 0) { + return Status(ErrorCodes::ImmutableField, + mongoutils::str::stream() + << "After applying the update to the document {" + << oldElem.toString() << " , ...}, the (immutable) field '" + << current.dottedField() << "' was found to have been altered to " + << newElem.toString()); } - - return Status::OK(); } - - } // namespace - - // static - const char* UpdateStage::kStageType = "UPDATE"; - - UpdateStage::UpdateStage(OperationContext* txn, - const UpdateStageParams& params, - WorkingSet* ws, - Collection* collection, - PlanStage* child) - : _txn(txn), - _params(params), - _ws(ws), - _collection(collection), - _child(child), - _idRetrying(WorkingSet::INVALID_ID), - _idReturning(WorkingSet::INVALID_ID), - _commonStats(kStageType), - _updatedLocs(params.request->isMulti() ? new DiskLocSet() : NULL), - _doc(params.driver->getDocument()) { - // We are an update until we fall into the insert case. - params.driver->setContext(ModifierInterface::ExecInfo::UPDATE_CONTEXT); - - // Before we even start executing, we know whether or not this is a replacement - // style or $mod style update. - _specificStats.isDocReplacement = params.driver->isDocReplacement(); } - BSONObj UpdateStage::transformAndUpdate(const Snapshotted<BSONObj>& oldObj, RecordId& loc) { - const UpdateRequest* request = _params.request; - UpdateDriver* driver = _params.driver; - CanonicalQuery* cq = _params.canonicalQuery; - UpdateLifecycle* lifecycle = request->getLifecycle(); - - // If asked to return new doc, default to the oldObj, in case nothing changes. - BSONObj newObj = oldObj.value(); - - // Ask the driver to apply the mods. It may be that the driver can apply those "in - // place", that is, some values of the old document just get adjusted without any - // change to the binary layout on the bson layer. It may be that a whole new document - // is needed to accomodate the new bson layout of the resulting document. In any event, - // only enable in-place mutations if the underlying storage engine offers support for - // writing damage events. - _doc.reset(oldObj.value(), - (_collection->updateWithDamagesSupported() ? - mutablebson::Document::kInPlaceEnabled : - mutablebson::Document::kInPlaceDisabled)); - - BSONObj logObj; - - FieldRefSet updatedFields; - bool docWasModified = false; - - Status status = Status::OK(); - if (!driver->needMatchDetails()) { - // If we don't need match details, avoid doing the rematch - status = driver->update(StringData(), &_doc, &logObj, &updatedFields, &docWasModified); + return Status::OK(); +} + +Status ensureIdAndFirst(mb::Document& doc) { + mb::Element idElem = mb::findFirstChildNamed(doc.root(), idFieldName); + + // Move _id as first element if it exists + if (idElem.ok()) { + if (idElem.leftSibling().ok()) { + Status s = idElem.remove(); + if (!s.isOK()) + return s; + s = doc.root().pushFront(idElem); + if (!s.isOK()) + return s; } - else { - // If there was a matched field, obtain it. - MatchDetails matchDetails; - matchDetails.requestElemMatchKey(); + } else { + // Create _id if the document does not currently have one. + idElem = doc.makeElementNewOID(idFieldName); + if (!idElem.ok()) + return Status( + ErrorCodes::BadValue, "Could not create new _id ObjectId element.", 17268); + Status s = doc.root().pushFront(idElem); + if (!s.isOK()) + return s; + } - dassert(cq); - verify(cq->root()->matchesBSON(oldObj.value(), &matchDetails)); + return Status::OK(); +} + +} // namespace + +// static +const char* UpdateStage::kStageType = "UPDATE"; + +UpdateStage::UpdateStage(OperationContext* txn, + const UpdateStageParams& params, + WorkingSet* ws, + Collection* collection, + PlanStage* child) + : _txn(txn), + _params(params), + _ws(ws), + _collection(collection), + _child(child), + _idRetrying(WorkingSet::INVALID_ID), + _idReturning(WorkingSet::INVALID_ID), + _commonStats(kStageType), + _updatedLocs(params.request->isMulti() ? new DiskLocSet() : NULL), + _doc(params.driver->getDocument()) { + // We are an update until we fall into the insert case. + params.driver->setContext(ModifierInterface::ExecInfo::UPDATE_CONTEXT); + + // Before we even start executing, we know whether or not this is a replacement + // style or $mod style update. + _specificStats.isDocReplacement = params.driver->isDocReplacement(); +} + +BSONObj UpdateStage::transformAndUpdate(const Snapshotted<BSONObj>& oldObj, RecordId& loc) { + const UpdateRequest* request = _params.request; + UpdateDriver* driver = _params.driver; + CanonicalQuery* cq = _params.canonicalQuery; + UpdateLifecycle* lifecycle = request->getLifecycle(); + + // If asked to return new doc, default to the oldObj, in case nothing changes. + BSONObj newObj = oldObj.value(); + + // Ask the driver to apply the mods. It may be that the driver can apply those "in + // place", that is, some values of the old document just get adjusted without any + // change to the binary layout on the bson layer. It may be that a whole new document + // is needed to accomodate the new bson layout of the resulting document. In any event, + // only enable in-place mutations if the underlying storage engine offers support for + // writing damage events. + _doc.reset(oldObj.value(), + (_collection->updateWithDamagesSupported() + ? mutablebson::Document::kInPlaceEnabled + : mutablebson::Document::kInPlaceDisabled)); + + BSONObj logObj; + + FieldRefSet updatedFields; + bool docWasModified = false; + + Status status = Status::OK(); + if (!driver->needMatchDetails()) { + // If we don't need match details, avoid doing the rematch + status = driver->update(StringData(), &_doc, &logObj, &updatedFields, &docWasModified); + } else { + // If there was a matched field, obtain it. + MatchDetails matchDetails; + matchDetails.requestElemMatchKey(); + + dassert(cq); + verify(cq->root()->matchesBSON(oldObj.value(), &matchDetails)); + + string matchedField; + if (matchDetails.hasElemMatchKey()) + matchedField = matchDetails.elemMatchKey(); + + // TODO: Right now, each mod checks in 'prepare' that if it needs positional + // data, that a non-empty StringData() was provided. In principle, we could do + // that check here in an else clause to the above conditional and remove the + // checks from the mods. + + status = driver->update(matchedField, &_doc, &logObj, &updatedFields, &docWasModified); + } - string matchedField; - if (matchDetails.hasElemMatchKey()) - matchedField = matchDetails.elemMatchKey(); + if (!status.isOK()) { + uasserted(16837, status.reason()); + } - // TODO: Right now, each mod checks in 'prepare' that if it needs positional - // data, that a non-empty StringData() was provided. In principle, we could do - // that check here in an else clause to the above conditional and remove the - // checks from the mods. + // Ensure _id exists and is first + uassertStatusOK(ensureIdAndFirst(_doc)); + + // See if the changes were applied in place + const char* source = NULL; + const bool inPlace = _doc.getInPlaceUpdates(&_damages, &source); + + if (inPlace && _damages.empty()) { + // An interesting edge case. A modifier didn't notice that it was really a no-op + // during its 'prepare' phase. That represents a missed optimization, but we still + // shouldn't do any real work. Toggle 'docWasModified' to 'false'. + // + // Currently, an example of this is '{ $pushAll : { x : [] } }' when the 'x' array + // exists. + docWasModified = false; + } - status = driver->update(matchedField, &_doc, &logObj, &updatedFields, &docWasModified); - } + if (docWasModified) { + // Verify that no immutable fields were changed and data is valid for storage. - if (!status.isOK()) { - uasserted(16837, status.reason()); - } + if (!(!_txn->writesAreReplicated() || request->isFromMigration())) { + const std::vector<FieldRef*>* immutableFields = NULL; + if (lifecycle) + immutableFields = lifecycle->getImmutableFields(); - // Ensure _id exists and is first - uassertStatusOK(ensureIdAndFirst(_doc)); - - // See if the changes were applied in place - const char* source = NULL; - const bool inPlace = _doc.getInPlaceUpdates(&_damages, &source); - - if (inPlace && _damages.empty()) { - // An interesting edge case. A modifier didn't notice that it was really a no-op - // during its 'prepare' phase. That represents a missed optimization, but we still - // shouldn't do any real work. Toggle 'docWasModified' to 'false'. - // - // Currently, an example of this is '{ $pushAll : { x : [] } }' when the 'x' array - // exists. - docWasModified = false; + uassertStatusOK(validate( + oldObj.value(), updatedFields, _doc, immutableFields, driver->modOptions())); } - if (docWasModified) { - - // Verify that no immutable fields were changed and data is valid for storage. - - if (!(!_txn->writesAreReplicated() || request->isFromMigration())) { - const std::vector<FieldRef*>* immutableFields = NULL; - if (lifecycle) - immutableFields = lifecycle->getImmutableFields(); - - uassertStatusOK(validate(oldObj.value(), - updatedFields, - _doc, - immutableFields, - driver->modOptions()) ); + // Prepare to write back the modified document + WriteUnitOfWork wunit(_txn); + + RecordId newLoc; + + if (inPlace) { + // Don't actually do the write if this is an explain. + if (!request->isExplain()) { + invariant(_collection); + newObj = oldObj.value(); + const RecordData oldRec(oldObj.value().objdata(), oldObj.value().objsize()); + BSONObj idQuery = driver->makeOplogEntryQuery(newObj, request->isMulti()); + oplogUpdateEntryArgs args; + args.update = logObj; + args.criteria = idQuery; + args.fromMigrate = request->isFromMigration(); + _collection->updateDocumentWithDamages( + _txn, + loc, + Snapshotted<RecordData>(oldObj.snapshotId(), oldRec), + source, + _damages, + args); } - // Prepare to write back the modified document - WriteUnitOfWork wunit(_txn); - - RecordId newLoc; - - if (inPlace) { - - // Don't actually do the write if this is an explain. - if (!request->isExplain()) { - invariant(_collection); - newObj = oldObj.value(); - const RecordData oldRec(oldObj.value().objdata(), oldObj.value().objsize()); - BSONObj idQuery = driver->makeOplogEntryQuery(newObj, request->isMulti()); - oplogUpdateEntryArgs args; - args.update = logObj; - args.criteria = idQuery; - args.fromMigrate = request->isFromMigration(); - _collection->updateDocumentWithDamages( - _txn, - loc, - Snapshotted<RecordData>(oldObj.snapshotId(), oldRec), - source, - _damages, - args); - } - - _specificStats.fastmod = true; - newLoc = loc; - } - else { - // The updates were not in place. Apply them through the file manager. - - newObj = _doc.getObject(); - uassert(17419, - str::stream() << "Resulting document after update is larger than " - << BSONObjMaxUserSize, - newObj.objsize() <= BSONObjMaxUserSize); - - // Don't actually do the write if this is an explain. - if (!request->isExplain()) { - invariant(_collection); - BSONObj idQuery = driver->makeOplogEntryQuery(newObj, request->isMulti()); - oplogUpdateEntryArgs args; - args.update = logObj; - args.criteria = idQuery; - args.fromMigrate = request->isFromMigration(); - StatusWith<RecordId> res = _collection->updateDocument( - _txn, - loc, - oldObj, - newObj, - true, - driver->modsAffectIndices(), - _params.opDebug, - args); - uassertStatusOK(res.getStatus()); - newLoc = res.getValue(); - } - } - - invariant(oldObj.snapshotId() == _txn->recoveryUnit()->getSnapshotId()); - wunit.commit(); - - // If the document moved, we might see it again in a collection scan (maybe it's - // a document after our current document). - // - // If the document is indexed and the mod changes an indexed value, we might see - // it again. For an example, see the comment above near declaration of - // updatedLocs. - // - // This must be done after the wunit commits so we are sure we won't be rolling back. - if (_updatedLocs && (newLoc != loc || driver->modsAffectIndices())) { - _updatedLocs->insert(newLoc); + _specificStats.fastmod = true; + newLoc = loc; + } else { + // The updates were not in place. Apply them through the file manager. + + newObj = _doc.getObject(); + uassert(17419, + str::stream() << "Resulting document after update is larger than " + << BSONObjMaxUserSize, + newObj.objsize() <= BSONObjMaxUserSize); + + // Don't actually do the write if this is an explain. + if (!request->isExplain()) { + invariant(_collection); + BSONObj idQuery = driver->makeOplogEntryQuery(newObj, request->isMulti()); + oplogUpdateEntryArgs args; + args.update = logObj; + args.criteria = idQuery; + args.fromMigrate = request->isFromMigration(); + StatusWith<RecordId> res = _collection->updateDocument(_txn, + loc, + oldObj, + newObj, + true, + driver->modsAffectIndices(), + _params.opDebug, + args); + uassertStatusOK(res.getStatus()); + newLoc = res.getValue(); } } - // Only record doc modifications if they wrote (exclude no-ops). Explains get - // recorded as if they wrote. - if (docWasModified || request->isExplain()) { - _specificStats.nModified++; + invariant(oldObj.snapshotId() == _txn->recoveryUnit()->getSnapshotId()); + wunit.commit(); + + // If the document moved, we might see it again in a collection scan (maybe it's + // a document after our current document). + // + // If the document is indexed and the mod changes an indexed value, we might see + // it again. For an example, see the comment above near declaration of + // updatedLocs. + // + // This must be done after the wunit commits so we are sure we won't be rolling back. + if (_updatedLocs && (newLoc != loc || driver->modsAffectIndices())) { + _updatedLocs->insert(newLoc); } - - return newObj; } - // static - Status UpdateStage::applyUpdateOpsForInsert(const CanonicalQuery* cq, - const BSONObj& query, - UpdateDriver* driver, - UpdateLifecycle* lifecycle, - mutablebson::Document* doc, - bool isInternalRequest, - UpdateStats* stats, - BSONObj* out) { - // Since this is an insert (no docs found and upsert:true), we will be logging it - // as an insert in the oplog. We don't need the driver's help to build the - // oplog record, then. We also set the context of the update driver to the INSERT_CONTEXT. - // Some mods may only work in that context (e.g. $setOnInsert). - driver->setLogOp(false); - driver->setContext(ModifierInterface::ExecInfo::INSERT_CONTEXT); - - const vector<FieldRef*>* immutablePaths = NULL; - if (!isInternalRequest && lifecycle) - immutablePaths = lifecycle->getImmutableFields(); - - // The original document we compare changes to - immutable paths must not change - BSONObj original; - - if (cq) { - Status status = driver->populateDocumentWithQueryFields(cq, immutablePaths, *doc); - if (!status.isOK()) { - return status; - } + // Only record doc modifications if they wrote (exclude no-ops). Explains get + // recorded as if they wrote. + if (docWasModified || request->isExplain()) { + _specificStats.nModified++; + } - if (driver->isDocReplacement()) - stats->fastmodinsert = true; - original = doc->getObject(); - } - else { - fassert(17354, CanonicalQuery::isSimpleIdQuery(query)); - BSONElement idElt = query[idFieldName]; - original = idElt.wrap(); - fassert(17352, doc->root().appendElement(idElt)); + return newObj; +} + +// static +Status UpdateStage::applyUpdateOpsForInsert(const CanonicalQuery* cq, + const BSONObj& query, + UpdateDriver* driver, + UpdateLifecycle* lifecycle, + mutablebson::Document* doc, + bool isInternalRequest, + UpdateStats* stats, + BSONObj* out) { + // Since this is an insert (no docs found and upsert:true), we will be logging it + // as an insert in the oplog. We don't need the driver's help to build the + // oplog record, then. We also set the context of the update driver to the INSERT_CONTEXT. + // Some mods may only work in that context (e.g. $setOnInsert). + driver->setLogOp(false); + driver->setContext(ModifierInterface::ExecInfo::INSERT_CONTEXT); + + const vector<FieldRef*>* immutablePaths = NULL; + if (!isInternalRequest && lifecycle) + immutablePaths = lifecycle->getImmutableFields(); + + // The original document we compare changes to - immutable paths must not change + BSONObj original; + + if (cq) { + Status status = driver->populateDocumentWithQueryFields(cq, immutablePaths, *doc); + if (!status.isOK()) { + return status; } - // Apply the update modifications here. - Status updateStatus = driver->update(StringData(), doc); - if (!updateStatus.isOK()) { - return Status(updateStatus.code(), updateStatus.reason(), 16836); - } + if (driver->isDocReplacement()) + stats->fastmodinsert = true; + original = doc->getObject(); + } else { + fassert(17354, CanonicalQuery::isSimpleIdQuery(query)); + BSONElement idElt = query[idFieldName]; + original = idElt.wrap(); + fassert(17352, doc->root().appendElement(idElt)); + } - // Ensure _id exists and is first - Status idAndFirstStatus = ensureIdAndFirst(*doc); - if (!idAndFirstStatus.isOK()) { - return idAndFirstStatus; - } + // Apply the update modifications here. + Status updateStatus = driver->update(StringData(), doc); + if (!updateStatus.isOK()) { + return Status(updateStatus.code(), updateStatus.reason(), 16836); + } - // Validate that the object replacement or modifiers resulted in a document - // that contains all the immutable keys and can be stored if it isn't coming - // from a migration or via replication. - if (!isInternalRequest) { - FieldRefSet noFields; - // This will only validate the modified fields if not a replacement. - Status validateStatus = validate(original, - noFields, - *doc, - immutablePaths, - driver->modOptions()); - if (!validateStatus.isOK()) { - return validateStatus; - } - } + // Ensure _id exists and is first + Status idAndFirstStatus = ensureIdAndFirst(*doc); + if (!idAndFirstStatus.isOK()) { + return idAndFirstStatus; + } - BSONObj newObj = doc->getObject(); - if (newObj.objsize() > BSONObjMaxUserSize) { - return Status(ErrorCodes::InvalidBSON, - str::stream() << "Document to upsert is larger than " - << BSONObjMaxUserSize, - 17420); + // Validate that the object replacement or modifiers resulted in a document + // that contains all the immutable keys and can be stored if it isn't coming + // from a migration or via replication. + if (!isInternalRequest) { + FieldRefSet noFields; + // This will only validate the modified fields if not a replacement. + Status validateStatus = + validate(original, noFields, *doc, immutablePaths, driver->modOptions()); + if (!validateStatus.isOK()) { + return validateStatus; } + } - *out = newObj; - return Status::OK(); + BSONObj newObj = doc->getObject(); + if (newObj.objsize() > BSONObjMaxUserSize) { + return Status(ErrorCodes::InvalidBSON, + str::stream() << "Document to upsert is larger than " << BSONObjMaxUserSize, + 17420); } - void UpdateStage::doInsert() { - _specificStats.inserted = true; + *out = newObj; + return Status::OK(); +} - const UpdateRequest* request = _params.request; - bool isInternalRequest = !_txn->writesAreReplicated() || request->isFromMigration(); +void UpdateStage::doInsert() { + _specificStats.inserted = true; - // Reset the document we will be writing to. - _doc.reset(); + const UpdateRequest* request = _params.request; + bool isInternalRequest = !_txn->writesAreReplicated() || request->isFromMigration(); - BSONObj newObj; - uassertStatusOK(applyUpdateOpsForInsert(_params.canonicalQuery, - request->getQuery(), - _params.driver, - request->getLifecycle(), - &_doc, - isInternalRequest, - &_specificStats, - &newObj)); + // Reset the document we will be writing to. + _doc.reset(); - _specificStats.objInserted = newObj; + BSONObj newObj; + uassertStatusOK(applyUpdateOpsForInsert(_params.canonicalQuery, + request->getQuery(), + _params.driver, + request->getLifecycle(), + &_doc, + isInternalRequest, + &_specificStats, + &newObj)); - // If this is an explain, bail out now without doing the insert. - if (request->isExplain()) { - return; - } - MONGO_WRITE_CONFLICT_RETRY_LOOP_BEGIN { - WriteUnitOfWork wunit(_txn); - invariant(_collection); - const bool enforceQuota = !request->isGod(); - uassertStatusOK(_collection->insertDocument(_txn, - newObj, - enforceQuota, - request->isFromMigration())); - - // Technically, we should save/restore state here, but since we are going to return - // immediately after, it would just be wasted work. - wunit.commit(); - } MONGO_WRITE_CONFLICT_RETRY_LOOP_END(_txn, "upsert", _collection->ns().ns()); - } + _specificStats.objInserted = newObj; - bool UpdateStage::doneUpdating() { - // We're done updating if either the child has no more results to give us, or we've - // already gotten a result back and we're not a multi-update. - return _idRetrying == WorkingSet::INVALID_ID && _idReturning == WorkingSet::INVALID_ID - && (_child->isEOF() || (_specificStats.nMatched > 0 && !_params.request->isMulti())); + // If this is an explain, bail out now without doing the insert. + if (request->isExplain()) { + return; } + MONGO_WRITE_CONFLICT_RETRY_LOOP_BEGIN { + WriteUnitOfWork wunit(_txn); + invariant(_collection); + const bool enforceQuota = !request->isGod(); + uassertStatusOK( + _collection->insertDocument(_txn, newObj, enforceQuota, request->isFromMigration())); - bool UpdateStage::needInsert() { - // We need to insert if - // 1) we haven't inserted already, - // 2) the child stage returned zero matches, and - // 3) the user asked for an upsert. - return !_specificStats.inserted - && _specificStats.nMatched == 0 - && _params.request->isUpsert(); + // Technically, we should save/restore state here, but since we are going to return + // immediately after, it would just be wasted work. + wunit.commit(); } - - bool UpdateStage::isEOF() { - return doneUpdating() && !needInsert(); + MONGO_WRITE_CONFLICT_RETRY_LOOP_END(_txn, "upsert", _collection->ns().ns()); +} + +bool UpdateStage::doneUpdating() { + // We're done updating if either the child has no more results to give us, or we've + // already gotten a result back and we're not a multi-update. + return _idRetrying == WorkingSet::INVALID_ID && _idReturning == WorkingSet::INVALID_ID && + (_child->isEOF() || (_specificStats.nMatched > 0 && !_params.request->isMulti())); +} + +bool UpdateStage::needInsert() { + // We need to insert if + // 1) we haven't inserted already, + // 2) the child stage returned zero matches, and + // 3) the user asked for an upsert. + return !_specificStats.inserted && _specificStats.nMatched == 0 && _params.request->isUpsert(); +} + +bool UpdateStage::isEOF() { + return doneUpdating() && !needInsert(); +} + +PlanStage::StageState UpdateStage::work(WorkingSetID* out) { + ++_commonStats.works; + + // Adds the amount of time taken by work() to executionTimeMillis. + ScopedTimer timer(&_commonStats.executionTimeMillis); + + if (isEOF()) { + return PlanStage::IS_EOF; } - PlanStage::StageState UpdateStage::work(WorkingSetID* out) { - ++_commonStats.works; + if (doneUpdating()) { + // Even if we're done updating, we may have some inserting left to do. + if (needInsert()) { + // TODO we may want to handle WriteConflictException here. Currently we bounce it + // out to a higher level since if this WCEs it is likely that we raced with another + // upsert that may have matched our query, and therefore this may need to perform an + // update rather than an insert. Bouncing to the higher level allows restarting the + // query in this case. + doInsert(); - // Adds the amount of time taken by work() to executionTimeMillis. - ScopedTimer timer(&_commonStats.executionTimeMillis); + invariant(isEOF()); + if (_params.request->shouldReturnNewDocs()) { + // Want to return the document we just inserted, create it as a WorkingSetMember + // so that we can return it. + BSONObj newObj = _specificStats.objInserted; + *out = _ws->allocate(); + WorkingSetMember* member = _ws->get(*out); + member->obj = + Snapshotted<BSONObj>(_txn->recoveryUnit()->getSnapshotId(), newObj.getOwned()); + member->state = WorkingSetMember::OWNED_OBJ; + ++_commonStats.advanced; + return PlanStage::ADVANCED; + } + } - if (isEOF()) { return PlanStage::IS_EOF; } + // At this point either we're done updating and there was no insert to do, + // or we're done updating and we're done inserting. Either way, we're EOF. + invariant(isEOF()); + return PlanStage::IS_EOF; + } - if (doneUpdating()) { - // Even if we're done updating, we may have some inserting left to do. - if (needInsert()) { - // TODO we may want to handle WriteConflictException here. Currently we bounce it - // out to a higher level since if this WCEs it is likely that we raced with another - // upsert that may have matched our query, and therefore this may need to perform an - // update rather than an insert. Bouncing to the higher level allows restarting the - // query in this case. - doInsert(); + // If we're here, then we still have to ask for results from the child and apply + // updates to them. We should only get here if the collection exists. + invariant(_collection); - invariant(isEOF()); - if (_params.request->shouldReturnNewDocs()) { - // Want to return the document we just inserted, create it as a WorkingSetMember - // so that we can return it. - BSONObj newObj = _specificStats.objInserted; - *out = _ws->allocate(); - WorkingSetMember* member = _ws->get(*out); - member->obj = Snapshotted<BSONObj>(_txn->recoveryUnit()->getSnapshotId(), - newObj.getOwned()); - member->state = WorkingSetMember::OWNED_OBJ; - ++_commonStats.advanced; - return PlanStage::ADVANCED; - } - } + // It is possible that after an update was applied, a WriteConflictException + // occurred and prevented us from returning ADVANCED with the requested version + // of the document. + if (_idReturning != WorkingSet::INVALID_ID) { + // We should only get here if we were trying to return something before. + invariant(_params.request->shouldReturnAnyDocs()); - // At this point either we're done updating and there was no insert to do, - // or we're done updating and we're done inserting. Either way, we're EOF. - invariant(isEOF()); - return PlanStage::IS_EOF; - } + WorkingSetMember* member = _ws->get(_idReturning); + invariant(member->state == WorkingSetMember::OWNED_OBJ); - // If we're here, then we still have to ask for results from the child and apply - // updates to them. We should only get here if the collection exists. - invariant(_collection); + *out = _idReturning; + _idReturning = WorkingSet::INVALID_ID; + ++_commonStats.advanced; + return PlanStage::ADVANCED; + } - // It is possible that after an update was applied, a WriteConflictException - // occurred and prevented us from returning ADVANCED with the requested version - // of the document. - if (_idReturning != WorkingSet::INVALID_ID) { - // We should only get here if we were trying to return something before. - invariant(_params.request->shouldReturnAnyDocs()); + // Either retry the last WSM we worked on or get a new one from our child. + WorkingSetID id; + StageState status; + if (_idRetrying == WorkingSet::INVALID_ID) { + status = _child->work(&id); + } else { + status = ADVANCED; + id = _idRetrying; + _idRetrying = WorkingSet::INVALID_ID; + } - WorkingSetMember* member = _ws->get(_idReturning); - invariant(member->state == WorkingSetMember::OWNED_OBJ); + if (PlanStage::ADVANCED == status) { + // Need to get these things from the result returned by the child. + RecordId loc; - *out = _idReturning; - _idReturning = WorkingSet::INVALID_ID; - ++_commonStats.advanced; - return PlanStage::ADVANCED; - } + WorkingSetMember* member = _ws->get(id); - // Either retry the last WSM we worked on or get a new one from our child. - WorkingSetID id; - StageState status; - if (_idRetrying == WorkingSet::INVALID_ID) { - status = _child->work(&id); - } - else { - status = ADVANCED; - id = _idRetrying; - _idRetrying = WorkingSet::INVALID_ID; + // We want to free this member when we return, unless we need to retry it. + ScopeGuard memberFreer = MakeGuard(&WorkingSet::free, _ws, id); + + if (!member->hasLoc()) { + // We expect to be here because of an invalidation causing a force-fetch, and + // doc-locking storage engines do not issue invalidations. + ++_specificStats.nInvalidateSkips; + ++_commonStats.needTime; + return PlanStage::NEED_TIME; } + loc = member->loc; - if (PlanStage::ADVANCED == status) { - // Need to get these things from the result returned by the child. - RecordId loc; + // Updates can't have projections. This means that covering analysis will always add + // a fetch. We should always get fetched data, and never just key data. + invariant(member->hasObj()); - WorkingSetMember* member = _ws->get(id); + // We fill this with the new locs of moved doc so we don't double-update. + if (_updatedLocs && _updatedLocs->count(loc) > 0) { + // Found a loc that refers to a document we had already updated. Note that + // we can never remove from _updatedLocs because updates by other clients + // could cause us to encounter a document again later. + ++_commonStats.needTime; + return PlanStage::NEED_TIME; + } - // We want to free this member when we return, unless we need to retry it. - ScopeGuard memberFreer = MakeGuard(&WorkingSet::free, _ws, id); + try { + std::unique_ptr<RecordCursor> cursor; + if (_txn->recoveryUnit()->getSnapshotId() != member->obj.snapshotId()) { + cursor = _collection->getCursor(_txn); + // our snapshot has changed, refetch + if (!WorkingSetCommon::fetch(_txn, member, cursor)) { + // document was deleted, we're done here + ++_commonStats.needTime; + return PlanStage::NEED_TIME; + } - if (!member->hasLoc()) { - // We expect to be here because of an invalidation causing a force-fetch, and - // doc-locking storage engines do not issue invalidations. - ++_specificStats.nInvalidateSkips; - ++_commonStats.needTime; - return PlanStage::NEED_TIME; - } - loc = member->loc; - - // Updates can't have projections. This means that covering analysis will always add - // a fetch. We should always get fetched data, and never just key data. - invariant(member->hasObj()); - - // We fill this with the new locs of moved doc so we don't double-update. - if (_updatedLocs && _updatedLocs->count(loc) > 0) { - // Found a loc that refers to a document we had already updated. Note that - // we can never remove from _updatedLocs because updates by other clients - // could cause us to encounter a document again later. - ++_commonStats.needTime; - return PlanStage::NEED_TIME; + // we have to re-match the doc as it might not match anymore + CanonicalQuery* cq = _params.canonicalQuery; + if (cq && !cq->root()->matchesBSON(member->obj.value(), NULL)) { + // doesn't match predicates anymore! + ++_commonStats.needTime; + return PlanStage::NEED_TIME; + } } + // Save state before making changes try { - std::unique_ptr<RecordCursor> cursor; - if (_txn->recoveryUnit()->getSnapshotId() != member->obj.snapshotId()) { - cursor = _collection->getCursor(_txn); - // our snapshot has changed, refetch - if (!WorkingSetCommon::fetch(_txn, member, cursor)) { - // document was deleted, we're done here - ++_commonStats.needTime; - return PlanStage::NEED_TIME; - } - - // we have to re-match the doc as it might not match anymore - CanonicalQuery* cq = _params.canonicalQuery; - if (cq && !cq->root()->matchesBSON(member->obj.value(), NULL)) { - // doesn't match predicates anymore! - ++_commonStats.needTime; - return PlanStage::NEED_TIME; - } + _child->saveState(); + if (supportsDocLocking()) { + // Doc-locking engines require this after saveState() since they don't use + // invalidations. + WorkingSetCommon::prepareForSnapshotChange(_ws); } + } catch (const WriteConflictException& wce) { + std::terminate(); + } - // Save state before making changes - try { - _child->saveState(); - if (supportsDocLocking()) { - // Doc-locking engines require this after saveState() since they don't use - // invalidations. - WorkingSetCommon::prepareForSnapshotChange(_ws); - } - } - catch ( const WriteConflictException& wce ) { - std::terminate(); - } + // If we care about the pre-updated version of the doc, save it out here. + BSONObj oldObj; + if (_params.request->shouldReturnOldDocs()) { + oldObj = member->obj.value().getOwned(); + } - // If we care about the pre-updated version of the doc, save it out here. - BSONObj oldObj; - if (_params.request->shouldReturnOldDocs()) { - oldObj = member->obj.value().getOwned(); - } + // Do the update, get us the new version of the doc. + BSONObj newObj = transformAndUpdate(member->obj, loc); - // Do the update, get us the new version of the doc. - BSONObj newObj = transformAndUpdate(member->obj, loc); - - // Set member's obj to be the doc we want to return. - if (_params.request->shouldReturnAnyDocs()) { - if (_params.request->shouldReturnNewDocs()) { - member->obj = Snapshotted<BSONObj>(_txn->recoveryUnit()->getSnapshotId(), - newObj.getOwned()); - } - else { - invariant(_params.request->shouldReturnOldDocs()); - member->obj.setValue(oldObj); - } - member->loc = RecordId(); - member->state = WorkingSetMember::OWNED_OBJ; + // Set member's obj to be the doc we want to return. + if (_params.request->shouldReturnAnyDocs()) { + if (_params.request->shouldReturnNewDocs()) { + member->obj = Snapshotted<BSONObj>(_txn->recoveryUnit()->getSnapshotId(), + newObj.getOwned()); + } else { + invariant(_params.request->shouldReturnOldDocs()); + member->obj.setValue(oldObj); } + member->loc = RecordId(); + member->state = WorkingSetMember::OWNED_OBJ; } - catch ( const WriteConflictException& wce ) { - _idRetrying = id; - memberFreer.Dismiss(); // Keep this member around so we can retry updating it. - *out = WorkingSet::INVALID_ID; - _commonStats.needYield++; - return NEED_YIELD; - } - - // This should be after transformAndUpdate to make sure we actually updated this doc. - ++_specificStats.nMatched; + } catch (const WriteConflictException& wce) { + _idRetrying = id; + memberFreer.Dismiss(); // Keep this member around so we can retry updating it. + *out = WorkingSet::INVALID_ID; + _commonStats.needYield++; + return NEED_YIELD; + } - // Restore state after modification + // This should be after transformAndUpdate to make sure we actually updated this doc. + ++_specificStats.nMatched; - // As restoreState may restore (recreate) cursors, make sure to restore the - // state outside of the WritUnitOfWork. - try { - _child->restoreState(_txn); - } - catch ( const WriteConflictException& wce ) { - // Note we don't need to retry updating anything in this case since the update - // already was committed. However, we still need to return the updated document - // (if it was requested). - if (_params.request->shouldReturnAnyDocs()) { - // member->obj should refer to the document we want to return. - invariant(member->state == WorkingSetMember::OWNED_OBJ); - - _idReturning = id; - // Keep this member around so that we can return it on the next work() call. - memberFreer.Dismiss(); - } - *out = WorkingSet::INVALID_ID; - _commonStats.needYield++; - return NEED_YIELD; - } + // Restore state after modification + // As restoreState may restore (recreate) cursors, make sure to restore the + // state outside of the WritUnitOfWork. + try { + _child->restoreState(_txn); + } catch (const WriteConflictException& wce) { + // Note we don't need to retry updating anything in this case since the update + // already was committed. However, we still need to return the updated document + // (if it was requested). if (_params.request->shouldReturnAnyDocs()) { // member->obj should refer to the document we want to return. invariant(member->state == WorkingSetMember::OWNED_OBJ); - memberFreer.Dismiss(); // Keep this member around so we can return it. - *out = id; - ++_commonStats.advanced; - return PlanStage::ADVANCED; - } - - ++_commonStats.needTime; - return PlanStage::NEED_TIME; - } - else if (PlanStage::IS_EOF == status) { - // The child is out of results, but we might not be done yet because we still might - // have to do an insert. - ++_commonStats.needTime; - return PlanStage::NEED_TIME; - } - else if (PlanStage::FAILURE == status) { - *out = id; - // If a stage fails, it may create a status WSM to indicate why it failed, in which case - // 'id' is valid. If ID is invalid, we create our own error message. - if (WorkingSet::INVALID_ID == id) { - const std::string errmsg = "update stage failed to read in results from child"; - *out = WorkingSetCommon::allocateStatusMember(_ws, Status(ErrorCodes::InternalError, - errmsg)); - return PlanStage::FAILURE; + _idReturning = id; + // Keep this member around so that we can return it on the next work() call. + memberFreer.Dismiss(); } - return status; - } - else if (PlanStage::NEED_TIME == status) { - ++_commonStats.needTime; + *out = WorkingSet::INVALID_ID; + _commonStats.needYield++; + return NEED_YIELD; } - else if (PlanStage::NEED_YIELD == status) { - ++_commonStats.needYield; + + if (_params.request->shouldReturnAnyDocs()) { + // member->obj should refer to the document we want to return. + invariant(member->state == WorkingSetMember::OWNED_OBJ); + + memberFreer.Dismiss(); // Keep this member around so we can return it. *out = id; + ++_commonStats.advanced; + return PlanStage::ADVANCED; } + ++_commonStats.needTime; + return PlanStage::NEED_TIME; + } else if (PlanStage::IS_EOF == status) { + // The child is out of results, but we might not be done yet because we still might + // have to do an insert. + ++_commonStats.needTime; + return PlanStage::NEED_TIME; + } else if (PlanStage::FAILURE == status) { + *out = id; + // If a stage fails, it may create a status WSM to indicate why it failed, in which case + // 'id' is valid. If ID is invalid, we create our own error message. + if (WorkingSet::INVALID_ID == id) { + const std::string errmsg = "update stage failed to read in results from child"; + *out = WorkingSetCommon::allocateStatusMember( + _ws, Status(ErrorCodes::InternalError, errmsg)); + return PlanStage::FAILURE; + } return status; + } else if (PlanStage::NEED_TIME == status) { + ++_commonStats.needTime; + } else if (PlanStage::NEED_YIELD == status) { + ++_commonStats.needYield; + *out = id; } - void UpdateStage::saveState() { - _txn = NULL; - ++_commonStats.yields; - _child->saveState(); - } - - Status UpdateStage::restoreUpdateState(OperationContext* opCtx) { - const UpdateRequest& request = *_params.request; - const NamespaceString& nsString(request.getNamespaceString()); - - // We may have stepped down during the yield. - bool userInitiatedWritesAndNotPrimary = opCtx->writesAreReplicated() && - !repl::getGlobalReplicationCoordinator()->canAcceptWritesFor(nsString); + return status; +} - if (userInitiatedWritesAndNotPrimary) { - return Status(ErrorCodes::NotMaster, - str::stream() << "Demoted from primary while performing update on " - << nsString.ns()); - } +void UpdateStage::saveState() { + _txn = NULL; + ++_commonStats.yields; + _child->saveState(); +} - if (request.getLifecycle()) { - UpdateLifecycle* lifecycle = request.getLifecycle(); - lifecycle->setCollection(_collection); +Status UpdateStage::restoreUpdateState(OperationContext* opCtx) { + const UpdateRequest& request = *_params.request; + const NamespaceString& nsString(request.getNamespaceString()); - if (!lifecycle->canContinue()) { - return Status(ErrorCodes::IllegalOperation, - "Update aborted due to invalid state transitions after yield.", - 17270); - } + // We may have stepped down during the yield. + bool userInitiatedWritesAndNotPrimary = opCtx->writesAreReplicated() && + !repl::getGlobalReplicationCoordinator()->canAcceptWritesFor(nsString); - _params.driver->refreshIndexKeys(lifecycle->getIndexKeys(opCtx)); - } - - return Status::OK(); - } - - void UpdateStage::restoreState(OperationContext* opCtx) { - invariant(_txn == NULL); - _txn = opCtx; - ++_commonStats.unyields; - // Restore our child. - _child->restoreState(opCtx); - // Restore self. - uassertStatusOK(restoreUpdateState(opCtx)); + if (userInitiatedWritesAndNotPrimary) { + return Status(ErrorCodes::NotMaster, + str::stream() << "Demoted from primary while performing update on " + << nsString.ns()); } - void UpdateStage::invalidate(OperationContext* txn, const RecordId& dl, InvalidationType type) { - ++_commonStats.invalidates; - _child->invalidate(txn, dl, type); - } + if (request.getLifecycle()) { + UpdateLifecycle* lifecycle = request.getLifecycle(); + lifecycle->setCollection(_collection); - vector<PlanStage*> UpdateStage::getChildren() const { - vector<PlanStage*> children; - children.push_back(_child.get()); - return children; - } - - PlanStageStats* UpdateStage::getStats() { - _commonStats.isEOF = isEOF(); - unique_ptr<PlanStageStats> ret(new PlanStageStats(_commonStats, STAGE_UPDATE)); - ret->specific.reset(new UpdateStats(_specificStats)); - ret->children.push_back(_child->getStats()); - return ret.release(); - } + if (!lifecycle->canContinue()) { + return Status(ErrorCodes::IllegalOperation, + "Update aborted due to invalid state transitions after yield.", + 17270); + } - const CommonStats* UpdateStage::getCommonStats() const { - return &_commonStats; + _params.driver->refreshIndexKeys(lifecycle->getIndexKeys(opCtx)); } - const SpecificStats* UpdateStage::getSpecificStats() const { - return &_specificStats; + return Status::OK(); +} + +void UpdateStage::restoreState(OperationContext* opCtx) { + invariant(_txn == NULL); + _txn = opCtx; + ++_commonStats.unyields; + // Restore our child. + _child->restoreState(opCtx); + // Restore self. + uassertStatusOK(restoreUpdateState(opCtx)); +} + +void UpdateStage::invalidate(OperationContext* txn, const RecordId& dl, InvalidationType type) { + ++_commonStats.invalidates; + _child->invalidate(txn, dl, type); +} + +vector<PlanStage*> UpdateStage::getChildren() const { + vector<PlanStage*> children; + children.push_back(_child.get()); + return children; +} + +PlanStageStats* UpdateStage::getStats() { + _commonStats.isEOF = isEOF(); + unique_ptr<PlanStageStats> ret(new PlanStageStats(_commonStats, STAGE_UPDATE)); + ret->specific.reset(new UpdateStats(_specificStats)); + ret->children.push_back(_child->getStats()); + return ret.release(); +} + +const CommonStats* UpdateStage::getCommonStats() const { + return &_commonStats; +} + +const SpecificStats* UpdateStage::getSpecificStats() const { + return &_specificStats; +} + +// static +UpdateResult UpdateStage::makeUpdateResult(PlanExecutor* exec, OpDebug* opDebug) { + // Get stats from the root stage. + invariant(exec->getRootStage()->isEOF()); + invariant(exec->getRootStage()->stageType() == STAGE_UPDATE); + UpdateStage* updateStage = static_cast<UpdateStage*>(exec->getRootStage()); + const UpdateStats* updateStats = + static_cast<const UpdateStats*>(updateStage->getSpecificStats()); + + // Use stats from the root stage to fill out opDebug. + opDebug->nMatched = updateStats->nMatched; + opDebug->nModified = updateStats->nModified; + opDebug->upsert = updateStats->inserted; + opDebug->fastmodinsert = updateStats->fastmodinsert; + opDebug->fastmod = updateStats->fastmod; + + // Historically, 'opDebug' considers 'nMatched' and 'nModified' to be 1 (rather than 0) + // if there is an upsert that inserts a document. The UpdateStage does not participate + // in this madness in order to have saner stats reporting for explain. This means that + // we have to set these values "manually" in the case of an insert. + if (updateStats->inserted) { + opDebug->nMatched = 1; + opDebug->nModified = 1; } - // static - UpdateResult UpdateStage::makeUpdateResult(PlanExecutor* exec, OpDebug* opDebug) { - // Get stats from the root stage. - invariant(exec->getRootStage()->isEOF()); - invariant(exec->getRootStage()->stageType() == STAGE_UPDATE); - UpdateStage* updateStage = static_cast<UpdateStage*>(exec->getRootStage()); - const UpdateStats* updateStats = - static_cast<const UpdateStats*>(updateStage->getSpecificStats()); - - // Use stats from the root stage to fill out opDebug. - opDebug->nMatched = updateStats->nMatched; - opDebug->nModified = updateStats->nModified; - opDebug->upsert = updateStats->inserted; - opDebug->fastmodinsert = updateStats->fastmodinsert; - opDebug->fastmod = updateStats->fastmod; - - // Historically, 'opDebug' considers 'nMatched' and 'nModified' to be 1 (rather than 0) - // if there is an upsert that inserts a document. The UpdateStage does not participate - // in this madness in order to have saner stats reporting for explain. This means that - // we have to set these values "manually" in the case of an insert. - if (updateStats->inserted) { - opDebug->nMatched = 1; - opDebug->nModified = 1; - } - - // Get summary information about the plan. - PlanSummaryStats stats; - Explain::getSummaryStats(exec, &stats); - opDebug->nscanned = stats.totalKeysExamined; - opDebug->nscannedObjects = stats.totalDocsExamined; + // Get summary information about the plan. + PlanSummaryStats stats; + Explain::getSummaryStats(exec, &stats); + opDebug->nscanned = stats.totalKeysExamined; + opDebug->nscannedObjects = stats.totalDocsExamined; - return UpdateResult(updateStats->nMatched > 0 /* Did we update at least one obj? */, - !updateStats->isDocReplacement /* $mod or obj replacement */, - opDebug->nModified /* number of modified docs, no no-ops */, - opDebug->nMatched /* # of docs matched/updated, even no-ops */, - updateStats->objInserted); - }; + return UpdateResult(updateStats->nMatched > 0 /* Did we update at least one obj? */, + !updateStats->isDocReplacement /* $mod or obj replacement */, + opDebug->nModified /* number of modified docs, no no-ops */, + opDebug->nMatched /* # of docs matched/updated, even no-ops */, + updateStats->objInserted); +}; -} // namespace mongo +} // namespace mongo |