예제 #1
0
    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,
                              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'.
        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, 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) {
            mb::Element idElem = mb::findElementNamed(root.leftChild(), "_id");
            return Status(
                ErrorCodes::BadValue,
                str::stream() << "Cannot apply $addToSet to a non-array field. Field named '"
                              <<  _preparedState->elemFound.getFieldName()
                              << "' has a non-array type "
                              << typeName(_preparedState->elemFound.getType())
                              << " in the document "
                              << idElem.toString());
        }

        // 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.
        if (_preparedState->elementsToAdd.empty()) {
            _preparedState->noOp = execInfo->noOp = true;
        }

        return Status::OK();
    }
예제 #2
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();
}
예제 #3
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();
}
예제 #4
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;
}