Пример #1
0
    Status ModifierUnset::log(mutablebson::Element logRoot) const {

        // We'd like to create an entry such as {$unset: {<fieldname>: 1}} under 'logRoot'.
        // We start by creating the {$unset: ...} Element.
        mutablebson::Document& doc = logRoot.getDocument();
        mutablebson::Element unsetElement = doc.makeElementObject("$unset");
        if (!unsetElement.ok()) {
            return Status(ErrorCodes::InternalError, "cannot create log entry for $unset mod");
        }

        // Then we create the {<fieldname>: <value>} Element. Note that <fieldname> must be a
        // dotted field, and not only the last part of that field. The rationale here is that
        // somoene picking up this log entry -- e.g., a secondary -- must be capable of doing
        // the same path find/creation that was done in the previous calls here.
        mutablebson::Element logElement = doc.makeElementInt(_fieldRef.dottedField(), 1);
        if (!logElement.ok()) {
            return Status(ErrorCodes::InternalError, "cannot create log details for $unset mod");
        }

        // Now, we attach the {<fieldname>: `} Element under the {$unset: ...} one.
        Status status = unsetElement.pushBack(logElement);
        if (!status.isOK()) {
            return status;
        }

        // And attach the result under the 'logRoot' Element provided.
        return logRoot.pushBack(unsetElement);
    }
    Status ModifierInc::log(mutablebson::Element logRoot) const {

        // We'd like to create an entry such as {$set: {<fieldname>: <value>}} under 'logRoot'.
        // We start by creating the {$set: ...} Element.
        mutablebson::Document& doc = logRoot.getDocument();
        mutablebson::Element setElement = doc.makeElementObject("$set");
        if (!setElement.ok()) {
            return Status(ErrorCodes::InternalError, "cannot append log entry for $set mod");
        }

        // Then we create the {<fieldname>: <value>} Element.
        mutablebson::Element logElement = doc.makeElementSafeNum(
            _fieldRef.dottedField(),
            _preparedState->newValue);

        if (!logElement.ok()) {
            return Status(ErrorCodes::InternalError, "cannot append details for $set mod");
        }

        // Now, we attach the {<fieldname>: <value>} Element under the {$set: ...} one.
        Status status = setElement.pushBack(logElement);
        if (!status.isOK()) {
            return status;
        }

        // And attach the result under the 'logRoot' Element provided.
        return logRoot.pushBack(setElement);
    }
Пример #3
0
Status ModifierSet::log(mutablebson::Element logRoot) const {

    // We'd like to create an entry such as {$set: {<fieldname>: <value>}} under 'logRoot'.
    // We start by creating the {$set: ...} Element.
    mutablebson::Document& doc = logRoot.getDocument();
    mutablebson::Element setElement = doc.makeElementObject("$set");
    if (!setElement.ok()) {
        return Status(ErrorCodes::InternalError, "cannot create log entry for $set mod");
    }

    // Then we create the {<fieldname>: <value>} Element. Note that we log the mod with a
    // dotted field, if it was applied over a dotted field. The rationale is that the
    // secondary may be in a different state than the primary and thus make different
    // decisions about creating the intermediate path in _fieldRef or not.
    mutablebson::Element logElement = doc.makeElementWithNewFieldName(_fieldRef.dottedField(),
                                      _val);
    if (!logElement.ok()) {
        return Status(ErrorCodes::InternalError, "cannot create details for $set mod");
    }

    // Now, we attach the {<fieldname>: <value>} Element under the {$set: ...} one.
    Status status = setElement.pushBack(logElement);
    if (!status.isOK()) {
        return status;
    }

    // And attach the result under the 'logRoot' Element provided.
    return logRoot.pushBack(setElement);
}
Пример #4
0
    Status ModifierPush::prepare(mutablebson::Element root,
                                 const StringData& matchedField,
                                 ExecInfo* execInfo) {

        _preparedState.reset(new PreparedState(&root.getDocument()));

        // If we have a $-positional field, it is time to bind it to an actual field part.
        if (_posDollar) {
            if (matchedField.empty()) {
                return Status(ErrorCodes::BadValue,
                              str::stream() << "The positional operator did not find the match "
                                               "needed from the query. Unexpanded update: "
                                            << _fieldRef.dottedField());
            }
            _fieldRef.setPart(_posDollar, matchedField);
        }

        // Locate the field name in 'root'. Note that we may not have all the parts in the path
        // in the doc -- which is fine. Our goal now is merely to reason about whether this mod
        // apply is a noOp or whether is can be in place. The remainin path, if missing, will
        // be created during the apply.
        Status status = pathsupport::findLongestPrefix(_fieldRef,
                                                       root,
                                                       &_preparedState->idxFound,
                                                       &_preparedState->elemFound);

        // FindLongestPrefix may say the path does not exist at all, which is fine here, or
        // that the path was not viable or otherwise wrong, in which case, the mod cannot
        // proceed.
        if (status.code() == ErrorCodes::NonExistentPath) {
            _preparedState->elemFound = root.getDocument().end();

        }
        else if (status.isOK()) {

            const bool destExists = (_preparedState->idxFound == (_fieldRef.numParts()-1));
            // If the path exists, we require the target field to be already an
            // array.
            if (destExists && _preparedState->elemFound.getType() != Array) {
                mb::Element idElem = mb::findFirstChildNamed(root, "_id");
                return Status(ErrorCodes::BadValue,
                              str::stream() << "The field '" << _fieldRef.dottedField() << "'"
                                            << " must be an array but is of type "
                                            << typeName(_preparedState->elemFound.getType())
                                            << " in document {" << idElem.toString() << "}");
            }
        }
        else {
            return status;
        }

        // We register interest in the field name. The driver needs this info to sort out if
        // there is any conflict among mods.
        execInfo->fieldRef[0] = &_fieldRef;

        return Status::OK();
    }
