UpdateNode::ApplyResult PathCreatingNode::apply(ApplyParams applyParams) const { if (context == Context::kInsertOnly && !applyParams.insert) { return ApplyResult::noopResult(); } // The value in this Element gets used to create a logging entry (if we have a LogBuilder). mutablebson::Element valueToLog = applyParams.element.getDocument().end(); if (applyParams.pathToCreate->empty()) { // If 'pathTaken' is a strict prefix of any immutable path, store the original document to // ensure the immutable path does not change. BSONObj original; for (auto immutablePath = applyParams.immutablePaths.begin(); immutablePath != applyParams.immutablePaths.end(); ++immutablePath) { if (applyParams.pathTaken->isPrefixOf(**immutablePath)) { original = applyParams.element.getDocument().getObject(); break; } } // We found an existing element at the update path. if (!updateExistingElement(&applyParams.element)) { return ApplyResult::noopResult(); // Successful no-op update. } if (applyParams.validateForStorage) { const bool doRecursiveCheck = true; const uint32_t recursionLevel = applyParams.pathTaken->numParts(); storage_validation::storageValid(applyParams.element, doRecursiveCheck, recursionLevel); } checkImmutablePathsNotModified( applyParams.element, applyParams.pathTaken.get(), applyParams.immutablePaths, original); valueToLog = applyParams.element; } else { // We did not find an element at the update path. Create one. auto newElementFieldName = applyParams.pathToCreate->getPart(applyParams.pathToCreate->numParts() - 1); auto newElement = applyParams.element.getDocument().makeElementNull(newElementFieldName); setValueForNewElement(&newElement); invariant(newElement.ok()); auto statusWithFirstCreatedElem = pathsupport::createPathAt( *(applyParams.pathToCreate), 0, applyParams.element, newElement); if (!statusWithFirstCreatedElem.isOK()) { // $sets on non-viable paths are ignored when the update came from replication. We do // not error because idempotency requires that any other update modifiers must still be // applied. For example, consider applying the following updates twice to an initially // empty document: // {$set: {c: 0}} // {$set: {'a.b': 0, c: 1}} // {$set: {a: 0}} // Setting 'a.b' will fail the second time, but we must still set 'c'. // (There are modifiers besides $set that use this code path, but they are not used for // replication, so we are not concerned with their behavior when "fromReplication" is // true.) if (statusWithFirstCreatedElem.getStatus().code() == ErrorCodes::PathNotViable && applyParams.fromReplication) { return ApplyResult::noopResult(); } uassertStatusOK(statusWithFirstCreatedElem); MONGO_UNREACHABLE; // The previous uassertStatusOK should always throw. } if (applyParams.validateForStorage) { const bool doRecursiveCheck = true; const uint32_t recursionLevel = applyParams.pathTaken->numParts() + 1; storage_validation::storageValid( statusWithFirstCreatedElem.getValue(), doRecursiveCheck, recursionLevel); } for (auto immutablePath = applyParams.immutablePaths.begin(); immutablePath != applyParams.immutablePaths.end(); ++immutablePath) { // If 'immutablePath' is a (strict or non-strict) prefix of 'pathTaken', then we are // modifying 'immutablePath'. For example, adding '_id.x' will illegally modify '_id'. uassert(ErrorCodes::ImmutableField, str::stream() << "Updating the path '" << applyParams.pathTaken->dottedField() << "' to " << applyParams.element.toString() << " would modify the immutable field '" << (*immutablePath)->dottedField() << "'", applyParams.pathTaken->commonPrefixSize(**immutablePath) != (*immutablePath)->numParts()); } valueToLog = newElement; } // Create full field path of set element. StringBuilder builder; builder << applyParams.pathTaken->dottedField(); if (!applyParams.pathTaken->empty() && !applyParams.pathToCreate->empty()) { builder << "."; } builder << applyParams.pathToCreate->dottedField(); auto fullPath = builder.str(); ApplyResult applyResult; // Determine if indexes are affected. if (!applyParams.indexData || !applyParams.indexData->mightBeIndexed(fullPath)) { applyResult.indexesAffected = false; } // Log the operation. if (applyParams.logBuilder) { auto logElement = applyParams.logBuilder->getDocument().makeElementWithNewFieldName(fullPath, valueToLog); invariant(logElement.ok()); uassertStatusOK(applyParams.logBuilder->addToSets(logElement)); } return applyResult; }
UpdateNode::ApplyResult ModifierNode::applyToExistingElement(ApplyParams applyParams) const { invariant(!applyParams.pathTaken->empty()); invariant(applyParams.pathToCreate->empty()); invariant(applyParams.element.ok()); mutablebson::ConstElement leftSibling = applyParams.element.leftSibling(); mutablebson::ConstElement rightSibling = applyParams.element.rightSibling(); bool compareWithOriginal = false; if (canSetObjectValue()) { for (auto immutablePath = applyParams.immutablePaths.begin(); immutablePath != applyParams.immutablePaths.end(); ++immutablePath) { if (applyParams.pathTaken->isPrefixOf(**immutablePath)) { compareWithOriginal = true; break; } } } // We have two different ways of checking for changes to immutable paths, depending on the style // of update. See the comments above checkImmutablePathsNotModifiedFromOriginal() and // checkImmutablePathsNotModified(). ModifyResult updateResult; if (compareWithOriginal) { BSONObj original = applyParams.element.getDocument().getObject(); updateResult = updateExistingElement(&applyParams.element, applyParams.pathTaken); if (updateResult == ModifyResult::kNoOp) { return ApplyResult::noopResult(); } checkImmutablePathsNotModifiedFromOriginal( applyParams.element, applyParams.pathTaken.get(), applyParams.immutablePaths, original); } else { updateResult = updateExistingElement(&applyParams.element, applyParams.pathTaken); if (updateResult == ModifyResult::kNoOp) { return ApplyResult::noopResult(); } checkImmutablePathsNotModified( applyParams.element, applyParams.pathTaken.get(), applyParams.immutablePaths); } invariant(updateResult != ModifyResult::kCreated); ApplyResult applyResult; if (!applyParams.indexData || !applyParams.indexData->mightBeIndexed(applyParams.pathTaken->dottedField())) { applyResult.indexesAffected = false; } if (applyParams.validateForStorage) { const uint32_t recursionLevel = applyParams.pathTaken->numParts(); validateUpdate( applyParams.element, leftSibling, rightSibling, recursionLevel, updateResult); } if (applyParams.logBuilder) { logUpdate(applyParams.logBuilder, applyParams.pathTaken->dottedField(), applyParams.element, updateResult); } return applyResult; }