Exemple #1
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<int32_t>(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();
}
Exemple #2
0
BSONObj FTSSpec::fixSpec( const BSONObj& spec ) {
    if ( spec["textIndexVersion"].numberInt() == TEXT_INDEX_VERSION_1 ) {
        return _fixSpecV1( spec );
    }

    map<string,int> m;

    BSONObj keyPattern;
    {
        BSONObjBuilder b;

        // Populate m and keyPattern.
        {
            bool addedFtsStuff = false;
            BSONObjIterator i( spec["key"].Obj() );
            while ( i.more() ) {
                BSONElement e = i.next();
                if ( str::equals( e.fieldName(), "_fts" ) ) {
                    uassert( 17271,
                             "expecting _fts:\"text\"",
                             INDEX_NAME == e.valuestrsafe() );
                    addedFtsStuff = true;
                    b.append( e );
                }
                else if ( str::equals( e.fieldName(), "_ftsx" ) ) {
                    uassert( 17272, "expecting _ftsx:1", e.numberInt() == 1 );
                    b.append( e );
                }
                else if ( e.type() == String && INDEX_NAME == e.valuestr() ) {

                    if ( !addedFtsStuff ) {
                        _addFTSStuff( &b );
                        addedFtsStuff = true;
                    }

                    m[e.fieldName()] = 1;
                }
                else {
                    uassert( 17273,
                             "expected value 1 or -1 for non-text key in compound index",
                             e.numberInt() == 1 || e.numberInt() == -1 );
                    b.append( e );
                }
            }
            verify( addedFtsStuff );
        }
        keyPattern = b.obj();

        // Verify that index key is in the correct format: extraBefore fields, then text
        // fields, then extraAfter fields.
        {
            BSONObjIterator i( spec["key"].Obj() );
            BSONElement e;

            // extraBefore fields
            do {
                verify( i.more() );
                e = i.next();
            } while ( INDEX_NAME != e.valuestrsafe() );

            // text fields
            bool alreadyFixed = str::equals( e.fieldName(), "_fts" );
            if ( alreadyFixed ) {
                uassert( 17288, "expected _ftsx after _fts", i.more() );
                e = i.next();
                uassert( 17274,
                         "expected _ftsx after _fts",
                         str::equals( e.fieldName(), "_ftsx" ) );
                e = i.next();
            }
            else {
                do {
                    uassert( 17289,
                             "text index with reserved fields _fts/ftsx not allowed",
                             !str::equals( e.fieldName(), "_fts" ) &&
                             !str::equals( e.fieldName(), "_ftsx" ) );
                    e = i.next();
                } while ( !e.eoo() && INDEX_NAME == e.valuestrsafe() );
            }

            // extraAfterFields
            while ( !e.eoo() ) {
                uassert( 17290,
                         "compound text index key suffix fields must have value 1",
                         e.numberInt() == 1 && !str::equals( "_ftsx", e.fieldName() ) );
                e = i.next();
            }
        }

    }

    if ( spec["weights"].type() == Object ) {
        BSONObjIterator i( spec["weights"].Obj() );
        while ( i.more() ) {
            BSONElement e = i.next();
            uassert( 17283,
                     "weight for text index needs numeric type",
                     e.isNumber() );
            m[e.fieldName()] = e.numberInt();

            // Verify weight refers to a valid field.
            if ( str::equals( e.fieldName(), "$**" ) ) {
                continue;
            }
            FieldRef keyField( e.fieldName() );
            uassert( 17294,
                     "weight cannot be on an empty field",
                     keyField.numParts() != 0 );
            for ( size_t i = 0; i < keyField.numParts(); i++ ) {
                StringData part = keyField.getPart(i);
                uassert( 17291, "weight cannot have empty path component", !part.empty() );
                uassert( 17292,
                         "weight cannot have path component with $ prefix",
                         !part.startsWith( "$" ) );
            }
        }
    }
    else if ( spec["weights"].str() == WILDCARD ) {
        m[WILDCARD] = 1;
    }
    else if ( !spec["weights"].eoo() ) {
        uasserted( 17284, "text index option 'weights' must be an object" );
    }

    BSONObj weights;
    {
        BSONObjBuilder b;
        for ( map<string,int>::iterator i = m.begin(); i != m.end(); ++i ) {
            uassert( 16674, "score for word too high",
                     i->second > 0 && i->second < MAX_WORD_WEIGHT );
            b.append( i->first, i->second );
        }
        weights = b.obj();
    }

    BSONElement default_language_elt = spec["default_language"];
    string default_language( default_language_elt.str() );
    if ( default_language_elt.eoo() ) {
        default_language = moduleDefaultLanguage;
    }
    else {
        uassert( 17263,
                 "default_language needs a string type",
                 default_language_elt.type() == String );
    }
    uassert( 17264,
             "default_language is not valid",
             FTSLanguage::make( default_language,
                                TEXT_INDEX_VERSION_2 ).getStatus().isOK() );

    BSONElement language_override_elt = spec["language_override"];
    string language_override( language_override_elt.str() );
    if ( language_override_elt.eoo() ) {
        language_override = "language";
    }
    else {
        uassert( 17136,
                 "language_override is not valid",
                 language_override_elt.type() == String
                 && validateOverride( language_override ) );
    }

    int version = -1;
    int textIndexVersion = TEXT_INDEX_VERSION_2;

    BSONObjBuilder b;
    BSONObjIterator i( spec );
    while ( i.more() ) {
        BSONElement e = i.next();
        if ( str::equals( e.fieldName(), "key" ) ) {
            b.append( "key", keyPattern );
        }
        else if ( str::equals( e.fieldName(), "weights" ) ) {
            b.append( "weights", weights );
            weights = BSONObj();
        }
        else if ( str::equals( e.fieldName(), "default_language" ) ) {
            b.append( "default_language", default_language);
            default_language = "";
        }
        else if ( str::equals( e.fieldName(), "language_override" ) ) {
            b.append( "language_override", language_override);
            language_override = "";
        }
        else if ( str::equals( e.fieldName(), "v" ) ) {
            version = e.numberInt();
        }
        else if ( str::equals( e.fieldName(), "textIndexVersion" ) ) {
            uassert( 17293,
                     "text index option 'textIndexVersion' must be a number",
                     e.isNumber() );
            textIndexVersion = e.numberInt();
            uassert( 16730,
                     str::stream() << "bad textIndexVersion: " << textIndexVersion,
                     textIndexVersion == TEXT_INDEX_VERSION_2 );
        }
        else {
            b.append( e );
        }
    }

    if ( !weights.isEmpty() )
        b.append( "weights", weights );
    if ( !default_language.empty() )
        b.append( "default_language", default_language);
    if ( !language_override.empty() )
        b.append( "language_override", language_override);

    if ( version >= 0 )
        b.append( "v", version );

    b.append( "textIndexVersion", textIndexVersion );

    return b.obj();

}
Exemple #3
0
Status validateKeyPattern(const BSONObj& key) {
    const ErrorCodes::Error code = ErrorCodes::CannotCreateIndex;

    if (key.objsize() > 2048)
        return Status(code, "Index key pattern too large.");

    if (key.isEmpty())
        return Status(code, "Index keys cannot be empty.");

    string pluginName = IndexNames::findPluginName(key);
    if (pluginName.size()) {
        if (!IndexNames::isKnownName(pluginName))
            return Status(
                code, mongoutils::str::stream() << "Unknown index plugin '" << pluginName << '\'');
    }

    BSONObjIterator it(key);
    while (it.more()) {
        BSONElement keyElement = it.next();

        if (keyElement.isNumber()) {
            double value = keyElement.number();
            if (std::isnan(value)) {
                return {code, "Values in the index key pattern cannot be NaN."};
            } else if (value == 0.0) {
                return {code, "Values in the index key pattern cannot be 0."};
            }
        } else if (keyElement.type() != String) {
            return {code,
                    str::stream() << "Values in index key pattern cannot be of type "
                                  << typeName(keyElement.type())
                                  << ". Only numbers > 0, numbers < 0, and strings are allowed."};
        }

        if (keyElement.type() == String && pluginName != keyElement.str()) {
            return Status(code, "Can't use more than one index plugin for a single index.");
        }

        // Ensure that the fields on which we are building the index are valid: a field must not
        // begin with a '$' unless it is part of a DBRef or text index, and a field path cannot
        // contain an empty field. If a field cannot be created or updated, it should not be
        // indexable.

        FieldRef keyField(keyElement.fieldName());

        const size_t numParts = keyField.numParts();
        if (numParts == 0) {
            return Status(code, "Index keys cannot be an empty field.");
        }

        // "$**" is acceptable for a text index.
        if (mongoutils::str::equals(keyElement.fieldName(), "$**") &&
            keyElement.valuestrsafe() == IndexNames::TEXT)
            continue;

        if (mongoutils::str::equals(keyElement.fieldName(), "_fts") &&
            keyElement.valuestrsafe() != IndexNames::TEXT) {
            return Status(code, "Index key contains an illegal field name: '_fts'");
        }

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

            // Check if the index key path contains an empty field.
            if (part.empty()) {
                return Status(code, "Index keys cannot contain an empty field.");
            }

            if (part[0] != '$')
                continue;

            // Check if the '$'-prefixed field is part of a DBRef: since we don't have the
            // necessary context to validate whether this is a proper DBRef, we allow index
            // creation on '$'-prefixed names that match those used in a DBRef.
            const bool mightBePartOfDbRef =
                (i != 0) && (part == "$db" || part == "$id" || part == "$ref");

            if (!mightBePartOfDbRef) {
                return Status(code,
                              "Index key contains an illegal field name: "
                              "field name starts with '$'.");
            }
        }
    }

    return Status::OK();
}
Status ModifierAddToSet::prepare(mb::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, 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();
}
    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 (_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();
    }