Пример #5
0
Status LogBuilder::addToSetsWithNewFieldName(StringData name, const mutablebson::Element val) {
    mutablebson::Element elemToSet = _logRoot.getDocument().makeElementWithNewFieldName(name, val);
    if (!elemToSet.ok())
        return Status(ErrorCodes::InternalError,
                      str::stream() << "Could not create new '" << name
                                    << "' element from existing element '"
                                    << val.getFieldName()
                                    << "' of type "
                                    << typeName(val.getType()));

    return addToSets(elemToSet);
}
    Status ModifierAddToSet::log(mb::Element logRoot) const {

        // TODO: This is copied more or less identically from $push. As a result, it copies the
        // behavior in $push that relies on 'apply' having been called unless this is a no-op.

        // TODO We can log just a positional set in several cases. For now, let's just log the
        // full resulting array.

        // We'd like to create an entry such as {$set: {<fieldname>: [<resulting aray>]}} under
        // 'logRoot'.  We start by creating the {$set: ...} Element.
        mb::Document& doc = logRoot.getDocument();
        mb::Element setElement = doc.makeElementObject("$set");
        if (!setElement.ok()) {
            return Status(ErrorCodes::InternalError, "cannot create log entry for $addToSet mod");
        }

        // Then we create the {<fieldname>:[]} Element, that is, an empty array.
        mb::Element logElement = doc.makeElementArray(_fieldRef.dottedField());
        if (!logElement.ok()) {
            return Status(ErrorCodes::InternalError, "cannot create details for $addToSet mod");
        }

        // Fill up the empty array.
        mb::Element curr = _preparedState->elemFound.leftChild();
        while (curr.ok()) {

            dassert(curr.hasValue());

            // We need to copy each array entry from the resulting document to the log
            // document.
            mb::Element currCopy = doc.makeElementWithNewFieldName(
                StringData(),
                curr.getValue());
            if (!currCopy.ok()) {
                return Status(ErrorCodes::InternalError, "could create copy element");
            }
            Status status = logElement.pushBack(currCopy);
            if (!status.isOK()) {
                return Status(ErrorCodes::BadValue, "could not append entry for $addToSet log");
            }
            curr = curr.rightSibling();
        }

        // Now, we attach the {<fieldname>: [<filled array>]} Element under the {$set: ...}
        // one.
        Status status = setElement.pushBack(logElement);
        if (!status.isOK()) {
            return status;
        }

        // And attach the result under the 'logRoot' Element provided.
        return logRoot.pushBack(setElement);
    }
Пример #7
0
Status ModifierObjectReplace::prepare(mutablebson::Element root,
                                      StringData matchedField,
                                      ExecInfo* execInfo) {
    _preparedState.reset(new PreparedState(&root.getDocument()));

    // objectSize checked by binaryEqual (optimization)
    BSONObj objOld = root.getDocument().getObject();
    if (objOld.binaryEqual(_val)) {
        _preparedState->noOp = true;
        execInfo->noOp = true;
    }

    return Status::OK();
}
    Status ModifierObjectReplace::prepare(mutablebson::Element root,
                                          const StringData& matchedField,
                                          ExecInfo* execInfo) {

        _preparedState.reset(new PreparedState(&root.getDocument()));
        return Status::OK();
    }
Пример #9
0
 PreparedState(mutablebson::Element root)
     : doc(root.getDocument())
     , fromElemFound(doc.end())
     , toIdxFound(0)
     , toElemFound(doc.end())
     , applyCalled(false){
 }
Пример #10
0
    Status ModifierCurrentDate::prepare(mutablebson::Element root,
                                StringData matchedField,
                                ExecInfo* execInfo) {

        _preparedState.reset(new PreparedState(root.getDocument()));

        // If we have a $-positional field, it is time to bind it to an actual field part.
        if (_pathReplacementPosition) {
            if (matchedField.empty()) {
                return Status(ErrorCodes::BadValue,
                              str::stream() << "The positional operator did not find the match "
                                               "needed from the query. Unexpanded update: "
                                            << _updatePath.dottedField());
            }
            _updatePath.setPart(_pathReplacementPosition, matchedField);
        }

        // Locate the field name in 'root'. Note that we may not have all the parts in the path
        // in the doc -- which is fine. Our goal now is merely to reason about whether this mod
        // apply is a noOp or whether is can be in place. The remaining path, if missing, will
        // be created during the apply.
        Status status = pathsupport::findLongestPrefix(_updatePath,
                                                root,
                                                &_preparedState->idxFound,
                                                &_preparedState->elemFound);

        // FindLongestPrefix may say the path does not exist at all, which is fine here, or
        // that the path was not viable or otherwise wrong, in which case, the mod cannot
        // proceed.
        if (status.code() == ErrorCodes::NonExistentPath) {
            _preparedState->elemFound = root.getDocument().end();
        }
        else if (!status.isOK()) {
            return status;
        }

        // We register interest in the field name. The driver needs this info to sort out if
        // there is any conflict among mods.
        execInfo->fieldRef[0] = &_updatePath;

        return Status::OK();
    }
Пример #11
0
Status ModifierUnset::prepare(mutablebson::Element root,
                              const StringData& matchedField,
                              ExecInfo* execInfo) {

    _preparedState.reset(new PreparedState(&root.getDocument()));

    // If we have a $-positional field, it is time to bind it to an actual field part.
    if (_posDollar) {
        if (matchedField.empty()) {
            return Status(ErrorCodes::BadValue,
                          str::stream() << "The positional operator did not find the match "
                          "needed from the query. Unexpanded update: "
                          << _fieldRef.dottedField());
        }
        _preparedState->boundDollar = matchedField.toString();
        _fieldRef.setPart(_posDollar, _preparedState->boundDollar);
    }


    // Locate the field name in 'root'. Note that if we don't have the full path in the
    // doc, there isn't anything to unset, really.
    Status status = pathsupport::findLongestPrefix(_fieldRef,
                    root,
                    &_preparedState->idxFound,
                    &_preparedState->elemFound);
    if (!status.isOK() ||
            _preparedState->idxFound != (_fieldRef.numParts() -1)) {
        execInfo->noOp = _preparedState->noOp = true;
        execInfo->fieldRef[0] = &_fieldRef;

        return Status::OK();
    }

    // If there is indeed something to unset, we register so, along with the interest in
    // the field name. The driver needs this info to sort out if there is any conflict
    // among mods.
    execInfo->fieldRef[0] = &_fieldRef;

    // The only way for an $unset to be inplace is for its target field to be the last one
    // of the object. That is, it is always the right child on its paths. The current
    // rationale is that there should be no holes in a BSONObj and, to be in place, no
    // field boundaries must change.
    //
    // TODO:
    // mutablebson::Element curr = _preparedState->elemFound;
    // while (curr.ok()) {
    //     if (curr.rightSibling().ok()) {
    //     }
    //     curr = curr.parent();
    // }

    return Status::OK();
}
Пример #12
0
 Status AuthorizationManager::getBSONForPrivileges(const PrivilegeVector& privileges,
                                                   mutablebson::Element resultArray) {
     for (PrivilegeVector::const_iterator it = privileges.begin();
             it != privileges.end(); ++it) {
         std::string errmsg;
         ParsedPrivilege privilege;
         if (!ParsedPrivilege::privilegeToParsedPrivilege(*it, &privilege, &errmsg)) {
             return Status(ErrorCodes::BadValue, errmsg);
         }
         resultArray.appendObject("privileges", privilege.toBSON());
     }
     return Status::OK();
 }
    Status ModifierObjectReplace::log(mutablebson::Element logRoot) const {

        // We'd like to create an entry such as {<object replacement>} under 'logRoot'.
        mutablebson::Document& doc = logRoot.getDocument();
        BSONObjIterator it(_val);
        while (it.more()) {
            BSONElement elem = it.next();
            Status status = doc.root().appendElement(elem);
            if (!status.isOK()) {
                return status;
            }
        }

        return Status::OK();
    }
