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()); if (fullPathsToExtract) { FieldRefSet conflictPaths; fullPathsToExtract->findConflicts(&path, &conflictPaths); // Ignore if this path is unrelated to the full paths if (conflictPaths.empty()) return Status::OK(); // Make sure we're a prefix of all the conflict paths Status status = checkPathIsPrefixOf(path, conflictPaths); if (!status.isOK()) return status; } Status status = checkEqualityConflicts(*equalities, path); if (!status.isOK()) return status; equalities->insert(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 UpdateDriver::update(const StringData& matchedField, mutablebson::Document* doc, BSONObj* logOpRec) { // TODO: assert that update() is called at most once in a !_multi case. FieldRefSet targetFields; _affectIndices = false; _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; } // 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; } // 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 (!targetFields.empty() || _mods.size() > 1) { const FieldRef* other; if (!targetFields.insert(execInfo.fieldRef[i], &other)) { return Status(ErrorCodes::ConflictingUpdateOperators, mongoutils::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.mightBeIndexed(execInfo.fieldRef[i]->dottedField())) { _affectIndices = true; doc->disableInPlaceUpdates(); } } if (!execInfo.noOp) { status = (*it)->apply(); if (!status.isOK()) { return status; } } // If we require a replication oplog entry for this update, go ahead and generate one. if (_logOp && logOpRec) { status = (*it)->log(&logBuilder); if (!status.isOK()) { return status; } } } 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(); }