Esempio n. 1
0
StatusWith<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 (e.fieldNameStringData() == "_fts") {
                    if (INDEX_NAME != e.valuestrsafe()) {
                        return {ErrorCodes::CannotCreateIndex, "expecting _fts:\"text\""};
                    }
                    addedFtsStuff = true;
                    b.append(e);
                } else if (e.fieldNameStringData() == "_ftsx") {
                    if (e.numberInt() != 1) {
                        return {ErrorCodes::CannotCreateIndex, "expecting _ftsx:1"};
                    }
                    b.append(e);
                } else if (e.type() == String && INDEX_NAME == e.valuestr()) {
                    if (!addedFtsStuff) {
                        _addFTSStuff(&b);
                        addedFtsStuff = true;
                    }

                    m[e.fieldName()] = 1;
                } else {
                    if (e.numberInt() != 1 && e.numberInt() != -1) {
                        return {ErrorCodes::CannotCreateIndex,
                                "expected value 1 or -1 for non-text key in compound index"};
                    }
                    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());
            verify(i.more());
            BSONElement e = i.next();

            // extraBefore fields
            while (String != e.type()) {
                Status notReservedStatus = verifyFieldNameNotReserved(e.fieldNameStringData());
                if (!notReservedStatus.isOK()) {
                    return notReservedStatus;
                }

                if (!i.more()) {
                    return {ErrorCodes::CannotCreateIndex,
                            "expected additional fields in text index key pattern"};
                }

                e = i.next();
            }

            // text fields
            bool alreadyFixed = (e.fieldNameStringData() == "_fts");
            if (alreadyFixed) {
                if (!i.more()) {
                    return {ErrorCodes::CannotCreateIndex, "expected _ftsx after _fts"};
                }
                e = i.next();
                if (e.fieldNameStringData() != "_ftsx") {
                    return {ErrorCodes::CannotCreateIndex, "expected _ftsx after _fts"};
                }
                e = i.next();
            } else {
                do {
                    Status notReservedStatus = verifyFieldNameNotReserved(e.fieldNameStringData());
                    if (!notReservedStatus.isOK()) {
                        return notReservedStatus;
                    }
                    e = i.next();
                } while (!e.eoo() && e.type() == String);
            }

            // extraAfterFields
            while (!e.eoo()) {
                if (e.type() == BSONType::String) {
                    return {ErrorCodes::CannotCreateIndex,
                            "'text' fields in index must all be adjacent"};
                }
                Status notReservedStatus = verifyFieldNameNotReserved(e.fieldNameStringData());
                if (!notReservedStatus.isOK()) {
                    return notReservedStatus;
                }
                e = i.next();
            }
        }
    }

    if (spec["weights"].type() == Object) {
        BSONObjIterator i(spec["weights"].Obj());
        while (i.more()) {
            BSONElement e = i.next();
            if (!e.isNumber()) {
                return {ErrorCodes::CannotCreateIndex, "weight for text index needs numeric type"};
            }
            m[e.fieldName()] = e.numberInt();
        }
    } else if (spec["weights"].str() == WILDCARD) {
        m[WILDCARD] = 1;
    } else if (!spec["weights"].eoo()) {
        return {ErrorCodes::CannotCreateIndex, "text index option 'weights' must be an object"};
    }

    if (m.empty()) {
        return {ErrorCodes::CannotCreateIndex,
                "text index option 'weights' must specify fields or the wildcard"};
    }

    BSONObj weights;
    {
        BSONObjBuilder b;
        for (map<string, int>::iterator i = m.begin(); i != m.end(); ++i) {
            if (i->second <= 0 || i->second >= MAX_WORD_WEIGHT) {
                return {ErrorCodes::CannotCreateIndex,
                        str::stream() << "text index weight must be in the exclusive interval (0,"
                                      << MAX_WORD_WEIGHT
                                      << ") but found: "
                                      << i->second};
            }

            // Verify weight refers to a valid field.
            if (i->first != "$**") {
                FieldRef keyField(i->first);
                if (keyField.numParts() == 0) {
                    return {ErrorCodes::CannotCreateIndex, "weight cannot be on an empty field"};
                }

                for (size_t partNum = 0; partNum < keyField.numParts(); partNum++) {
                    StringData part = keyField.getPart(partNum);
                    if (part.empty()) {
                        return {ErrorCodes::CannotCreateIndex,
                                "weight cannot have empty path component"};
                    }

                    if (part.startsWith("$")) {
                        return {ErrorCodes::CannotCreateIndex,
                                "weight cannot have path component with $ prefix"};
                    }
                }
            }

            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 if (default_language_elt.type() != BSONType::String) {
        return {ErrorCodes::CannotCreateIndex, "default_language needs a string type"};
    }

    if (!FTSLanguage::make(default_language, TEXT_INDEX_VERSION_3).getStatus().isOK()) {
        return {ErrorCodes::CannotCreateIndex, "default_language is not valid"};
    }

    BSONElement language_override_elt = spec["language_override"];
    string language_override(language_override_elt.str());
    if (language_override_elt.eoo()) {
        language_override = "language";
    } else if (language_override_elt.type() != BSONType::String) {
        return {ErrorCodes::CannotCreateIndex, "language_override must be a string"};
    } else if (!validateOverride(language_override)) {
        return {ErrorCodes::CannotCreateIndex, "language_override is not valid"};
    }

    int version = -1;
    int textIndexVersion = TEXT_INDEX_VERSION_3;  // default text index version

    BSONObjBuilder b;
    BSONObjIterator i(spec);
    while (i.more()) {
        BSONElement e = i.next();
        StringData fieldName = e.fieldNameStringData();
        if (fieldName == "key") {
            b.append("key", keyPattern);
        } else if (fieldName == "weights") {
            b.append("weights", weights);
            weights = BSONObj();
        } else if (fieldName == "default_language") {
            b.append("default_language", default_language);
            default_language = "";
        } else if (fieldName == "language_override") {
            b.append("language_override", language_override);
            language_override = "";
        } else if (fieldName == "v") {
            version = e.numberInt();
        } else if (fieldName == "textIndexVersion") {
            if (!e.isNumber()) {
                return {ErrorCodes::CannotCreateIndex,
                        "text index option 'textIndexVersion' must be a number"};
            }

            textIndexVersion = e.numberInt();
            if (textIndexVersion != TEXT_INDEX_VERSION_2 &&
                textIndexVersion != TEXT_INDEX_VERSION_3) {
                return {ErrorCodes::CannotCreateIndex,
                        str::stream() << "bad textIndexVersion: " << textIndexVersion};
            }
        } 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();
}
Esempio n. 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();

}