Пример #14
0
    Status AuthorizationManager::getBSONForRole(RoleGraph* graph,
                                                const RoleName& roleName,
                                                mutablebson::Element result) {
        if (!graph->roleExists(roleName)) {
            return Status(ErrorCodes::RoleNotFound,
                          mongoutils::str::stream() << roleName.getFullName() <<
                                  "does not name an existing role");
        }
        std::string id = mongoutils::str::stream() << roleName.getDB() << "." << roleName.getRole();
        result.appendString("_id", id);
        result.appendString(ROLE_NAME_FIELD_NAME, roleName.getRole());
        result.appendString(ROLE_DB_FIELD_NAME, roleName.getDB());

        // Build privileges array
        mutablebson::Element privilegesArrayElement =
                result.getDocument().makeElementArray("privileges");
        result.pushBack(privilegesArrayElement);
        const PrivilegeVector& privileges = graph->getDirectPrivileges(roleName);
        Status status = getBSONForPrivileges(privileges, privilegesArrayElement);
        if (!status.isOK()) {
            return status;
        }

        // Build roles array
        mutablebson::Element rolesArrayElement = result.getDocument().makeElementArray("roles");
        result.pushBack(rolesArrayElement);
        for (RoleNameIterator roles = graph->getDirectSubordinates(roleName);
             roles.more();
             roles.next()) {

            const RoleName& subRole = roles.get();
            mutablebson::Element roleObj = result.getDocument().makeElementObject("");
            roleObj.appendString(ROLE_NAME_FIELD_NAME, subRole.getRole());
            roleObj.appendString(ROLE_DB_FIELD_NAME, subRole.getDB());
            rolesArrayElement.pushBack(roleObj);
        }

        return Status::OK();
    }
    Status ModifierAddToSet::prepare(mb::Element root,
                                     const StringData& matchedField,
                                     ExecInfo* execInfo) {

        _preparedState.reset(new PreparedState(root.getDocument()));

        // If we have a $-positional field, it is time to bind it to an actual field part.
        if (_posDollar) {
            if (matchedField.empty()) {
                return Status(ErrorCodes::BadValue, "matched field not provided");
            }
            _preparedState->boundDollar = matchedField.toString();
            _fieldRef.setPart(_posDollar, _preparedState->boundDollar);
        }

        // Locate the field name in 'root'.
        Status status = pathsupport::findLongestPrefix(_fieldRef,
                                                       root,
                                                       &_preparedState->idxFound,
                                                       &_preparedState->elemFound);

        // FindLongestPrefix may say the path does not exist at all, which is fine here, or
        // that the path was not viable or otherwise wrong, in which case, the mod cannot
        // proceed.
        if (status.code() == ErrorCodes::NonExistentPath) {
            _preparedState->elemFound = root.getDocument().end();
        } else if (!status.isOK()) {
            return status;
        }

        // We register interest in the field name. The driver needs this info to sort out if
        // there is any conflict among mods.
        execInfo->fieldRef[0] = &_fieldRef;

        //
        // in-place and no-op logic
        //

        // If the field path is not fully present, then this mod cannot be in place, nor is a
        // noOp.
        if (!_preparedState->elemFound.ok() ||
            _preparedState->idxFound < static_cast<int32_t>(_fieldRef.numParts() - 1)) {
            // If no target element exists, we will simply be creating a new array.
            _preparedState->addAll = true;
            return Status::OK();
        }

        // This operation only applies to arrays
        if (_preparedState->elemFound.getType() != mongo::Array)
            return Status(
                ErrorCodes::BadValue,
                "Cannot apply $addToSet to a non-array value");

        // If the array is empty, then we don't need to check anything: all of the values are
        // going to be added.
        if (!_preparedState->elemFound.hasChildren()) {
           _preparedState->addAll = true;
            return Status::OK();
        }

        // For each value in the $each clause, compare it against the values in the array. If
        // the element is not present, record it as one to add.
        mb::Element eachIter = _val.leftChild();
        while (eachIter.ok()) {
            mb::Element where = mb::findElement(
                _preparedState->elemFound.leftChild(),
                mb::woEqualTo(eachIter, false));
            if (!where.ok()) {
                // The element was not found. Record the element from $each as one to be added.
                _preparedState->elementsToAdd.push_back(eachIter);
            }
            eachIter = eachIter.rightSibling();
        }

        // If we didn't find any elements to add, then this is a no-op, and therefore in place.
        if (_preparedState->elementsToAdd.empty()) {
            _preparedState->noOp = execInfo->noOp = true;
            execInfo->inPlace = true;
        }

        return Status::OK();
    }
    Status ModifierInc::prepare(mutablebson::Element root,
                                const StringData& matchedField,
                                ExecInfo* execInfo) {

        _preparedState.reset(new PreparedState(root.getDocument()));

        // If we have a $-positional field, it is time to bind it to an actual field part.
        if (_posDollar) {
            if (matchedField.empty()) {
                return Status(ErrorCodes::BadValue, "matched field not provided");
            }
            _preparedState->boundDollar = matchedField.toString();
            _fieldRef.setPart(_posDollar, _preparedState->boundDollar);
        }

        // Locate the field name in 'root'. Note that we may not have all the parts in the path
        // in the doc -- which is fine. Our goal now is merely to reason about whether this mod
        // apply is a noOp or whether is can be in place. The remaining path, if missing, will
        // be created during the apply.
        Status status = pathsupport::findLongestPrefix(_fieldRef,
                                                       root,
                                                       &_preparedState->idxFound,
                                                       &_preparedState->elemFound);

        // FindLongestPrefix may say the path does not exist at all, which is fine here, or
        // that the path was not viable or otherwise wrong, in which case, the mod cannot
        // proceed.
        if (status.code() == ErrorCodes::NonExistentPath) {
            _preparedState->elemFound = root.getDocument().end();
        }
        else if (!status.isOK()) {
            return status;
        }

        // We register interest in the field name. The driver needs this info to sort out if
        // there is any conflict among mods.
        execInfo->fieldRef[0] = &_fieldRef;

        // Capture the value we are going to write. At this point, there may not be a value
        // against which to operate, so the result will be simply _val.
        _preparedState->newValue = _val;

        //
        // in-place and no-op logic
        //
        // If the field path is not fully present, then this mod cannot be in place, nor is a
        // noOp.
        if (!_preparedState->elemFound.ok() ||
            _preparedState->idxFound < static_cast<int32_t>(_fieldRef.numParts() - 1)) {
            return Status::OK();
        }

        // If the value being $inc'ed is the same as the one already in the doc, than this is a
        // noOp.
        if (!_preparedState->elemFound.isNumeric())
            return Status(ErrorCodes::BadValue,
                          "invalid attempt to increment a non-numeric field");

        const SafeNum currentValue = _preparedState->elemFound.getValueSafeNum();

        // Update newValue w.r.t to the current value of the found element.
        _preparedState->newValue += currentValue;

        // If the result of the addition is invalid, we must return an error.
        if (!_preparedState->newValue.isValid())
            return Status(ErrorCodes::BadValue,
                          "Failed to increment current value");

        // If the values are identical (same type, same value), then this is a no-op, and
        // therefore in-place as well.
        if (_preparedState->newValue.isIdentical(currentValue)) {
            _preparedState->noOp = execInfo->noOp = true;
            _preparedState->inPlace = execInfo->inPlace = true;
            return Status::OK();
        }

        // If the types are the same, this can be done in place.
        //
        // TODO: Potentially, cases where $inc results in a mixed type of the same size could
        // be in-place as well, but we don't currently handle them.
        if (_preparedState->newValue.type() == currentValue.type()) {
            _preparedState->inPlace = execInfo->inPlace = true;
        }

        return Status::OK();
    }