Exemple #6
0
StatusWith<BSONObj> validateIndexSpec(
    const BSONObj& indexSpec,
    const NamespaceString& expectedNamespace,
    const ServerGlobalParams::FeatureCompatibility& featureCompatibility) {
    bool hasKeyPatternField = false;
    bool hasNamespaceField = false;
    bool hasVersionField = false;
    bool hasCollationField = false;

    auto fieldNamesValidStatus = validateIndexSpecFieldNames(indexSpec);
    if (!fieldNamesValidStatus.isOK()) {
        return fieldNamesValidStatus;
    }

    boost::optional<IndexVersion> resolvedIndexVersion;

    for (auto&& indexSpecElem : indexSpec) {
        auto indexSpecElemFieldName = indexSpecElem.fieldNameStringData();
        if (IndexDescriptor::kKeyPatternFieldName == indexSpecElemFieldName) {
            if (indexSpecElem.type() != BSONType::Object) {
                return {ErrorCodes::TypeMismatch,
                        str::stream() << "The field '" << IndexDescriptor::kKeyPatternFieldName
                                      << "' must be an object, but got "
                                      << typeName(indexSpecElem.type())};
            }

            std::vector<StringData> keys;
            for (auto&& keyElem : indexSpecElem.Obj()) {
                auto keyElemFieldName = keyElem.fieldNameStringData();
                if (std::find(keys.begin(), keys.end(), keyElemFieldName) != keys.end()) {
                    return {ErrorCodes::BadValue,
                            str::stream() << "The field '" << keyElemFieldName
                                          << "' appears multiple times in the index key pattern "
                                          << indexSpecElem.Obj()};
                }
                keys.push_back(keyElemFieldName);
            }

            hasKeyPatternField = true;
        } else if (IndexDescriptor::kNamespaceFieldName == indexSpecElemFieldName) {
            if (indexSpecElem.type() != BSONType::String) {
                return {ErrorCodes::TypeMismatch,
                        str::stream() << "The field '" << IndexDescriptor::kNamespaceFieldName
                                      << "' must be a string, but got "
                                      << typeName(indexSpecElem.type())};
            }

            StringData ns = indexSpecElem.valueStringData();
            if (ns.empty()) {
                return {ErrorCodes::BadValue,
                        str::stream() << "The field '" << IndexDescriptor::kNamespaceFieldName
                                      << "' cannot be an empty string"};
            }

            if (ns != expectedNamespace.ns()) {
                return {ErrorCodes::BadValue,
                        str::stream() << "The value of the field '"
                                      << IndexDescriptor::kNamespaceFieldName
                                      << "' ("
                                      << ns
                                      << ") doesn't match the namespace '"
                                      << expectedNamespace.ns()
                                      << "'"};
            }

            hasNamespaceField = true;
        } else if (IndexDescriptor::kIndexVersionFieldName == indexSpecElemFieldName) {
            if (!indexSpecElem.isNumber()) {
                return {ErrorCodes::TypeMismatch,
                        str::stream() << "The field '" << IndexDescriptor::kIndexVersionFieldName
                                      << "' must be a number, but got "
                                      << typeName(indexSpecElem.type())};
            }

            auto requestedIndexVersionAsInt = representAs<int>(indexSpecElem.number());
            if (!requestedIndexVersionAsInt) {
                return {ErrorCodes::BadValue,
                        str::stream()
                            << "Index version must be representable as a 32-bit integer, but got "
                            << indexSpecElem.toString(false, false)};
            }

            const IndexVersion requestedIndexVersion =
                static_cast<IndexVersion>(*requestedIndexVersionAsInt);
            auto creationAllowedStatus = IndexDescriptor::isIndexVersionAllowedForCreation(
                requestedIndexVersion, featureCompatibility, indexSpec);
            if (!creationAllowedStatus.isOK()) {
                return creationAllowedStatus;
            }

            hasVersionField = true;
            resolvedIndexVersion = requestedIndexVersion;
        } else if (IndexDescriptor::kCollationFieldName == indexSpecElemFieldName) {
            if (indexSpecElem.type() != BSONType::Object) {
                return {ErrorCodes::TypeMismatch,
                        str::stream() << "The field '" << IndexDescriptor::kNamespaceFieldName
                                      << "' must be an object, but got "
                                      << typeName(indexSpecElem.type())};
            }

            hasCollationField = true;
        } else {
            // We can assume field name is valid at this point. Validation of fieldname is handled
            // prior to this in validateIndexSpecFieldNames().
            continue;
        }
    }

    if (!resolvedIndexVersion) {
        resolvedIndexVersion =
            IndexDescriptor::getDefaultIndexVersion(featureCompatibility.version.load());
    }

    if (!hasKeyPatternField) {
        return {ErrorCodes::FailedToParse,
                str::stream() << "The '" << IndexDescriptor::kKeyPatternFieldName
                              << "' field is a required property of an index specification"};
    }

    if (hasCollationField && *resolvedIndexVersion < IndexVersion::kV2) {
        return {ErrorCodes::CannotCreateIndex,
                str::stream() << "Invalid index specification " << indexSpec
                              << "; cannot create an index with the '"
                              << IndexDescriptor::kCollationFieldName
                              << "' option and "
                              << IndexDescriptor::kIndexVersionFieldName
                              << "="
                              << static_cast<int>(*resolvedIndexVersion)};
    }

    if (!hasNamespaceField || !hasVersionField) {
        BSONObjBuilder bob;

        if (!hasNamespaceField) {
            // We create a new index specification with the 'ns' field set as 'expectedNamespace' if
            // the field was omitted.
            bob.append(IndexDescriptor::kNamespaceFieldName, expectedNamespace.ns());
        }

        if (!hasVersionField) {
            // We create a new index specification with the 'v' field set as 'defaultIndexVersion'
            // if the field was omitted.
            bob.append(IndexDescriptor::kIndexVersionFieldName,
                       static_cast<int>(*resolvedIndexVersion));
        }

        bob.appendElements(indexSpec);
        return bob.obj();
    }

    return indexSpec;
}
Exemple #7
0
Status ModifierPull::prepare(mb::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;

    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();
}
void BSONComparatorInterfaceBase<T>::hashCombineBSONElement(
    size_t& hash,
    BSONElement elemToHash,
    ComparisonRulesSet rules,
    const StringData::ComparatorInterface* stringComparator) {
    boost::hash_combine(hash, elemToHash.canonicalType());

    const StringData fieldName = elemToHash.fieldNameStringData();
    if ((rules & ComparisonRules::kConsiderFieldName) && !fieldName.empty()) {
        SimpleStringDataComparator::kInstance.hash_combine(hash, fieldName);
    }

    switch (elemToHash.type()) {
        case mongo::EOO:
        case mongo::Undefined:
        case mongo::jstNULL:
        case mongo::MaxKey:
        case mongo::MinKey:
            // These are valueless types
            break;

        case mongo::Bool:
            boost::hash_combine(hash, elemToHash.boolean());
            break;

        case mongo::bsonTimestamp:
            boost::hash_combine(hash, elemToHash.timestamp().asULL());
            break;

        case mongo::Date:
            boost::hash_combine(hash, elemToHash.date().asInt64());
            break;

        case mongo::NumberDecimal: {
            const Decimal128 dcml = elemToHash.numberDecimal();
            if (dcml.toAbs().isGreater(Decimal128(std::numeric_limits<double>::max(),
                                                  Decimal128::kRoundTo34Digits,
                                                  Decimal128::kRoundTowardZero)) &&
                !dcml.isInfinite() && !dcml.isNaN()) {
                // Normalize our decimal to force equivalent decimals
                // in the same cohort to hash to the same value
                Decimal128 dcmlNorm(dcml.normalize());
                boost::hash_combine(hash, dcmlNorm.getValue().low64);
                boost::hash_combine(hash, dcmlNorm.getValue().high64);
                break;
            }
            // Else, fall through and convert the decimal to a double and hash.
            // At this point the decimal fits into the range of doubles, is infinity, or is NaN,
            // which doubles have a cheaper representation for.
        }
        case mongo::NumberDouble:
        case mongo::NumberLong:
        case mongo::NumberInt: {
            // This converts all numbers to doubles, which ignores the low-order bits of
            // NumberLongs > 2**53 and precise decimal numbers without double representations,
            // but that is ok since the hash will still be the same for equal numbers and is
            // still likely to be different for different numbers. (Note: this issue only
            // applies for decimals when they are outside of the valid double range. See
            // the above case.)
            // SERVER-16851
            const double dbl = elemToHash.numberDouble();
            if (std::isnan(dbl)) {
                boost::hash_combine(hash, std::numeric_limits<double>::quiet_NaN());
            } else {
                boost::hash_combine(hash, dbl);
            }
            break;
        }

        case mongo::jstOID:
            elemToHash.__oid().hash_combine(hash);
            break;

        case mongo::String: {
            if (stringComparator) {
                stringComparator->hash_combine(hash, elemToHash.valueStringData());
            } else {
                SimpleStringDataComparator::kInstance.hash_combine(hash,
                                                                   elemToHash.valueStringData());
            }
            break;
        }

        case mongo::Code:
        case mongo::Symbol:
            SimpleStringDataComparator::kInstance.hash_combine(hash, elemToHash.valueStringData());
            break;

        case mongo::Object:
        case mongo::Array:
            hashCombineBSONObj(hash,
                               elemToHash.embeddedObject(),
                               rules | ComparisonRules::kConsiderFieldName,
                               stringComparator);
            break;

        case mongo::DBRef:
        case mongo::BinData:
            // All bytes of the value are required to be identical.
            SimpleStringDataComparator::kInstance.hash_combine(
                hash, StringData(elemToHash.value(), elemToHash.valuesize()));
            break;

        case mongo::RegEx:
            SimpleStringDataComparator::kInstance.hash_combine(hash, elemToHash.regex());
            SimpleStringDataComparator::kInstance.hash_combine(hash, elemToHash.regexFlags());
            break;

        case mongo::CodeWScope: {
            SimpleStringDataComparator::kInstance.hash_combine(
                hash, StringData(elemToHash.codeWScopeCode(), elemToHash.codeWScopeCodeLen()));
            hashCombineBSONObj(hash,
                               elemToHash.codeWScopeObject(),
                               rules | ComparisonRules::kConsiderFieldName,
                               &SimpleStringDataComparator::kInstance);
            break;
        }
    }
}
Exemple #9
0
    bool run(OperationContext* txn,
             const string& dbname,
             BSONObj& cmdObj,
             int,
             string& errmsg,
             BSONObjBuilder& result) {
        BSONElement first = cmdObj.firstElement();
        uassert(28528,
                str::stream() << "Argument to listIndexes must be of type String, not "
                              << typeName(first.type()),
                first.type() == String);
        StringData collectionName = first.valueStringData();
        uassert(28529,
                str::stream() << "Argument to listIndexes must be a collection name, "
                              << "not the empty string",
                !collectionName.empty());
        const NamespaceString ns(dbname, collectionName);

        const long long defaultBatchSize = std::numeric_limits<long long>::max();
        long long batchSize;
        Status parseCursorStatus = parseCommandCursorOptions(cmdObj, defaultBatchSize, &batchSize);
        if (!parseCursorStatus.isOK()) {
            return appendCommandStatus(result, parseCursorStatus);
        }

        AutoGetCollectionForRead autoColl(txn, ns);
        if (!autoColl.getDb()) {
            return appendCommandStatus(result,
                                       Status(ErrorCodes::NamespaceNotFound, "no database"));
        }

        const Collection* collection = autoColl.getCollection();
        if (!collection) {
            return appendCommandStatus(result,
                                       Status(ErrorCodes::NamespaceNotFound, "no collection"));
        }

        const CollectionCatalogEntry* cce = collection->getCatalogEntry();
        invariant(cce);

        vector<string> indexNames;
        MONGO_WRITE_CONFLICT_RETRY_LOOP_BEGIN {
            indexNames.clear();
            cce->getAllIndexes(txn, &indexNames);
        }
        MONGO_WRITE_CONFLICT_RETRY_LOOP_END(txn, "listIndexes", ns.ns());

        auto ws = make_unique<WorkingSet>();
        auto root = make_unique<QueuedDataStage>(txn, ws.get());

        for (size_t i = 0; i < indexNames.size(); i++) {
            BSONObj indexSpec;
            MONGO_WRITE_CONFLICT_RETRY_LOOP_BEGIN {
                indexSpec = cce->getIndexSpec(txn, indexNames[i]);
            }
            MONGO_WRITE_CONFLICT_RETRY_LOOP_END(txn, "listIndexes", ns.ns());

            WorkingSetID id = ws->allocate();
            WorkingSetMember* member = ws->get(id);
            member->keyData.clear();
            member->loc = RecordId();
            member->obj = Snapshotted<BSONObj>(SnapshotId(), indexSpec.getOwned());
            member->transitionToOwnedObj();
            root->pushBack(id);
        }

        std::string cursorNamespace = str::stream() << dbname << ".$cmd." << name << "."
                                                    << ns.coll();
        dassert(NamespaceString(cursorNamespace).isValid());
        dassert(NamespaceString(cursorNamespace).isListIndexesCursorNS());
        dassert(ns == NamespaceString(cursorNamespace).getTargetNSForListIndexes());

        auto statusWithPlanExecutor = PlanExecutor::make(
            txn, std::move(ws), std::move(root), cursorNamespace, PlanExecutor::YIELD_MANUAL);
        if (!statusWithPlanExecutor.isOK()) {
            return appendCommandStatus(result, statusWithPlanExecutor.getStatus());
        }
        unique_ptr<PlanExecutor> exec = std::move(statusWithPlanExecutor.getValue());

        BSONArrayBuilder firstBatch;

        const int byteLimit = FindCommon::kMaxBytesToReturnToClientAtOnce;
        for (long long objCount = 0; objCount < batchSize && firstBatch.len() < byteLimit;
             objCount++) {
            BSONObj next;
            PlanExecutor::ExecState state = exec->getNext(&next, NULL);
            if (state == PlanExecutor::IS_EOF) {
                break;
            }
            invariant(state == PlanExecutor::ADVANCED);
            firstBatch.append(next);
        }

        CursorId cursorId = 0LL;
        if (!exec->isEOF()) {
            exec->saveState();
            exec->detachFromOperationContext();
            ClientCursor* cursor =
                new ClientCursor(CursorManager::getGlobalCursorManager(),
                                 exec.release(),
                                 cursorNamespace,
                                 txn->recoveryUnit()->isReadingFromMajorityCommittedSnapshot());
            cursorId = cursor->cursorid();
        }

        appendCursorResponseObject(cursorId, cursorNamespace, firstBatch.arr(), &result);

        return true;
    }
