FieldRef UpdateIndexData::getCanonicalIndexField(const FieldRef& path) {
    if (path.numParts() <= 1)
        return path;

    // The first part of the path must always be a valid field name, since it's not possible to
    // store a top-level array or '$' field name in a document.
    FieldRef buf(path.getPart(0));
    for (size_t i = 1; i < path.numParts(); ++i) {
        auto pathComponent = path.getPart(i);

        if (pathComponent == "$"_sd) {
            continue;
        }

        if (FieldRef::isNumericPathComponentLenient(pathComponent)) {
            // Peek ahead to see if the next component is also all digits. This implies that the
            // update is attempting to create a numeric field name which would violate the
            // "ambiguous field name in array" constraint for multi-key indexes. Break early in this
            // case and conservatively return that this path affects the prefix of the consecutive
            // numerical path components. For instance, an input such as 'a.0.1.b.c' would return
            // the canonical index path of 'a'.
            if ((i + 1) < path.numParts() &&
                FieldRef::isNumericPathComponentLenient(path.getPart(i + 1))) {
                break;
            }
            continue;
        }

        buf.appendPart(pathComponent);
    }

    return buf;
}
Status setElementAtPath(const FieldRef& path,
                        const BSONElement& value,
                        mutablebson::Document* doc) {
    size_t deepestElemPathPart;
    mutablebson::Element deepestElem(doc->end());

    // Get the existing parents of this path
    Status status = findLongestPrefix(path, doc->root(), &deepestElemPathPart, &deepestElem);

    // TODO: All this is pretty awkward, why not return the position immediately after the
    // consumed path or use a signed sentinel?  Why is it a special case when we've consumed the
    // whole path?

    if (!status.isOK() && status.code() != ErrorCodes::NonExistentPath)
        return status;

    // Inc the path by one *unless* we matched nothing
    if (status.code() != ErrorCodes::NonExistentPath) {
        ++deepestElemPathPart;
    } else {
        deepestElemPathPart = 0;
        deepestElem = doc->root();
    }

    if (deepestElemPathPart == path.numParts()) {
        // The full path exists already in the document, so just set a value
        return deepestElem.setValueBSONElement(value);
    } else {
        // Construct the rest of the path we need with empty documents and set the value
        StringData leafFieldName = path.getPart(path.numParts() - 1);
        mutablebson::Element leafElem = doc->makeElementWithNewFieldName(leafFieldName, value);
        dassert(leafElem.ok());
        return createPathAt(path, deepestElemPathPart, deepestElem, leafElem);
    }
}
Exemple #3
0
 Status basicIsUpdatable(const FieldRef& field) {
     StringData firstPart = field.getPart(0);
     if (firstPart.compare("_id") == 0) {
         return Status(ErrorCodes::BadValue, "updated cannot affect the _id");
     }
     return Status::OK();
 }