Пример #17
0
    Status ModifierInc::prepare(mutablebson::Element root,
                                const StringData& matchedField,
                                ExecInfo* execInfo) {

        _preparedState.reset(new PreparedState(root.getDocument()));

        // If we have a $-positional field, it is time to bind it to an actual field part.
        if (_posDollar) {
            if (matchedField.empty()) {
                return Status(ErrorCodes::BadValue,
                              str::stream() << "The positional operator did not find the match "
                                               "needed from the query. Unexpanded update: "
                                            << _fieldRef.dottedField());
            }
            _fieldRef.setPart(_posDollar, matchedField);
        }

        // Locate the field name in 'root'. Note that we may not have all the parts in the path
        // in the doc -- which is fine. Our goal now is merely to reason about whether this mod
        // apply is a noOp or whether is can be in place. The remaining path, if missing, will
        // be created during the apply.
        Status status = pathsupport::findLongestPrefix(_fieldRef,
                                                       root,
                                                       &_preparedState->idxFound,
                                                       &_preparedState->elemFound);

        // FindLongestPrefix may say the path does not exist at all, which is fine here, or
        // that the path was not viable or otherwise wrong, in which case, the mod cannot
        // proceed.
        if (status.code() == ErrorCodes::NonExistentPath) {
            _preparedState->elemFound = root.getDocument().end();
        }
        else if (!status.isOK()) {
            return status;
        }

        // We register interest in the field name. The driver needs this info to sort out if
        // there is any conflict among mods.
        execInfo->fieldRef[0] = &_fieldRef;

        // Capture the value we are going to write. At this point, there may not be a value
        // against which to operate, so the result will be simply _val.
        _preparedState->newValue = _val;

        //
        // in-place and no-op logic
        //
        // If the field path is not fully present, then this mod cannot be in place, nor is a
        // noOp.
        if (!_preparedState->elemFound.ok() ||
            _preparedState->idxFound < (_fieldRef.numParts() - 1)) {

            // For multiplication, we treat ops against missing as yielding zero. We take
            // advantage here of the promotion rules for SafeNum; the expression below will
            // always yield a zero of the same type of operand that the user provided
            // (e.g. double).
            if (_mode == MODE_MUL)
                _preparedState->newValue *= SafeNum(static_cast<int>(0));

            return Status::OK();
        }

        // If the value being $inc'ed is the same as the one already in the doc, than this is a
        // noOp.
        if (!_preparedState->elemFound.isNumeric()) {
            mb::Element idElem = mb::findFirstChildNamed(root, "_id");
            return Status(
                ErrorCodes::BadValue,
                str::stream() << "Cannot apply "
                              << (_mode == MODE_INC ? "$inc" : "$mul")
                              << " to a value of non-numeric type. {"
                              << idElem.toString()
                              << "} has the field '" <<  _preparedState->elemFound.getFieldName()
                              << "' of non-numeric type "
                              << typeName(_preparedState->elemFound.getType()));
        }
        const SafeNum currentValue = _preparedState->elemFound.getValueSafeNum();

        // Update newValue w.r.t to the current value of the found element.
        if (_mode == MODE_INC)
            _preparedState->newValue += currentValue;
        else
            _preparedState->newValue *= currentValue;

        // If the result of the addition is invalid, we must return an error.
        if (!_preparedState->newValue.isValid()) {
            mb::Element idElem = mb::findFirstChildNamed(root, "_id");
            return Status(ErrorCodes::BadValue,
                          str::stream() << "Failed to apply $inc operations to current value ("
                                        << currentValue.debugString() << ") for document {"
                                        << idElem.toString() << "}");
        }

        // If the values are identical (same type, same value), then this is a no-op.
        if (_preparedState->newValue.isIdentical(currentValue)) {
            _preparedState->noOp = execInfo->noOp = true;
            return Status::OK();
        }

        return Status::OK();
    }
Пример #18
0
    Status ModifierPush::prepare(mutablebson::Element root,
                                 const StringData& matchedField,
                                 ExecInfo* execInfo) {

        _preparedState.reset(new PreparedState(&root.getDocument()));

        // If we have a $-positional field, it is time to bind it to an actual field part.
        if (_posDollar) {
            if (matchedField.empty()) {
                return Status(ErrorCodes::BadValue, "matched field not provided");
            }
            _preparedState->boundDollar = matchedField.toString();
            _fieldRef.setPart(_posDollar, _preparedState->boundDollar);
        }

        // Locate the field name in 'root'. Note that we may not have all the parts in the path
        // in the doc -- which is fine. Our goal now is merely to reason about whether this mod
        // apply is a noOp or whether is can be in place. The remainin path, if missing, will
        // be created during the apply.
        Status status = pathsupport::findLongestPrefix(_fieldRef,
                                                       root,
                                                       &_preparedState->idxFound,
                                                       &_preparedState->elemFound);

        // FindLongestPrefix may say the path does not exist at all, which is fine here, or
        // that the path was not viable or otherwise wrong, in which case, the mod cannot
        // proceed.
        if (status.code() == ErrorCodes::NonExistentPath) {
            _preparedState->elemFound = root.getDocument().end();

        }
        else if (status.isOK()) {

            const bool destExists = (_preparedState->idxFound == (_fieldRef.numParts()-1));
            // If the path exists, we require the target field to be already an
            // array.
            if (destExists && _preparedState->elemFound.getType() != Array) {
                return Status(ErrorCodes::BadValue, "can only $push into arrays");
            }

            // If the $sort clause is being used, we require all the items in the array to be
            // objects themselves (as opposed to base types). This is a temporary restriction
            // that can be lifted once we support full sort semantics in $push.
            if (_sortPresent && destExists) {
                mutablebson::Element curr = _preparedState->elemFound.leftChild();
                while (curr.ok()) {
                    if (curr.getType() != Object) {
                        return Status(ErrorCodes::BadValue,
                                      "$push with sort requires object arrays");
                    }
                    curr = curr.rightSibling();
                }
            }

        }
        else {
            return status;
        }

        // We register interest in the field name. The driver needs this info to sort out if
        // there is any conflict among mods.
        execInfo->fieldRef[0] = &_fieldRef;

        return Status::OK();
    }
