/** * 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(); }
Status UpdateDriver::update(StringData matchedField, BSONObj original, mutablebson::Document* doc, bool validateForStorage, const FieldRefSet& immutablePaths, BSONObj* logOpRec, bool* docWasModified) { // TODO: assert that update() is called at most once in a !_multi case. _affectIndices = (isDocReplacement() && (_indexedFields != NULL)); _logDoc.reset(); LogBuilder logBuilder(_logDoc.root()); if (_root) { // We parsed using the new UpdateNode implementation. UpdateNode::ApplyParams applyParams(doc->root(), immutablePaths); applyParams.matchedField = matchedField; applyParams.insert = _insert; applyParams.fromReplication = _modOptions.fromReplication; applyParams.validateForStorage = validateForStorage; applyParams.indexData = _indexedFields; if (_logOp && logOpRec) { applyParams.logBuilder = &logBuilder; } auto applyResult = _root->apply(applyParams); if (applyResult.indexesAffected) { _affectIndices = true; doc->disableInPlaceUpdates(); } if (docWasModified) { *docWasModified = !applyResult.noop; } } else { // We parsed using the old ModifierInterface implementation. // Ask each of the mods to type check whether they can operate over the current document // and, if so, to change that document accordingly. FieldRefSet updatedPaths; 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; } if (execInfo.context == ModifierInterface::ExecInfo::INSERT_CONTEXT && !_insert) { continue; } // 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 (!updatedPaths.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(); } } if (!execInfo.noOp) { status = (*it)->apply(); if (docWasModified) *docWasModified = true; 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; } } } // Check for BSON depth and DBRef constraint violations. if (validateForStorage) { for (auto path = updatedPaths.begin(); path != updatedPaths.end(); ++path) { // Find the updated field in the updated document. auto newElem = doc->root(); for (size_t i = 0; i < (*path)->numParts(); ++i) { newElem = newElem[(*path)->getPart(i)]; if (!newElem.ok()) { break; } } // newElem might be missing if $unset/$renamed-away. if (newElem.ok()) { // Check parents. const std::uint32_t recursionLevel = 0; auto parentsDepth = storage_validation::storageValidParents(newElem, recursionLevel); // Check element and its children. const bool doRecursiveCheck = true; storage_validation::storageValid(newElem, doRecursiveCheck, parentsDepth); } } } for (auto path = immutablePaths.begin(); path != immutablePaths.end(); ++path) { if (!updatedPaths.findConflicts(*path, nullptr)) { continue; } // Find the updated field in the updated document. auto newElem = doc->root(); for (size_t i = 0; i < (*path)->numParts(); ++i) { newElem = newElem[(*path)->getPart(i)]; if (!newElem.ok()) { break; } uassert(ErrorCodes::NotSingleValueField, str::stream() << "After applying the update to the document, the (immutable) field '" << (*path)->dottedField() << "' was found to be an array or array descendant.", newElem.getType() != BSONType::Array); } auto oldElem = dotted_path_support::extractElementAtPath(original, (*path)->dottedField()); uassert(ErrorCodes::ImmutableField, str::stream() << "After applying the update, the '" << (*path)->dottedField() << "' (required and immutable) field was " "found to have been removed --" << original, newElem.ok() || !oldElem.ok()); if (newElem.ok() && oldElem.ok()) { uassert(ErrorCodes::ImmutableField, str::stream() << "After applying the update, the (immutable) field '" << (*path)->dottedField() << "' was found to have been altered to " << newElem.toString(), newElem.compareWithBSONElement(oldElem, nullptr, false) == 0); } } } if (_logOp && logOpRec) *logOpRec = _logDoc.getObject(); 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; } // 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); // 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 = dps::extractElementAtPath(original, 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, nullptr, 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(); }