Exemple #4
0
void BSONElementIterator::ArrayIterationState::reset(const FieldRef& ref, int start) {
    restOfPath = ref.dottedField(start).toString();
    hasMore = restOfPath.size() > 0;
    if (hasMore) {
        nextPieceOfPath = ref.getPart(start);
        nextPieceOfPathIsNumber = isAllDigits(nextPieceOfPath);
    } else {
        nextPieceOfPathIsNumber = false;
    }
}
Exemple #5
0
bool hasArrayFilter(const FieldRef& fieldRef) {
    auto size = fieldRef.numParts();
    for (size_t i = 0; i < size; i++) {
        auto fieldPart = fieldRef.getPart(i);
        if (isArrayFilterIdentifier(fieldPart)) {
            return true;
        }
    }
    return false;
}
int FieldRef::compare(const FieldRef& other) const {
    const size_t toCompare = std::min(_size, other._size);
    for (size_t i = 0; i < toCompare; i++) {
        if (getPart(i) == other.getPart(i)) {
            continue;
        }
        return getPart(i) < other.getPart(i) ? -1 : 1;
    }

    const size_t rest = _size - toCompare;
    const size_t otherRest = other._size - toCompare;
    if ((rest == 0) && (otherRest == 0)) {
        return 0;
    } else if (rest < otherRest) {
        return -1;
    } else {
        return 1;
    }
}
Exemple #7
0
    Status UpdateDriver::createFromQuery(const BSONObj& query, mutablebson::Document& doc) {
        BSONObjIteratorSorted i(query);
        while (i.more()) {
            BSONElement e = i.next();
            // TODO: get this logic/exclude-list from the query system?
            if (e.fieldName()[0] == '$' || e.fieldNameStringData() == "_id")
                continue;


            if (e.type() == Object && e.embeddedObject().firstElementFieldName()[0] == '$') {
                // we have something like { x : { $gt : 5 } }
                // this can be a query piece
                // or can be a dbref or something

                int op = e.embeddedObject().firstElement().getGtLtOp();
                if (op > 0) {
                    // This means this is a $gt type filter, so don't make it part of the new
                    // object.
                    continue;
                }

                if (mongoutils::str::equals(e.embeddedObject().firstElement().fieldName(),
                                              "$not")) {
                    // A $not filter operator is not detected in getGtLtOp() and should not
                    // become part of the new object.
                    continue;
                }
            }

            // Add to the field to doc after expanding and checking for conflicts.
            FieldRef elemName;
            const StringData& elemNameSD(e.fieldNameStringData());
            elemName.parse(elemNameSD);

            size_t pos;
            mutablebson::Element* elemFound = NULL;

            Status status = pathsupport::findLongestPrefix(elemName, doc.root(), &pos, elemFound);
            // Not NonExistentPath, of OK, return
            if (!(status.code() == ErrorCodes::NonExistentPath || status.isOK()))
                return status;

            status = pathsupport::createPathAt(elemName,
                                               0,
                                               doc.root(),
                                               doc.makeElementWithNewFieldName(
                                                       elemName.getPart(elemName.numParts()-1),
                                                       e));
            if (!status.isOK())
                return status;
        }
        return Status::OK();
    }
Exemple #8
0
    Status isUpdatable(const FieldRef& field) {
        Status status = basicIsUpdatable(field);
        if (! status.isOK()) {
            return status;
        }

        StringData firstPart = field.getPart(0);
        if (firstPart[0] == '$') {
            return Status(ErrorCodes::BadValue, "field name cannot start with $");
        }

        return Status::OK();
    }
    bool FieldRef::isPrefixOf( const FieldRef& other ) const {
        // Can't be a prefix if equal to or larger
        if ( other._size <= _size )
            return false;

        for ( size_t i = 0; i < _size; i++ ) {
            // Get parts
            StringData part = getPart( i );
            StringData otherPart = other.getPart( i );

            // If the parts are diff, can't be a prefix
            if ( part != otherPart )
                return false;
        }
        return true;
    }
size_t FieldRef::commonPrefixSize(const FieldRef& other) const {
    if (_size == 0 || other._size == 0) {
        return 0;
    }

    size_t maxPrefixSize = std::min(_size - 1, other._size - 1);
    size_t prefixSize = 0;

    while (prefixSize <= maxPrefixSize) {
        if (getPart(prefixSize) != other.getPart(prefixSize)) {
            break;
        }
        prefixSize++;
    }

    return prefixSize;
}
bool isPositional(const FieldRef& fieldRef, size_t* pos, size_t* count) {
    // 'count' is optional.
    size_t dummy;
    if (count == NULL) {
        count = &dummy;
    }

    *count = 0;
    size_t size = fieldRef.numParts();
    for (size_t i = 0; i < size; i++) {
        StringData fieldPart = fieldRef.getPart(i);
        if ((fieldPart.size() == 1) && (fieldPart[0] == '$')) {
            if (*count == 0)
                *pos = i;
            (*count)++;
        }
    }
    return *count > 0;
}
Exemple #12
0
    BSONElement getFieldDottedOrArray( const BSONObj& doc,
                                       const FieldRef& path,
                                       size_t* idxPath ) {
        if ( path.numParts() == 0 )
            return doc.getField( "" );

        BSONElement res;

        BSONObj curr = doc;
        bool stop = false;
        size_t partNum = 0;
        while ( partNum < path.numParts() && !stop ) {

            res = curr.getField( path.getPart( partNum ) );

            switch ( res.type() ) {

            case EOO:
                stop = true;
                break;

            case Object:
                curr = res.Obj();
                ++partNum;
                break;

            case Array:
                stop = true;
                break;

            default:
                if ( partNum+1 < path.numParts() ) {
                    res = BSONElement();
                }
                stop = true;

            }
        }

        *idxPath = partNum;
        return res;
    }
