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::applyToNonexistentElement(ApplyParams applyParams) const { if (allowCreation()) { 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()) { // $set operaions 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 uint32_t recursionLevel = applyParams.pathTaken->numParts() + 1; mutablebson::ConstElement elementForValidation = statusWithFirstCreatedElem.getValue(); validateUpdate(elementForValidation, elementForValidation.leftSibling(), elementForValidation.rightSibling(), recursionLevel, ModifyResult::kCreated); } 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'. // (Note that this behavior is subtly different from checkImmutablePathsNotModified(), // because we just created this element.) 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()); } invariant(!applyParams.pathToCreate->empty()); std::string fullPath; if (applyParams.pathTaken->empty()) { fullPath = applyParams.pathToCreate->dottedField().toString(); } else { fullPath = str::stream() << applyParams.pathTaken->dottedField() << "." << applyParams.pathToCreate->dottedField(); } ApplyResult applyResult; // Determine if indexes are affected. if (!applyParams.indexData || !applyParams.indexData->mightBeIndexed(fullPath)) { applyResult.indexesAffected = false; } if (applyParams.logBuilder) { logUpdate(applyParams.logBuilder, fullPath, newElement, ModifyResult::kCreated); } return applyResult; } else { // This path is for modifiers like $pop or $pull that generally have no effect when applied // to a path that does not exist. if (!allowNonViablePath()) { // One exception: some of these modifiers still fail when the nonexistent path is // "non-viable," meaning it couldn't be created even if we intended to. UpdateLeafNode::checkViability( applyParams.element, *(applyParams.pathToCreate), *(applyParams.pathTaken)); } return ApplyResult::noopResult(); } }