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(); }
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(); }