Пример #19
0
Status ModifierBit::prepare(mutablebson::Element root,
                            StringData matchedField,
                            ExecInfo* execInfo) {
    _preparedState.reset(new PreparedState(root.getDocument()));

    // If we have a $-positional field, it is time to bind it to an actual field part.
    if (_posDollar) {
        if (matchedField.empty()) {
            return Status(ErrorCodes::BadValue,
                          str::stream() << "The positional operator did not find the match "
                          "needed from the query. Unexpanded update: "
                          << _fieldRef.dottedField());
        }
        _fieldRef.setPart(_posDollar, matchedField);
    }

    // Locate the field name in 'root'.
    Status status = pathsupport::findLongestPrefix(
                        _fieldRef, root, &_preparedState->idxFound, &_preparedState->elemFound);


    // FindLongestPrefix may say the path does not exist at all, which is fine here, or
    // that the path was not viable or otherwise wrong, in which case, the mod cannot
    // proceed.
    if (status.code() == ErrorCodes::NonExistentPath) {
        _preparedState->elemFound = root.getDocument().end();
    } else if (!status.isOK()) {
        return status;
    }

    // We register interest in the field name. The driver needs this info to sort out if
    // there is any conflict among mods.
    execInfo->fieldRef[0] = &_fieldRef;

    //
    // in-place and no-op logic
    //

    // If the field path is not fully present, then this mod cannot be in place, nor is a
    // noOp.
    if (!_preparedState->elemFound.ok() || _preparedState->idxFound < (_fieldRef.numParts() - 1)) {
        // If no target element exists, the value we will write is the result of applying
        // the operation to a zero-initialized integer element.
        _preparedState->newValue = apply(SafeNum(static_cast<int>(0)));
        return Status::OK();
    }

    if (!_preparedState->elemFound.isIntegral()) {
        mb::Element idElem = mb::findElementNamed(root.leftChild(), "_id");
        return Status(ErrorCodes::BadValue,
                      str::stream() << "Cannot apply $bit to a value of non-integral type."
                      << idElem.toString() << " has the field "
                      << _preparedState->elemFound.getFieldName()
                      << " of non-integer type "
                      << typeName(_preparedState->elemFound.getType()));
    }

    const SafeNum currentValue = _preparedState->elemFound.getValueSafeNum();

    // Apply the op over the existing value and the mod value, and capture the result.
    _preparedState->newValue = apply(currentValue);

    if (!_preparedState->newValue.isValid()) {
        // TODO: Include list of ops, if that is easy, at some future point.
        return Status(ErrorCodes::BadValue,
                      str::stream() << "Failed to apply $bit operations to current value: "
                      << currentValue.debugString());
    }
    // If the values are identical (same type, same value), then this is a no-op.
    if (_preparedState->newValue.isIdentical(currentValue)) {
        _preparedState->noOp = execInfo->noOp = true;
        return Status::OK();
    }

    return Status::OK();
}
    Status ModifierPull::log(mb::Element logRoot) const {

        mb::Document& doc = logRoot.getDocument();

        mb::Element opElement = doc.end();
        mb::Element logElement = doc.end();

        if (!_preparedState->elemFound.ok() ||
            _preparedState->idxFound < static_cast<int32_t>(_fieldRef.numParts() - 1)) {

            // If we didn't find the element that we wanted to pull from, we log an unset for
            // that element.

            opElement = doc.makeElementObject("$unset");
            if (!opElement.ok()) {
                return Status(ErrorCodes::InternalError, "cannot create log entry for $pull mod");
            }

            logElement = doc.makeElementInt(_fieldRef.dottedField(), 1);

        } else {

            // TODO: This is copied more or less identically from $push. As a result, it copies the
            // behavior in $push that relies on 'apply' having been called unless this is a no-op.

            // TODO We can log just a positional unset in several cases. For now, let's just log
            // the full resulting array.

            // We'd like to create an entry such as {$set: {<fieldname>: [<resulting aray>]}} under
            // 'logRoot'.  We start by creating the {$set: ...} Element.

            opElement = doc.makeElementObject("$set");
            if (!opElement.ok()) {
                return Status(ErrorCodes::InternalError, "cannot create log entry for $pull mod");
            }

            // Then we create the {<fieldname>:[]} Element, that is, an empty array.
            logElement = doc.makeElementArray(_fieldRef.dottedField());
            if (!logElement.ok()) {
                return Status(ErrorCodes::InternalError, "cannot create details for $pull mod");
            }

            mb::Element curr = _preparedState->elemFound.leftChild();
            while (curr.ok()) {

                dassert(curr.hasValue());

                // We need to copy each array entry from the resulting document to the log
                // document.
                mb::Element currCopy = doc.makeElementWithNewFieldName(
                    StringData(),
                    curr.getValue());
                if (!currCopy.ok()) {
                    return Status(ErrorCodes::InternalError, "could create copy element");
                }
                Status status = logElement.pushBack(currCopy);
                if (!status.isOK()) {
                    return Status(ErrorCodes::BadValue, "could not append entry for $pull log");
                }
                curr = curr.rightSibling();
            }

        }

        // Now, we attach log element under the op element.
        Status status = opElement.pushBack(logElement);
        if (!status.isOK()) {
            return status;
        }

        // And attach the result under the 'logRoot' Element provided by the caller.
        return logRoot.pushBack(opElement);
    }
