Status AuthzManagerExternalStateMock::updateOne(OperationContext* opCtx, const NamespaceString& collectionName, const BSONObj& query, const BSONObj& updatePattern, bool upsert, const BSONObj& writeConcern) { namespace mmb = mutablebson; const CollatorInterface* collator = nullptr; boost::intrusive_ptr<ExpressionContext> expCtx(new ExpressionContext(opCtx, collator)); UpdateDriver driver(std::move(expCtx)); std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; driver.parse(updatePattern, arrayFilters); BSONObjCollection::iterator iter; Status status = _findOneIter(opCtx, collectionName, query, &iter); mmb::Document document; if (status.isOK()) { document.reset(*iter, mmb::Document::kInPlaceDisabled); const bool validateForStorage = false; const FieldRefSet emptyImmutablePaths; const bool isInsert = false; BSONObj logObj; status = driver.update( StringData(), &document, validateForStorage, emptyImmutablePaths, isInsert, &logObj); if (!status.isOK()) return status; BSONObj newObj = document.getObject().copy(); *iter = newObj; BSONObj idQuery = newObj["_id"_sd].Obj(); if (_authzManager) { _authzManager->logOp(opCtx, "u", collectionName, logObj, &idQuery); } return Status::OK(); } else if (status == ErrorCodes::NoMatchingDocument && upsert) { if (query.hasField("_id")) { document.root().appendElement(query["_id"]).transitional_ignore(); } const FieldRef idFieldRef("_id"); FieldRefSet immutablePaths; invariant(immutablePaths.insert(&idFieldRef)); status = driver.populateDocumentWithQueryFields(opCtx, query, immutablePaths, document); if (!status.isOK()) { return status; } const bool validateForStorage = false; const FieldRefSet emptyImmutablePaths; const bool isInsert = false; status = driver.update( StringData(), &document, validateForStorage, emptyImmutablePaths, isInsert); if (!status.isOK()) { return status; } return insert(opCtx, collectionName, document.getObject(), writeConcern); } else { return status; } }
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); } if (!status.isOK()) return status; status = pathsupport::addEqualitiesToDoc(equalities, &doc); return status; }
/** * Updates roleGraph for an update-type oplog operation on admin.system.roles. * * Treats all updates as upserts. */ Status handleOplogUpdate(OperationContext* opCtx, RoleGraph* roleGraph, const BSONObj& updatePattern, const BSONObj& queryPattern) { RoleName roleToUpdate; Status status = getRoleNameFromIdField(queryPattern["_id"], &roleToUpdate); if (!status.isOK()) return status; boost::intrusive_ptr<ExpressionContext> expCtx(new ExpressionContext(opCtx, nullptr)); UpdateDriver driver(std::move(expCtx)); driver.setFromOplogApplication(true); // Oplog updates do not have array filters. std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; driver.parse(updatePattern, arrayFilters); mutablebson::Document roleDocument; status = RoleGraph::getBSONForRole(roleGraph, roleToUpdate, roleDocument.root()); if (status == ErrorCodes::RoleNotFound) { // The query pattern will only contain _id, no other immutable fields are present const FieldRef idFieldRef("_id"); FieldRefSet immutablePaths; invariant(immutablePaths.insert(&idFieldRef)); status = driver.populateDocumentWithQueryFields( opCtx, queryPattern, immutablePaths, roleDocument); } if (!status.isOK()) return status; const bool validateForStorage = false; const FieldRefSet emptyImmutablePaths; status = driver.update(StringData(), &roleDocument, validateForStorage, emptyImmutablePaths); if (!status.isOK()) return status; // Now use the updated document to totally replace the role in the graph! RoleInfo role; status = parseRoleFromDocument(roleDocument.getObject(), &role); if (!status.isOK()) return status; status = roleGraph->replaceRole(role.name, role.roles, role.privileges, role.restrictions); return status; }
Status AuthzManagerExternalStateMock::updateOne(OperationContext* opCtx, const NamespaceString& collectionName, const BSONObj& query, const BSONObj& updatePattern, bool upsert, const BSONObj& writeConcern) { namespace mmb = mutablebson; UpdateDriver::Options updateOptions; UpdateDriver driver(updateOptions); std::map<StringData, std::unique_ptr<ArrayFilter>> arrayFilters; Status status = driver.parse(updatePattern, arrayFilters); if (!status.isOK()) return status; BSONObjCollection::iterator iter; status = _findOneIter(collectionName, query, &iter); mmb::Document document; if (status.isOK()) { document.reset(*iter, mmb::Document::kInPlaceDisabled); const BSONObj emptyOriginal; const bool validateForStorage = false; const FieldRefSet emptyImmutablePaths; BSONObj logObj; status = driver.update(StringData(), emptyOriginal, &document, validateForStorage, emptyImmutablePaths, &logObj); if (!status.isOK()) return status; BSONObj newObj = document.getObject().copy(); *iter = newObj; BSONObj idQuery = driver.makeOplogEntryQuery(newObj, false); if (_authzManager) { _authzManager->logOp(opCtx, "u", collectionName, logObj, &idQuery); } return Status::OK(); } else if (status == ErrorCodes::NoMatchingDocument && upsert) { if (query.hasField("_id")) { document.root().appendElement(query["_id"]).transitional_ignore(); } const FieldRef idFieldRef("_id"); FieldRefSet immutablePaths; invariant(immutablePaths.insert(&idFieldRef)); status = driver.populateDocumentWithQueryFields(opCtx, query, immutablePaths, document); if (!status.isOK()) { return status; } // The original document can be empty because it is only needed for validation of immutable // paths. const BSONObj emptyOriginal; const bool validateForStorage = false; const FieldRefSet emptyImmutablePaths; status = driver.update( StringData(), emptyOriginal, &document, validateForStorage, emptyImmutablePaths); if (!status.isOK()) { return status; } return insert(opCtx, collectionName, document.getObject(), writeConcern); } else { return status; } }
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(); }
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(); }