Exemple #10
0
    Status ModifierRename::prepare(mutablebson::Element root,
                                   const 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();
    }
Exemple #11
0
StatusWith<BSONObj> validateIndexSpec(const BSONObj& indexSpec,
                                      const NamespaceString& expectedNamespace) {
    bool hasKeyPatternField = false;
    bool hasNamespaceField = false;

    for (auto&& indexSpecElem : indexSpec) {
        auto indexSpecElemFieldName = indexSpecElem.fieldNameStringData();
        if (kKeyPatternFieldName == indexSpecElemFieldName) {
            if (indexSpecElem.type() != BSONType::Object) {
                return {ErrorCodes::TypeMismatch,
                        str::stream() << "The field '" << kKeyPatternFieldName
                                      << "' must be an object, but got "
                                      << typeName(indexSpecElem.type())};
            }

            std::vector<StringData> keys;
            for (auto&& keyElem : indexSpecElem.Obj()) {
                auto keyElemFieldName = keyElem.fieldNameStringData();
                if (std::find(keys.begin(), keys.end(), keyElemFieldName) != keys.end()) {
                    return {ErrorCodes::BadValue,
                            str::stream() << "The field '" << keyElemFieldName
                                          << "' appears multiple times in the index key pattern "
                                          << indexSpecElem.Obj()};
                }
                keys.push_back(keyElemFieldName);
            }

            hasKeyPatternField = true;
        } else if (kNamespaceFieldName == indexSpecElemFieldName) {
            if (indexSpecElem.type() != BSONType::String) {
                return {ErrorCodes::TypeMismatch,
                        str::stream() << "The field '" << kNamespaceFieldName
                                      << "' must be a string, but got "
                                      << typeName(indexSpecElem.type())};
            }

            StringData ns = indexSpecElem.valueStringData();
            if (ns.empty()) {
                return {ErrorCodes::BadValue,
                        str::stream() << "The field '" << kNamespaceFieldName
                                      << "' cannot be an empty string"};
            }

            if (ns != expectedNamespace.ns()) {
                return {ErrorCodes::BadValue,
                        str::stream() << "The value of the field '" << kNamespaceFieldName << "' ("
                                      << ns
                                      << ") doesn't match the namespace '"
                                      << expectedNamespace.ns()
                                      << "'"};
            }

            hasNamespaceField = true;
        } else if (kVersionFieldName == indexSpecElemFieldName) {
            if (!indexSpecElem.isNumber()) {
                return {ErrorCodes::TypeMismatch,
                        str::stream() << "The field '" << kVersionFieldName
                                      << "' must be a number, but got "
                                      << typeName(indexSpecElem.type())};
            }

            if (kIndexVersionV0 == indexSpecElem.numberInt()) {
                return {ErrorCodes::CannotCreateIndex,
                        str::stream() << "Invalid index specification " << indexSpec
                                      << "; cannot create an index with "
                                      << kVersionFieldName
                                      << "="
                                      << kIndexVersionV0};
            }
        } else {
            // TODO SERVER-769: Validate index options specified in the "createIndexes" command.
            continue;
        }
    }

    if (!hasKeyPatternField) {
        return {ErrorCodes::FailedToParse,
                str::stream() << "The '" << kKeyPatternFieldName
                              << "' field is a required property of an index specification"};
    }

    if (!hasNamespaceField) {
        // We create a new index specification with the 'ns' field set as 'expectedNamespace' if the
        // field was omitted.
        BSONObjBuilder bob;
        bob.append(kNamespaceFieldName, expectedNamespace.ns());
        bob.appendElements(indexSpec);
        return bob.obj();
    }

    return indexSpec;
}
Exemple #12
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;
}
Exemple #13
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();
    }