Пример #21
0
    Status ModifierPull::prepare(mb::Element root,
                                 const StringData& matchedField,
                                 ExecInfo* execInfo) {

        _preparedState.reset(new PreparedState(root.getDocument()));

        // If we have a $-positional field, it is time to bind it to an actual field part.
        if (_posDollar) {
            if (matchedField.empty()) {
                return Status(ErrorCodes::BadValue, "matched field not provided");
            }
            _preparedState->boundDollar = matchedField.toString();
            _fieldRef.setPart(_posDollar, _preparedState->boundDollar);
        }

        // Locate the field name in 'root'.
        Status status = pathsupport::findLongestPrefix(_fieldRef,
                                                       root,
                                                       &_preparedState->idxFound,
                                                       &_preparedState->elemFound);

        // FindLongestPrefix may say the path does not exist at all, which is fine here, or
        // that the path was not viable or otherwise wrong, in which case, the mod cannot
        // proceed.
        if (status.code() == ErrorCodes::NonExistentPath) {
            _preparedState->elemFound = root.getDocument().end();
        } else if (!status.isOK()) {
            return status;
        }

        // We register interest in the field name. The driver needs this info to sort out if
        // there is any conflict among mods.
        execInfo->fieldRef[0] = &_fieldRef;

        if (!_preparedState->elemFound.ok() ||
            _preparedState->idxFound < (_fieldRef.numParts() - 1)) {
            // If no target element exists, then there is nothing to do here.
            _preparedState->noOp = execInfo->noOp = true;
            return Status::OK();
        }

        // This operation only applies to arrays
        if (_preparedState->elemFound.getType() != mongo::Array)
            return Status(
                ErrorCodes::BadValue,
                "Cannot apply $pull to a non-array value");

        // If the array is empty, there is nothing to pull, so this is a noop.
        if (!_preparedState->elemFound.hasChildren()) {
            _preparedState->noOp = execInfo->noOp = true;
            return Status::OK();
        }

        // Walk the values in the array
        mb::Element cursor = _preparedState->elemFound.leftChild();
        while (cursor.ok()) {
            if (isMatch(cursor))
                _preparedState->elementsToRemove.push_back(cursor);
            cursor = cursor.rightSibling();
        }

        // If we didn't find any elements to add, then this is a no-op, and therefore in place.
        if (_preparedState->elementsToRemove.empty()) {
            _preparedState->noOp = execInfo->noOp = true;
        }

        return Status::OK();
    }
Пример #22
0
    Status ModifierBit::prepare(mutablebson::Element root,
                                const StringData& matchedField,
                                ExecInfo* execInfo) {

        _preparedState.reset(new PreparedState(root.getDocument()));

        // If we have a $-positional field, it is time to bind it to an actual field part.
        if (_posDollar) {
            if (matchedField.empty()) {
                return Status(ErrorCodes::BadValue, "matched field not provided");
            }
            _preparedState->boundDollar = matchedField.toString();
            _fieldRef.setPart(_posDollar, _preparedState->boundDollar);
        }

        // Locate the field name in 'root'.
        Status status = pathsupport::findLongestPrefix(_fieldRef,
                                                       root,
                                                       &_preparedState->idxFound,
                                                       &_preparedState->elemFound);


        // FindLongestPrefix may say the path does not exist at all, which is fine here, or
        // that the path was not viable or otherwise wrong, in which case, the mod cannot
        // proceed.
        if (status.code() == ErrorCodes::NonExistentPath) {
            _preparedState->elemFound = root.getDocument().end();
        }
        else if (!status.isOK()) {
            return status;
        }

        // We register interest in the field name. The driver needs this info to sort out if
        // there is any conflict among mods.
        execInfo->fieldRef[0] = &_fieldRef;

        //
        // in-place and no-op logic
        //

        // If the field path is not fully present, then this mod cannot be in place, nor is a
        // noOp.
        if (!_preparedState->elemFound.ok() ||
            _preparedState->idxFound < (_fieldRef.numParts() - 1)) {
            // If no target element exists, the value we will write is the result of applying
            // the operation to a zero-initialized integer element.
            _preparedState->newValue = apply(SafeNum(static_cast<int>(0)));
            return Status::OK();
        }

        if (!_preparedState->elemFound.isIntegral())
            return Status(
                ErrorCodes::BadValue,
                "Cannot apply $bit to a value of non-integral type");

        const SafeNum currentValue = _preparedState->elemFound.getValueSafeNum();

        // Apply the op over the existing value and the mod value, and capture the result.
        _preparedState->newValue = apply(currentValue);

        if (!_preparedState->newValue.isValid())
            return Status(ErrorCodes::BadValue,
                          "Failed to apply $bit to current value");

        // If the values are identical (same type, same value), then this is a no-op.
        if (_preparedState->newValue.isIdentical(currentValue)) {
            _preparedState->noOp = execInfo->noOp = true;
            return Status::OK();
        }

        return Status::OK();
    }