Status isUpdatable(const FieldRef& field) {
    const size_t numParts = field.numParts();

    if (numParts == 0) {
        return Status(ErrorCodes::EmptyFieldName, "An empty update path is not valid.");
    }

    for (size_t i = 0; i != numParts; ++i) {
        const StringData part = field.getPart(i);

        if (part.empty()) {
            return Status(ErrorCodes::EmptyFieldName,
                          mongolutils::str::stream()
                              << "The update path '" << field.dottedField()
                              << "' contains an empty field name, which is not allowed.");
        }
    }

    return Status::OK();
}
bool UpdateIndexData::mightBeIndexed(const FieldRef& path) const {
    if (_allPathsIndexed) {
        return true;
    }

    FieldRef canonicalPath = getCanonicalIndexField(path);

    for (const auto& idx : _canonicalPaths) {
        if (_startsWith(canonicalPath, idx) || _startsWith(idx, canonicalPath))
            return true;
    }

    for (const auto& pathComponent : _pathComponents) {
        for (size_t partIdx = 0; partIdx < path.numParts(); ++partIdx) {
            if (pathComponent == path.getPart(partIdx)) {
                return true;
            }
        }
    }

    return false;
}
Exemple #15
0
bool LeafMatchExpression::_matches( const FieldRef& fieldRef,
                                    const MatchableDocument* doc,
                                    MatchDetails* details ) const {

    bool traversedArray = false;
    size_t idxPath = 0;
    BSONElement e = doc->getFieldDottedOrArray( fieldRef, &idxPath, &traversedArray );

    if ( e.type() != Array || traversedArray ) {
        return matchesSingleElement( e );
    }

    string rest = fieldRef.dottedField( idxPath + 1 );
    StringData next;
    bool nextIsNumber = false;
    if ( rest.size() > 0 ) {
        next = fieldRef.getPart( idxPath + 1 );
        nextIsNumber = isAllDigits( next );
    }

    BSONObjIterator i( e.Obj() );
    while ( i.more() ) {
        BSONElement x = i.next();

        bool found = false;
        if ( rest.size() == 0 ) {
            found = matchesSingleElement( x );
        }
        else if ( x.type() == Object ) {
            FieldRef myFieldRef;
            myFieldRef.parse( rest );
            BSONMatchableDocument myDoc( x.Obj() );
            found = _matches( myFieldRef, &myDoc, NULL );
        }


        if ( !found && nextIsNumber && next == x.fieldName() ) {
            string reallyNext = fieldRef.dottedField( idxPath + 2 );
            if ( reallyNext.size() == 0 ) {
                found = matchesSingleElement( x );
            }
            else if ( x.isABSONObj() ) {
                // TODO: this is slow
                FieldRef myFieldRef;
                myFieldRef.parse( "x." + reallyNext );
                BSONObjBuilder b;
                b.appendAs( x, "x" );
                BSONObj temp = b.obj();
                BSONMatchableDocument myDoc( temp );
                found = _matches( myFieldRef, &myDoc, NULL );
            }
        }

        if ( found ) {
            if ( !_allHaveToMatch ) {
                if ( details && details->needRecord() ) {
                    // this block doesn't have to be inside the _allHaveToMatch handler
                    // but this matches the old semantics
                    details->setElemMatchKey( x.fieldName() );
                }
                return true;
            }
        }
        else if ( _allHaveToMatch ) {
            return false;
        }
    }

    if ( rest.size() > 0 ) {
        // we're supposed to have gone further down
        return false;
    }

    return matchesSingleElement( e );
}
Exemple #16
0
    Status ModifierPush::init(const BSONElement& modExpr) {

        //
        // field name analysis
        //

        // Break down the field name into its 'dotted' components (aka parts) and check that
        // the field is fit for updates.
        _fieldRef.parse(modExpr.fieldName());
        Status status = fieldchecker::isUpdatable(_fieldRef);
        if (! status.isOK()) {
            return status;
        }

        // If a $-positional operator was used, get the index in which it occurred
        // and ensure only one occurrence.
        size_t foundCount;
        bool foundDollar = fieldchecker::isPositional(_fieldRef, &_posDollar, &foundCount);
        if (foundDollar && foundCount > 1) {
            return Status(ErrorCodes::BadValue, "too many positional($) elements found.");
        }

        //
        // value analysis
        //

        // Are the target push values safe to store?
        BSONElement sliceElem;
        BSONElement sortElem;
        switch (modExpr.type()) {

        case Array:
            if (! modExpr.Obj().okForStorage()) {
                return Status(ErrorCodes::BadValue, "cannot use '$' or '.' as values");
            }

            if (_pushMode == PUSH_ALL) {
                _eachMode = true;
                Status status = parseEachMode(PUSH_ALL,
                                              modExpr,
                                              &_eachElem,
                                              &sliceElem,
                                              &sortElem);
                if (!status.isOK()) {
                    return status;
                }
            }
            else {
                _val = modExpr;
            }
            break;

        case Object:
            if (_pushMode == PUSH_ALL) {
                return Status(ErrorCodes::BadValue, "$pushAll requires an array of values");
            }

            // If any known clause ($each, $slice, or $sort) is present, we'd assume
            // we're using the $each variation of push and would parse accodingly.
            _eachMode = inEachMode(modExpr);
            if (_eachMode) {
                Status status = parseEachMode(PUSH_NORMAL,
                                              modExpr,
                                              &_eachElem,
                                              &sliceElem,
                                              &sortElem);
                if (!status.isOK()) {
                    return status;
                }
            }
            else {
                if (! modExpr.Obj().okForStorage()) {
                    return Status(ErrorCodes::BadValue, "cannot use '$' as values");
                }
                _val = modExpr;
            }
            break;

        default:
            if (_pushMode == PUSH_ALL) {
                return Status(ErrorCodes::BadValue, "$pushAll requires an array of values");
            }

            _val = modExpr;
            break;
        }

        // Is slice present and correct?
        if (sliceElem.type() != EOO) {
            if (_pushMode == PUSH_ALL) {
                return Status(ErrorCodes::BadValue, "cannot use $slice in $pushAll");
            }

            if (!sliceElem.isNumber()) {
                return Status(ErrorCodes::BadValue, "$slice must be a numeric value");
            }

            // If the value of slice is not fraction, even if it's a double, we allow it. The
            // reason here is that the shell will use doubles by default unless told otherwise.
            double fractional = sliceElem.numberDouble();
            if (fractional - static_cast<int64_t>(fractional) != 0) {
                return Status(ErrorCodes::BadValue, "$slice in $push cannot be fractional");
            }

            _slice = sliceElem.numberLong();
            if (_slice > 0) {
                return Status(ErrorCodes::BadValue, "$slice in $push must be zero or negative");
            }
            _slicePresent = true;
        }

        // Is sort present and correct?
        if (sortElem.type() != EOO) {
            if (_pushMode == PUSH_ALL) {
                return Status(ErrorCodes::BadValue, "cannot use $sort in $pushAll");
            }

            if (!_slicePresent) {
                return Status(ErrorCodes::BadValue, "$sort requires $slice to be present");
            }
            else if (sortElem.type() != Object) {
                return Status(ErrorCodes::BadValue, "invalid $sort clause");
            }

            BSONObj sortObj = sortElem.embeddedObject();
            if (sortObj.isEmpty()) {
                return Status(ErrorCodes::BadValue, "sort parttern is empty");
            }

            // Check if the sort pattern is sound.
            BSONObjIterator sortIter(sortObj);
            while (sortIter.more()) {

                BSONElement sortPatternElem = sortIter.next();

                // We require either <field>: 1 or -1 for asc and desc.
                if (!isPatternElement(sortPatternElem)) {
                    return Status(ErrorCodes::BadValue, "$sort elements' must be either 1 or -1");
                }

                // All fields parts must be valid.
                FieldRef sortField;
                sortField.parse(sortPatternElem.fieldName());
                if (sortField.numParts() == 0) {
                    return Status(ErrorCodes::BadValue, "$sort field cannot be empty");
                }

                for (size_t i = 0; i < sortField.numParts(); i++) {
                    if (sortField.getPart(i).size() == 0) {
                        return Status(ErrorCodes::BadValue, "empty field in dotted sort pattern");
                    }
                }
            }

            _sort = PatternElementCmp(sortElem.embeddedObject());
            _sortPresent = true;
        }

        return Status::OK();
    }
Exemple #17
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();
}
Exemple #18
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();
    }
}