StringData FieldRef::dottedSubstring(size_t startPart, size_t endPart) const {
    if (_size == 0 || startPart >= endPart || endPart > numParts())
        return StringData();

    if (!_replacements.empty() || _size != _cachedSize)
        reserialize();
    dassert(_replacements.empty() && _size == _cachedSize);

    StringData result(_dotted);

    // Fast-path if we want the whole thing
    if (startPart == 0 && endPart == numParts())
        return result;

    size_t startChar = 0;
    for (size_t i = 0; i < startPart; ++i) {
        startChar += getPart(i).size() + 1;  // correct for '.'
    }
    size_t endChar = startChar;
    for (size_t i = startPart; i < endPart; ++i) {
        endChar += getPart(i).size() + 1;
    }
    // correct for last '.'
    if (endPart != numParts())
        --endChar;

    return result.substr(startChar, endChar - startChar);
}
bool FieldRef::hasNumericPathComponents() const {
    for (size_t i = 0; i < numParts(); ++i) {
        if (isNumericPathComponentStrict(i))
            return true;
    }
    return false;
}
std::set<size_t> FieldRef::getNumericPathComponents(size_t startPart) const {
    std::set<size_t> numericPathComponents;
    for (auto i = startPart; i < numParts(); ++i) {
        if (isNumericPathComponentStrict(i))
            numericPathComponents.insert(i);
    }
    return numericPathComponents;
}
Ejemplo n.º 4
0
    std::string FieldRef::dottedField( size_t offset ) const {
        std::string res;

        if (_size == 0 || offset >= numParts() ) {
            return res;
        }

        for (size_t i=offset; i<_size; i++) {
            if ( i > offset )
                res.append(1, '.');
            StringData part = getPart(i);
            res.append(part.rawData(), part.size());
        }
        return res;
    }
StringData FieldRef::dottedField(size_t offset) const {
    return dottedSubstring(offset, numParts());
}
UpdateNode::ApplyResult RenameNode::apply(ApplyParams applyParams) const {
    // It would make sense to store fromFieldRef and toFieldRef as members during
    // RenameNode::init(), but FieldRef is not copyable.
    auto fromFieldRef = std::make_shared<FieldRef>(_val.fieldName());
    FieldRef toFieldRef(_val.valueStringData());

    mutablebson::Document& document = applyParams.element.getDocument();

    size_t fromIdxFound;
    mutablebson::Element fromElement(document.end());
    auto status =
        pathsupport::findLongestPrefix(*fromFieldRef, document.root(), &fromIdxFound, &fromElement);

    if (!status.isOK() || !fromElement.ok() || fromIdxFound != (fromFieldRef->numParts() - 1)) {
        // We could safely remove this restriction (thereby treating a rename with a non-viable
        // source path as a no-op), but most updates fail on an attempt to update a non-viable path,
        // so we throw an error for consistency.
        if (status == ErrorCodes::PathNotViable) {
            uassertStatusOK(status);
            MONGO_UNREACHABLE;  // The previous uassertStatusOK should always throw.
        }

        // The element we want to rename does not exist. When that happens, we treat the operation
        // as a no-op. The attempted from/to paths are still considered modified.
        if (applyParams.modifiedPaths) {
            applyParams.modifiedPaths->keepShortest(*fromFieldRef);
            applyParams.modifiedPaths->keepShortest(toFieldRef);
        }
        return ApplyResult::noopResult();
    }

    // Renaming through an array is prohibited. Check that our source path does not contain an
    // array. (The element being renamed may be an array, however.)
    for (auto currentElement = fromElement.parent(); currentElement != document.root();
         currentElement = currentElement.parent()) {
        invariant(currentElement.ok());
        if (BSONType::Array == currentElement.getType()) {
            auto idElem = mutablebson::findFirstChildNamed(document.root(), "_id");
            uasserted(ErrorCodes::BadValue,
                      str::stream() << "The source field cannot be an array element, '"
                                    << fromFieldRef->dottedField()
                                    << "' in doc with "
                                    << (idElem.ok() ? idElem.toString() : "no id")
                                    << " has an array field called '"
                                    << currentElement.getFieldName()
                                    << "'");
        }
    }

    // Check that our destination path does not contain an array. (If the rename will overwrite an
    // existing element, that element may be an array. Iff pathToCreate is empty, "element"
    // represents an element that we are going to overwrite.)
    for (auto currentElement = applyParams.pathToCreate->empty() ? applyParams.element.parent()
                                                                 : applyParams.element;
         currentElement != document.root();
         currentElement = currentElement.parent()) {
        invariant(currentElement.ok());
        if (BSONType::Array == currentElement.getType()) {
            auto idElem = mutablebson::findFirstChildNamed(document.root(), "_id");
            uasserted(ErrorCodes::BadValue,
                      str::stream() << "The destination field cannot be an array element, '"
                                    << toFieldRef.dottedField()
                                    << "' in doc with "
                                    << (idElem.ok() ? idElem.toString() : "no id")
                                    << " has an array field called '"
                                    << currentElement.getFieldName()
                                    << "'");
        }
    }

    // Once we've determined that the rename is valid and found the source element, the actual work
    // gets broken out into a $set operation and an $unset operation. Note that, generally, we
    // should call the init() method of a ModifierNode before calling its apply() method, but the
    // init() methods of SetElementNode and UnsetNode don't do anything, so we can skip them.
    SetElementNode setElement(fromElement);
    auto setElementApplyResult = setElement.apply(applyParams);

    ApplyParams unsetParams(applyParams);
    unsetParams.element = fromElement;
    unsetParams.pathToCreate = std::make_shared<FieldRef>();
    unsetParams.pathTaken = fromFieldRef;

    UnsetNode unsetElement;
    auto unsetElementApplyResult = unsetElement.apply(unsetParams);

    // Determine the final result based on the results of the $set and $unset.
    ApplyResult applyResult;
    applyResult.indexesAffected =
        setElementApplyResult.indexesAffected || unsetElementApplyResult.indexesAffected;

    // The $unset would only be a no-op if the source element did not exist, in which case we would
    // have exited early with a no-op result.
    invariant(!unsetElementApplyResult.noop);

    return applyResult;
}