Exemple #14
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();
}
Exemple #15
0
Status HostAndPort::initialize(StringData s) {
    size_t colonPos = s.rfind(':');
    StringData hostPart = s.substr(0, colonPos);

    // handle ipv6 hostPart (which we require to be wrapped in []s)
    const size_t openBracketPos = s.find('[');
    const size_t closeBracketPos = s.find(']');
    if (openBracketPos != std::string::npos) {
        if (openBracketPos != 0) {
            return Status(ErrorCodes::FailedToParse,
                          str::stream() << "'[' present, but not first character in "
                                        << s.toString());
        }
        if (closeBracketPos == std::string::npos) {
            return Status(ErrorCodes::FailedToParse,
                          str::stream() << "ipv6 address is missing closing ']' in hostname in "
                                        << s.toString());
        }

        hostPart = s.substr(openBracketPos + 1, closeBracketPos - openBracketPos - 1);
        // prevent accidental assignment of port to the value of the final portion of hostPart
        if (colonPos < closeBracketPos) {
            colonPos = std::string::npos;
        } else if (colonPos != closeBracketPos + 1) {
            return Status(ErrorCodes::FailedToParse,
                          str::stream() << "Extraneous characters between ']' and pre-port ':'"
                                        << " in "
                                        << s.toString());
        }
    } else if (closeBracketPos != std::string::npos) {
        return Status(ErrorCodes::FailedToParse,
                      str::stream() << "']' present without '[' in " << s.toString());
    } else if (s.find(':') != colonPos) {
        return Status(ErrorCodes::FailedToParse,
                      str::stream() << "More than one ':' detected. If this is an ipv6 address,"
                                    << " it needs to be surrounded by '[' and ']'; "
                                    << s.toString());
    }

    if (hostPart.empty()) {
        return Status(ErrorCodes::FailedToParse,
                      str::stream() << "Empty host component parsing HostAndPort from \""
                                    << escape(s.toString())
                                    << "\"");
    }

    int port;
    if (colonPos != std::string::npos) {
        const StringData portPart = s.substr(colonPos + 1);
        Status status = parseNumberFromStringWithBase(portPart, 10, &port);
        if (!status.isOK()) {
            return status;
        }
        if (port <= 0) {
            return Status(ErrorCodes::FailedToParse,
                          str::stream() << "Port number " << port
                                        << " out of range parsing HostAndPort from \""
                                        << escape(s.toString())
                                        << "\"");
        }
    } else {
        port = -1;
    }
    _host = hostPart.toString();
    _port = port;
    return Status::OK();
}
Exemple #16
0
Status validateKeyPattern(const BSONObj& key, IndexDescriptor::IndexVersion indexVersion) {
    const ErrorCodes::Error code = ErrorCodes::CannotCreateIndex;

    if (key.objsize() > 2048)
        return Status(code, "Index key pattern too large.");

    if (key.isEmpty())
        return Status(code, "Index keys cannot be empty.");

    string pluginName = IndexNames::findPluginName(key);
    if (pluginName.size()) {
        if (!IndexNames::isKnownName(pluginName))
            return Status(
                code, mongoutils::str::stream() << "Unknown index plugin '" << pluginName << '\'');
    }

    BSONObjIterator it(key);
    while (it.more()) {
        BSONElement keyElement = it.next();

        switch (indexVersion) {
            case IndexVersion::kV0:
            case IndexVersion::kV1: {
                if (keyElement.type() == BSONType::Object || keyElement.type() == BSONType::Array) {
                    return {code,
                            str::stream() << "Values in index key pattern cannot be of type "
                                          << typeName(keyElement.type())
                                          << " for index version v:"
                                          << static_cast<int>(indexVersion)};
                }

                if (pluginName == IndexNames::ALLPATHS) {
                    return {code,
                            str::stream() << "'" << pluginName
                                          << "' index plugin is not allowed with index version v:"
                                          << static_cast<int>(indexVersion)};
                }
                break;
            }
            case IndexVersion::kV2: {
                if (keyElement.isNumber()) {
                    double value = keyElement.number();
                    if (std::isnan(value)) {
                        return {code, "Values in the index key pattern cannot be NaN."};
                    } else if (value == 0.0) {
                        return {code, "Values in the index key pattern cannot be 0."};
                    }
                } else if (keyElement.type() != BSONType::String) {
                    return {code,
                            str::stream()
                                << "Values in v:2 index key pattern cannot be of type "
                                << typeName(keyElement.type())
                                << ". Only numbers > 0, numbers < 0, and strings are allowed."};
                }

                break;
            }
            default:
                MONGO_UNREACHABLE;
        }

        if (keyElement.type() == String && pluginName != keyElement.str()) {
            return Status(code, "Can't use more than one index plugin for a single index.");
        }

        // Check if the all paths index is compounded. If it is the key is invalid because
        // compounded all paths indexes are disallowed.
        if (pluginName == IndexNames::ALLPATHS && key.nFields() != 1) {
            return Status(code, "all paths indexes do not allow compounding");
        }

        // Ensure that the fields on which we are building the index are valid: a field must not
        // begin with a '$' unless it is part of an allPaths, DBRef or text index, and a field path
        // cannot contain an empty field. If a field cannot be created or updated, it should not be
        // indexable.

        FieldRef keyField(keyElement.fieldName());

        const size_t numParts = keyField.numParts();
        if (numParts == 0) {
            return Status(code, "Index keys cannot be an empty field.");
        }

        // "$**" is acceptable for a text index or all paths index.
        if (mongoutils::str::equals(keyElement.fieldName(), "$**") &&
            ((keyElement.isNumber()) || (keyElement.valuestrsafe() == IndexNames::TEXT)))
            continue;

        if (mongoutils::str::equals(keyElement.fieldName(), "_fts") &&
            keyElement.valuestrsafe() != IndexNames::TEXT) {
            return Status(code, "Index key contains an illegal field name: '_fts'");
        }

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

            // Check if the index key path contains an empty field.
            if (part.empty()) {
                return Status(code, "Index keys cannot contain an empty field.");
            }

            if (part[0] != '$')
                continue;

            // Check if the '$'-prefixed field is part of a DBRef: since we don't have the
            // necessary context to validate whether this is a proper DBRef, we allow index
            // creation on '$'-prefixed names that match those used in a DBRef.
            const bool mightBePartOfDbRef =
                (i != 0) && (part == "$db" || part == "$id" || part == "$ref");

            const bool isPartOfAllPaths =
                (i == numParts - 1) && (part == "$**") && (pluginName == IndexNames::ALLPATHS);

            if (!mightBePartOfDbRef && !isPartOfAllPaths) {
                return Status(code,
                              "Index key contains an illegal field name: "
                              "field name starts with '$'.");
            }
        }
    }

    return Status::OK();
}
Exemple #17
0
StatusWithMatchExpression JSONSchemaParser::_parse(StringData path, BSONObj schema) {
    // Map from JSON Schema keyword to the corresponding element from 'schema', or to an empty
    // BSONElement if the JSON Schema keyword is not specified.
    StringMap<BSONElement> keywordMap{{kSchemaTypeKeyword, {}},
                                      {kSchemaPropertiesKeyword, {}},
                                      {kSchemaMaximumKeyword, {}},
                                      {kSchemaMinimumKeyword, {}},
                                      {kSchemaExclusiveMaximumKeyword, {}},
                                      {kSchemaExclusiveMinimumKeyword, {}},
                                      {kSchemaMaxLengthKeyword, {}},
                                      {kSchemaMinLengthKeyword, {}},
                                      {kSchemaPatternKeyword, {}},
                                      {kSchemaMultipleOfKeyword, {}}};

    for (auto&& elt : schema) {
        auto it = keywordMap.find(elt.fieldNameStringData());
        if (it == keywordMap.end()) {
            return Status(ErrorCodes::FailedToParse,
                          str::stream() << "Unknown $jsonSchema keyword: "
                                        << elt.fieldNameStringData());
        }

        if (it->second) {
            return Status(ErrorCodes::FailedToParse,
                          str::stream() << "Duplicate $jsonSchema keyword: "
                                        << elt.fieldNameStringData());
        }

        keywordMap[elt.fieldNameStringData()] = elt;
    }

    auto typeExpr = parseType(path, keywordMap[kSchemaTypeKeyword]);
    if (!typeExpr.isOK()) {
        return typeExpr.getStatus();
    }

    auto andExpr = stdx::make_unique<AndMatchExpression>();

    if (auto propertiesElt = keywordMap[kSchemaPropertiesKeyword]) {
        auto propertiesExpr = _parseProperties(path, propertiesElt, typeExpr.getValue().get());
        if (!propertiesExpr.isOK()) {
            return propertiesExpr;
        }
        andExpr->add(propertiesExpr.getValue().release());
    }

    if (auto maximumElt = keywordMap[kSchemaMaximumKeyword]) {
        bool isExclusiveMaximum = false;
        if (auto exclusiveMaximumElt = keywordMap[kSchemaExclusiveMaximumKeyword]) {
            if (!exclusiveMaximumElt.isBoolean()) {
                return {Status(ErrorCodes::TypeMismatch,
                               str::stream() << "$jsonSchema keyword '"
                                             << kSchemaExclusiveMaximumKeyword
                                             << "' must be a boolean")};
            } else {
                isExclusiveMaximum = exclusiveMaximumElt.boolean();
            }
        }
        auto maxExpr =
            parseMaximum(path, maximumElt, typeExpr.getValue().get(), isExclusiveMaximum);
        if (!maxExpr.isOK()) {
            return maxExpr;
        }
        andExpr->add(maxExpr.getValue().release());
    } else if (keywordMap[kSchemaExclusiveMaximumKeyword]) {
        // If "exclusiveMaximum" is present, "maximum" must also be present.
        return {Status(ErrorCodes::FailedToParse,
                       str::stream() << "$jsonSchema keyword '" << kSchemaMaximumKeyword
                                     << "' must be a present if "
                                     << kSchemaExclusiveMaximumKeyword
                                     << " is present")};
    }

    if (auto minimumElt = keywordMap[kSchemaMinimumKeyword]) {
        bool isExclusiveMinimum = false;
        if (auto exclusiveMinimumElt = keywordMap[kSchemaExclusiveMinimumKeyword]) {
            if (!exclusiveMinimumElt.isBoolean()) {
                return {Status(ErrorCodes::TypeMismatch,
                               str::stream() << "$jsonSchema keyword '"
                                             << kSchemaExclusiveMinimumKeyword
                                             << "' must be a boolean")};
            } else {
                isExclusiveMinimum = exclusiveMinimumElt.boolean();
            }
        }
        auto minExpr =
            parseMinimum(path, minimumElt, typeExpr.getValue().get(), isExclusiveMinimum);
        if (!minExpr.isOK()) {
            return minExpr;
        }
        andExpr->add(minExpr.getValue().release());
    } else if (keywordMap[kSchemaExclusiveMinimumKeyword]) {
        // If "exclusiveMinimum" is present, "minimum" must also be present.
        return {Status(ErrorCodes::FailedToParse,
                       str::stream() << "$jsonSchema keyword '" << kSchemaMinimumKeyword
                                     << "' must be a present if "
                                     << kSchemaExclusiveMinimumKeyword
                                     << " is present")};
    }

    if (auto maxLengthElt = keywordMap[kSchemaMaxLengthKeyword]) {
        auto maxLengthExpr = parseStrLength<InternalSchemaMaxLengthMatchExpression>(
            path, maxLengthElt, typeExpr.getValue().get(), kSchemaMaxLengthKeyword);
        if (!maxLengthExpr.isOK()) {
            return maxLengthExpr;
        }
        andExpr->add(maxLengthExpr.getValue().release());
    }

    if (auto minLengthElt = keywordMap[kSchemaMinLengthKeyword]) {
        auto minLengthExpr = parseStrLength<InternalSchemaMinLengthMatchExpression>(
            path, minLengthElt, typeExpr.getValue().get(), kSchemaMinLengthKeyword);
        if (!minLengthExpr.isOK()) {
            return minLengthExpr;
        }
        andExpr->add(minLengthExpr.getValue().release());
    }

    if (auto patternElt = keywordMap[kSchemaPatternKeyword]) {
        auto patternExpr = parsePattern(path, patternElt, typeExpr.getValue().get());
        if (!patternExpr.isOK()) {
            return patternExpr;
        }
        andExpr->add(patternExpr.getValue().release());
    }
    if (auto multipleOfElt = keywordMap[kSchemaMultipleOfKeyword]) {
        auto multipleOfExpr = parseMultipleOf(path, multipleOfElt, typeExpr.getValue().get());
        if (!multipleOfExpr.isOK()) {
            return multipleOfExpr;
        }
        andExpr->add(multipleOfExpr.getValue().release());
    }

    if (path.empty() && typeExpr.getValue() &&
        typeExpr.getValue()->getBSONType() != BSONType::Object) {
        // This is a top-level schema which requires that the type is something other than
        // "object". Since we only know how to store objects, this schema matches nothing.
        return {stdx::make_unique<AlwaysFalseMatchExpression>()};
    }

    if (!path.empty() && typeExpr.getValue()) {
        andExpr->add(makeTypeRestriction(std::move(typeExpr.getValue())).release());
    }
    return {std::move(andExpr)};
}