Пример #23
0
Status findLongestPrefix(const FieldRef& prefix,
                         mutablebson::Element root,
                         size_t* idxFound,
                         mutablebson::Element* elemFound) {
    // If root is empty or the prefix is so, there's no point in looking for a prefix.
    const size_t prefixSize = prefix.numParts();
    if (!root.hasChildren() || prefixSize == 0) {
        return Status(ErrorCodes::NonExistentPath, "either the document or the path are empty");
    }

    // Loop through prefix's parts. At each iteration, check that the part ('curr') exists
    // in 'root' and that the type of the previous part ('prev') allows for children.
    mutablebson::Element curr = root;
    mutablebson::Element prev = root;
    size_t i = 0;
    size_t numericPart = 0;
    bool viable = true;
    for (; i < prefixSize; i++) {
        // If prefix wants to reach 'curr' by applying a non-numeric index to an array
        // 'prev', or if 'curr' wants to traverse a leaf 'prev', then we'd be in a
        // non-viable path (see definition on the header file).
        StringData prefixPart = prefix.getPart(i);
        prev = curr;
        switch (curr.getType()) {
            case Object:
                curr = prev[prefixPart];
                break;

            case Array:
                if (!isNumeric(prefixPart, &numericPart)) {
                    viable = false;
                } else {
                    curr = prev[numericPart];
                }
                break;

            default:
                viable = false;
        }

        // If we couldn't find the next field part of the prefix in the document or if the
        // field part we're in constitutes a non-viable path, we can stop looking.
        if (!curr.ok() || !viable) {
            break;
        }
    }

    // We broke out of the loop because one of four things happened. (a) 'prefix' and
    // 'root' have nothing in common, (b) 'prefix' is not viable in 'root', (c) not all the
    // parts in 'prefix' exist in 'root', or (d) all parts do. In each case, we need to
    // figure out what index and Element pointer to return.
    if (i == 0) {
        return Status(ErrorCodes::NonExistentPath, "cannot find path in the document");
    } else if (!viable) {
        *idxFound = i - 1;
        *elemFound = prev;
        return Status(ErrorCodes::PathNotViable,
                      mongolutils::str::stream() << "cannot use the part (" << prefix.getPart(i - 1)
                                                << " of " << prefix.dottedField()
                                                << ") to traverse the element ({" << curr.toString()
                                                << "})");
    } else if (curr.ok()) {
        *idxFound = i - 1;
        *elemFound = curr;
        return Status::OK();
    } else {
        *idxFound = i - 1;
        *elemFound = prev;
        return Status::OK();
    }
}
Пример #24
0
Status createPathAt(const FieldRef& prefix,
                    size_t idxFound,
                    mutablebson::Element elemFound,
                    mutablebson::Element newElem) {
    Status status = Status::OK();

    // Sanity check that 'idxField' is an actual part.
    const size_t size = prefix.numParts();
    if (idxFound >= size) {
        return Status(ErrorCodes::BadValue, "index larger than path size");
    }

    mutablebson::Document& doc = elemFound.getDocument();

    // If we are creating children under an array and a numeric index is next, then perhaps
    // we need padding.
    size_t i = idxFound;
    bool inArray = false;
    if (elemFound.getType() == mongol::Array) {
        size_t newIdx = 0;
        if (!isNumeric(prefix.getPart(idxFound), &newIdx)) {
            return Status(ErrorCodes::InvalidPath, "Array require numeric fields");
        }

        status = maybePadTo(&elemFound, newIdx);
        if (!status.isOK()) {
            return status;
        }

        // If there is a next field, that would be an array element. We'd like to mark that
        // field because we create array elements differently than we do regular objects.
        if (++i < size) {
            inArray = true;
        }
    }

    // Create all the remaining parts but the last one.
    for (; i < size - 1; i++) {
        mutablebson::Element elem = doc.makeElementObject(prefix.getPart(i));
        if (!elem.ok()) {
            return Status(ErrorCodes::InternalError, "cannot create path");
        }

        // If this field is an array element, we wrap it in an object (because array
        // elements are wraped in { "N": <element> } objects.
        if (inArray) {
            // TODO pass empty StringData to makeElementObject, when that's supported.
            mutablebson::Element arrayObj = doc.makeElementObject("" /* it's an array */);
            if (!arrayObj.ok()) {
                return Status(ErrorCodes::InternalError, "cannot create item on array");
            }
            status = arrayObj.pushBack(elem);
            if (!status.isOK()) {
                return status;
            }
            status = elemFound.pushBack(arrayObj);
            if (!status.isOK()) {
                return status;
            }
            inArray = false;
        } else {
            status = elemFound.pushBack(elem);
            if (!status.isOK()) {
                return status;
            }
        }

        elemFound = elem;
    }

    // Attach the last element. Here again, if we're in a field that is an array element,
    // we wrap it in an object first.
    if (inArray) {
        // TODO pass empty StringData to makeElementObject, when that's supported.
        mutablebson::Element arrayObj = doc.makeElementObject("" /* it's an array */);
        if (!arrayObj.ok()) {
            return Status(ErrorCodes::InternalError, "cannot create item on array");
        }

        status = arrayObj.pushBack(newElem);
        if (!status.isOK()) {
            return status;
        }

        status = elemFound.pushBack(arrayObj);
        if (!status.isOK()) {
            return status;
        }

    } else {
        status = elemFound.pushBack(newElem);
        if (!status.isOK()) {
            return status;
        }
    }

    return Status::OK();
}
Пример #25
0
Status ModifierPullAll::prepare(mutablebson::Element root,
                                StringData matchedField,
                                ExecInfo* execInfo) {
    _preparedState.reset(new PreparedState(&root.getDocument()));

    // If we have a $-positional field, it is time to bind it to an actual field part.
    if (_positionalPathIndex) {
        if (matchedField.empty()) {
            return Status(ErrorCodes::BadValue,
                          str::stream() << "The positional operator did not find the match "
                                           "needed from the query. Unexpanded update: "
                                        << _fieldRef.dottedField());
        }
        _fieldRef.setPart(_positionalPathIndex, matchedField);
    }

    // Locate the field name in 'root'. Note that if we don't have the full path in the
    // doc, there isn't anything to unset, really.
    Status status = pathsupport::findLongestPrefix(
        _fieldRef, root, &_preparedState->pathFoundIndex, &_preparedState->pathFoundElement);
    // Check if we didn't find the full path
    if (status.isOK()) {
        const bool destExists = (_preparedState->pathFoundIndex == (_fieldRef.numParts() - 1));

        if (!destExists) {
            execInfo->noOp = true;
        } else {
            // If the path exists, we require the target field to be already an
            // array.
            if (_preparedState->pathFoundElement.getType() != Array) {
                mb::Element idElem = mb::findElementNamed(root.leftChild(), "_id");
                return Status(
                    ErrorCodes::BadValue,
                    str::stream() << "Can only apply $pullAll to an array. " << idElem.toString()
                                  << " has the field "
                                  << _preparedState->pathFoundElement.getFieldName()
                                  << " of non-array type "
                                  << typeName(_preparedState->pathFoundElement.getType()));
            }

            // No children, nothing to do -- not an error state
            if (!_preparedState->pathFoundElement.hasChildren()) {
                execInfo->noOp = true;
            } else {
                mutablebson::Element elem = _preparedState->pathFoundElement.leftChild();
                while (elem.ok()) {
                    if (std::find_if(_elementsToFind.begin(),
                                     _elementsToFind.end(),
                                     mutableElementEqualsBSONElement(elem, _collator)) !=
                        _elementsToFind.end()) {
                        _preparedState->elementsToRemove.push_back(elem);
                    }
                    elem = elem.rightSibling();
                }

                // Nothing to remove so it is a noOp.
                if (_preparedState->elementsToRemove.empty())
                    execInfo->noOp = true;
            }
        }
    } else {
        // Let the caller know we can't do anything given the mod, _fieldRef, and doc.
        execInfo->noOp = true;


        // okay if path not found
        if (status.code() == ErrorCodes::NonExistentPath)
            status = Status::OK();
    }

    // Let the caller know what field we care about
    execInfo->fieldRef[0] = &_fieldRef;

    return status;
}
Пример #26
0
Status ModifierRename::prepare(mutablebson::Element root,
                               StringData matchedField,
                               ExecInfo* execInfo) {
    // Rename doesn't work with positional fields ($)
    dassert(matchedField.empty());

    _preparedState.reset(new PreparedState(root));

    // Locate the to field name in 'root', which must exist.
    size_t fromIdxFound;
    Status status = pathsupport::findLongestPrefix(
        _fromFieldRef, root, &fromIdxFound, &_preparedState->fromElemFound);

    const bool sourceExists =
        (_preparedState->fromElemFound.ok() && fromIdxFound == (_fromFieldRef.numParts() - 1));

    // If we can't find the full element in the from field then we can't do anything.
    if (!status.isOK() || !sourceExists) {
        execInfo->noOp = true;
        _preparedState->fromElemFound = root.getDocument().end();

        // TODO: remove this special case from existing behavior
        if (status.code() == ErrorCodes::PathNotViable) {
            return status;
        }

        return Status::OK();
    }

    // Ensure no array in ancestry if what we found is not at the root
    mutablebson::Element curr = _preparedState->fromElemFound.parent();
    if (curr != curr.getDocument().root())
        while (curr.ok() && (curr != curr.getDocument().root())) {
            if (curr.getType() == Array)
                return Status(ErrorCodes::BadValue,
                              str::stream() << "The source field cannot be an array element, '"
                                            << _fromFieldRef.dottedField() << "' in doc with "
                                            << findElementNamed(root.leftChild(), "_id").toString()
                                            << " has an array field called '" << curr.getFieldName()
                                            << "'");
            curr = curr.parent();
        }

    // "To" side validation below

    status = pathsupport::findLongestPrefix(
        _toFieldRef, root, &_preparedState->toIdxFound, &_preparedState->toElemFound);

    // FindLongestPrefix may return not viable or any other error and then we cannot proceed.
    if (status.code() == ErrorCodes::NonExistentPath) {
        // Not an error condition as we will create the "to" path as needed.
    } else if (!status.isOK()) {
        return status;
    }

    const bool destExists = _preparedState->toElemFound.ok() &&
        (_preparedState->toIdxFound == (_toFieldRef.numParts() - 1));

    // Ensure no array in ancestry of "to" Element
    // Set to either parent, or node depending on if the full path element was found
    curr = (destExists ? _preparedState->toElemFound.parent() : _preparedState->toElemFound);
    if (curr != curr.getDocument().root()) {
        while (curr.ok()) {
            if (curr.getType() == Array)
                return Status(ErrorCodes::BadValue,
                              str::stream() << "The destination field cannot be an array element, '"
                                            << _fromFieldRef.dottedField() << "' in doc with "
                                            << findElementNamed(root.leftChild(), "_id").toString()
                                            << " has an array field called '" << curr.getFieldName()
                                            << "'");
            curr = curr.parent();
        }
    }

    // We register interest in the field name. The driver needs this info to sort out if
    // there is any conflict among mods.
    execInfo->fieldRef[0] = &_fromFieldRef;
    execInfo->fieldRef[1] = &_toFieldRef;

    execInfo->noOp = false;

    return Status::OK();
}
Пример #27
0
    Status ModifierSet::prepare(mutablebson::Element root,
                                const StringData& matchedField,
                                ExecInfo* execInfo) {

        _preparedState.reset(new PreparedState(&root.getDocument()));

        // If we have a $-positional field, it is time to bind it to an actual field part.
        if (_posDollar) {
            if (matchedField.empty()) {
                return Status(ErrorCodes::BadValue,
                              str::stream() << "The positional operator did not find the match "
                                               "needed from the query. Unexpanded update: "
                                            << _fieldRef.dottedField());
            }
            _fieldRef.setPart(_posDollar, matchedField);
        }

        // Locate the field name in 'root'. Note that we may not have all the parts in the path
        // in the doc -- which is fine. Our goal now is merely to reason about whether this mod
        // apply is a noOp or whether is can be in place. The remainin path, if missing, will
        // be created during the apply.
        Status status = pathsupport::findLongestPrefix(_fieldRef,
                                                       root,
                                                       &_preparedState->idxFound,
                                                       &_preparedState->elemFound);

        // FindLongestPrefix may say the path does not exist at all, which is fine here, or
        // that the path was not viable or otherwise wrong, in which case, the mod cannot
        // proceed.
        if (status.code() == ErrorCodes::NonExistentPath) {
            _preparedState->elemFound = root.getDocument().end();
        }
        else if (_modOptions.fromReplication && status.code() == ErrorCodes::PathNotViable) {
            // If we are coming from replication and it is an invalid path,
            // then push on indicating that we had a blocking element, which we stopped at
            _preparedState->elemIsBlocking = true;
        }
        else if (!status.isOK()) {
            return status;
        }

        if (_setMode == SET_ON_INSERT) {
            execInfo->context = ModifierInterface::ExecInfo::INSERT_CONTEXT;
        }

        // We register interest in the field name. The driver needs this info to sort out if
        // there is any conflict among mods.
        execInfo->fieldRef[0] = &_fieldRef;

        //
        // in-place and no-op logic
        //

        // If the field path is not fully present, then this mod cannot be in place, nor is a
        // noOp.
        if (!_preparedState->elemFound.ok() ||
            _preparedState->idxFound < (_fieldRef.numParts()-1)) {
            return Status::OK();
        }

        // If the value being $set is the same as the one already in the doc, than this is a
        // noOp.
        if (_preparedState->elemFound.ok() &&
            _preparedState->idxFound == (_fieldRef.numParts()-1) &&
            _preparedState->elemFound.compareWithBSONElement(_val, false /*ignore field*/) == 0) {
            execInfo->noOp = _preparedState->noOp = true;
        }

        return Status::OK();
    }
