Example #1
0
/**
 * Helper function to check if path conflicts are all prefixes.
 */
static Status checkPathIsPrefixOf(const FieldRef& path, const FieldRefSet& conflictPaths) {
    for (FieldRefSet::const_iterator it = conflictPaths.begin(); it != conflictPaths.end(); ++it) {
        const FieldRef* conflictingPath = *it;
        // Conflicts are always prefixes (or equal to) the path, or vice versa
        if (path.numParts() > conflictingPath->numParts()) {
            string errMsg = stream() << "field at '" << conflictingPath->dottedField()
                                     << "' must be exactly specified, field at sub-path '"
                                     << path.dottedField() << "'found";
            return Status(ErrorCodes::NotExactValueField, errMsg);
        }
    }

    return Status::OK();
}
Example #2
0
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();
}
Example #3
0
/**
 * 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(&current, &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();
}