예제 #1
0
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;
}
예제 #2
0
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();
    }
}