Пример #28
0
Status ModifierSet::prepare(mutablebson::Element root,
                            const StringData& matchedField,
                            ExecInfo* execInfo) {

    _preparedState.reset(new PreparedState(&root.getDocument()));

    // If we have a $-positional field, it is time to bind it to an actual field part.
    if (_posDollar) {
        if (matchedField.empty()) {
            return Status(ErrorCodes::BadValue, "matched field not provided");
        }
        _preparedState->boundDollar = matchedField.toString();
        _fieldRef.setPart(_posDollar, _preparedState->boundDollar);
    }

    // Locate the field name in 'root'. Note that we may not have all the parts in the path
    // in the doc -- which is fine. Our goal now is merely to reason about whether this mod
    // apply is a noOp or whether is can be in place. The remainin path, if missing, will
    // be created during the apply.
    Status status = pathsupport::findLongestPrefix(_fieldRef,
                    root,
                    &_preparedState->idxFound,
                    &_preparedState->elemFound);

    // FindLongestPrefix may say the path does not exist at all, which is fine here, or
    // that the path was not viable or otherwise wrong, in which case, the mod cannot
    // proceed.
    if (status.code() == ErrorCodes::NonExistentPath) {
        _preparedState->elemFound = root.getDocument().end();
    }
    else if (!status.isOK()) {
        return status;
    }

    if (_setMode == SET_ON_INSERT) {
        execInfo->context = ModifierInterface::ExecInfo::INSERT_CONTEXT;
    }

    // We register interest in the field name. The driver needs this info to sort out if
    // there is any conflict among mods.
    execInfo->fieldRef[0] = &_fieldRef;

    //
    // in-place and no-op logic
    //

    // If the field path is not fully present, then this mod cannot be in place, nor is a
    // noOp.
    if (!_preparedState->elemFound.ok() ||
            _preparedState->idxFound < static_cast<int32_t>(_fieldRef.numParts()-1)) {
        return Status::OK();
    }

    // We may allow this $set to be in place if the value being set and the existing one
    // have the same size.
    if (_val.isNumber() &&
            (_preparedState->elemFound != root.getDocument().end()) &&
            (_val.type() == _preparedState->elemFound.getType())) {
        execInfo->inPlace = _preparedState->inPlace = true;
    }

    // If the value being $set is the same as the one already in the doc, than this is a
    // noOp.
    if (_preparedState->elemFound.ok() &&
            _preparedState->idxFound == static_cast<int32_t>(_fieldRef.numParts()-1) &&
            _preparedState->elemFound.compareWithBSONElement(_val, false /*ignore field*/) == 0) {
        execInfo->noOp = _preparedState->noOp = true;
    }

    return Status::OK();
}