示例#1
0
    Status ModifierInc::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'. 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::TypeMismatch,
                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();
    }