summaryrefslogtreecommitdiff
path: root/src/mongo/db
diff options
context:
space:
mode:
authorJason Rassi <rassi@10gen.com>2013-12-06 00:30:36 -0500
committerJason Rassi <rassi@10gen.com>2013-12-06 13:42:54 -0500
commited086215c93b69356147c4c27cb5b2de0cd33267 (patch)
treedb1c966dca80b717fc7f80a6bcd3c01d8491c9d5 /src/mongo/db
parentae0a5132d127da644dbfbe54ce2e152553c4ce8d (diff)
downloadmongo-ed086215c93b69356147c4c27cb5b2de0cd33267.tar.gz
SERVER-10857 Validate various text index spec options
Implements validation for text spec options: - key - language_override - weights - textIndexVersion
Diffstat (limited to 'src/mongo/db')
-rw-r--r--src/mongo/db/fts/SConscript2
-rw-r--r--src/mongo/db/fts/fts_spec.cpp142
-rw-r--r--src/mongo/db/fts/fts_spec_test.cpp137
3 files changed, 237 insertions, 44 deletions
diff --git a/src/mongo/db/fts/SConscript b/src/mongo/db/fts/SConscript
index c508be06af8..b0b13a443c1 100644
--- a/src/mongo/db/fts/SConscript
+++ b/src/mongo/db/fts/SConscript
@@ -36,7 +36,7 @@ env.Library('base', [
'stop_words_list.cpp',
'tokenizer.cpp',
], LIBDEPS=["$BUILD_DIR/mongo/base/base",
- "$BUILD_DIR/mongo/bson",
+ "$BUILD_DIR/mongo/db/common",
"$BUILD_DIR/mongo/platform/platform",
"$BUILD_DIR/third_party/shim_stemmer"
])
diff --git a/src/mongo/db/fts/fts_spec.cpp b/src/mongo/db/fts/fts_spec.cpp
index 69a6ea05973..baad32dc67c 100644
--- a/src/mongo/db/fts/fts_spec.cpp
+++ b/src/mongo/db/fts/fts_spec.cpp
@@ -30,6 +30,7 @@
#include "mongo/pch.h"
+#include "mongo/db/field_ref.h"
#include "mongo/db/fts/fts_spec.h"
#include "mongo/db/fts/fts_util.h"
#include "mongo/util/mongoutils/str.h"
@@ -48,6 +49,15 @@ namespace mongo {
namespace {
// Default language. Used for new indexes.
const std::string moduleDefaultLanguage( "english" );
+
+ /** Validate the given language override string. */
+ bool validateOverride( const string& override ) {
+ // The override field can't be empty, can't be prefixed with a dollar sign, and
+ // can't contain a dot.
+ return !override.empty() &&
+ override[0] != '$' &&
+ override.find('.') == std::string::npos;
+ }
}
FTSSpec::FTSSpec( const BSONObj& indexInfo ) {
@@ -58,8 +68,7 @@ namespace mongo {
verify( status.isOK() );
_languageOverrideField = indexInfo["language_override"].valuestrsafe();
- if ( _languageOverrideField.size() == 0 )
- _languageOverrideField = "language";
+ verify( validateOverride( _languageOverrideField ) );
_wildcard = false;
@@ -329,46 +338,120 @@ namespace mongo {
BSONObj keyPattern;
{
BSONObjBuilder b;
- bool addedFtsStuff = false;
-
- BSONObjIterator i( spec["key"].Obj() );
- while ( i.more() ) {
- BSONElement e = i.next();
- if ( str::equals( e.fieldName(), "_fts" ) ||
- str::equals( e.fieldName(), "_ftsx" ) ) {
- addedFtsStuff = true;
- b.append( e );
- }
- else if ( e.type() == String && INDEX_NAME == e.valuestr() ) {
- if ( !addedFtsStuff ) {
- _addFTSStuff( &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();
- m[e.fieldName()] = 1;
+ // 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 {
- b.append( e );
+ 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() );
}
- }
- if ( !addedFtsStuff )
- _addFTSStuff( &b );
+ // 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();
+ }
+ }
- keyPattern = b.obj();
}
- if ( spec["weights"].isABSONObj() ) {
+ 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;
+ keyField.parse( 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;
{
@@ -395,9 +478,17 @@ namespace mongo {
"default_language is not valid",
FTSLanguage::makeFTSLanguage( default_language ).getStatus().isOK() );
- string language_override(spec.getStringField("language_override"));
- if ( language_override.empty() )
+ 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 = 2;
@@ -425,6 +516,9 @@ namespace mongo {
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,
diff --git a/src/mongo/db/fts/fts_spec_test.cpp b/src/mongo/db/fts/fts_spec_test.cpp
index 99a746d9137..3df7ee1d301 100644
--- a/src/mongo/db/fts/fts_spec_test.cpp
+++ b/src/mongo/db/fts/fts_spec_test.cpp
@@ -37,37 +37,136 @@
namespace mongo {
namespace fts {
- TEST( FTSSpec, Fix1 ) {
- BSONObj user = BSON( "key" << BSON( "title" << "text" <<
- "text" << "text" ) <<
- "weights" << BSON( "title" << 10 ) );
-
- BSONObj fixed = FTSSpec::fixSpec( user );
- BSONObj fixed2 = FTSSpec::fixSpec( fixed );
- ASSERT_EQUALS( fixed, fixed2 );
- }
-
- TEST( FTSSpec, DefaultLanguage1 ) {
- BSONObj user = BSON( "key" << BSON( "text" << "text" ) <<
- "default_language" << "spanish" );
+ /**
+ * Assert that fixSpec() accepts the provided text index spec.
+ */
+ void assertFixSuccess( const std::string& s ) {
+ BSONObj user = fromjson( s );
try {
+ // fixSpec() should not throw on a valid spec.
BSONObj fixed = FTSSpec::fixSpec( user );
+
+ // fixSpec() on an already-fixed spec shouldn't change it.
+ BSONObj fixed2 = FTSSpec::fixSpec( fixed );
+ ASSERT_EQUALS( fixed, fixed2 );
}
catch ( UserException& e ) {
- ASSERT(false);
+ ASSERT( false );
}
}
- TEST( FTSSpec, DefaultLanguage2 ) {
- BSONObj user = BSON( "key" << BSON( "text" << "text" ) <<
- "default_language" << "spanglish" );
+ /**
+ * Assert that fixSpec() rejects the provided text index spec.
+ */
+ void assertFixFailure( const std::string& s ) {
+ BSONObj user = fromjson( s );
try {
+ // fixSpec() on an invalid spec should uassert.
BSONObj fixed = FTSSpec::fixSpec( user );
- ASSERT(false);
}
- catch ( UserException& e ) {}
+ catch ( UserException& e ) {
+ return;
+ }
+ ASSERT( false );
+ }
+
+ TEST( FTSSpec, FixNormalKey1 ) {
+ assertFixSuccess("{key: {a: 'text'}}");
+ assertFixSuccess("{key: {a: 'text', b: 'text'}}");
+ assertFixSuccess("{key: {a: 'text', b: 'text', c: 'text'}}");
+
+ assertFixFailure("{key: {_fts: 'text'}}"); // not allowed to index reserved field
+ assertFixFailure("{key: {_ftsx: 'text'}}");
+ }
+
+ TEST( FTSSpec, FixCompoundKey1 ) {
+ assertFixSuccess("{key: {a: 'text', b: 1.0}}");
+ assertFixSuccess("{key: {a: 'text', b: NumberInt(1)}}");
+ assertFixSuccess("{key: {a: 'text', b: NumberLong(1)}}");
+ assertFixSuccess("{key: {a: 1.0, b: 'text'}}");
+ assertFixSuccess("{key: {a: NumberInt(1), b: 'text'}}");
+ assertFixSuccess("{key: {a: NumberLong(1), b: 'text'}}");
+ assertFixSuccess("{key: {a: -1, b: 'text'}}");
+ assertFixSuccess("{key: {a: 1, b: 1, c: 'text'}}");
+ assertFixSuccess("{key: {a: 1, b: -1, c: 'text'}}");
+ assertFixSuccess("{key: {a: -1, b: 1, c: 'text'}}");
+ assertFixSuccess("{key: {a: 1, b: 'text', c: 1}}");
+ assertFixSuccess("{key: {a: 'text', b: 1, c: 1}}");
+ assertFixSuccess("{key: {a: 'text', b: 'text', c: 1}}");
+ assertFixSuccess("{key: {a: 1, b: 'text', c: 'text'}}");
+
+ assertFixFailure("{key: {a: 'text', b: 0}}");
+ assertFixFailure("{key: {a: 'text', b: '2d'}}"); // not allowed to mix special indexes
+ assertFixFailure("{key: {a: 'text', b: '1'}}");
+ assertFixFailure("{key: {a: 'text', b: -1}}"); // -1 not allowed in suffix
+ assertFixFailure("{key: {a: 'text', _fts: 1}}");
+ assertFixFailure("{key: {a: 'text', _fts: 'text'}}");
+ assertFixFailure("{key: {a: 'text', _ftsx: 1}}");
+ assertFixFailure("{key: {a: 'text', _ftsx: 'text'}}");
+ assertFixFailure("{key: {a: 'text', b: 1, c: -1}}");
+ assertFixFailure("{key: {a: 'text', b: 1, c: 'text'}}"); // 'text' must all be adjacent
+ assertFixFailure("{key: {a: 'text', b: 1, c: 'text', d: 1}}");
+ assertFixFailure("{key: {a: 1, b: 'text', c: 1, d: 'text', e: 1}}");
+ }
+
+ TEST( FTSSpec, FixDefaultLanguage1 ) {
+ assertFixSuccess("{key: {a: 'text'}, default_language: 'english'}");
+ assertFixSuccess("{key: {a: 'text'}, default_language: 'engLISH'}");
+ assertFixSuccess("{key: {a: 'text'}, default_language: 'en'}");
+ assertFixSuccess("{key: {a: 'text'}, default_language: 'eN'}");
+ assertFixSuccess("{key: {a: 'text'}, default_language: 'spanish'}");
+ assertFixSuccess("{key: {a: 'text'}, default_language: 'none'}");
+
+ assertFixFailure("{key: {a: 'text'}, default_language: 'engrish'}");
+ assertFixFailure("{key: {a: 'text'}, default_language: ' english'}");
+ assertFixFailure("{key: {a: 'text'}, default_language: ''}");
+ }
+
+ TEST( FTSSpec, FixWeights1 ) {
+ assertFixSuccess("{key: {a: 'text'}, weights: {}}");
+ assertFixSuccess("{key: {a: 'text'}, weights: {a: 1.0}}");
+ assertFixSuccess("{key: {a: 'text'}, weights: {a: NumberInt(1)}}");
+ assertFixSuccess("{key: {a: 'text'}, weights: {a: NumberLong(1)}}");
+ assertFixSuccess("{key: {a: 'text'}, weights: {a: 99999}}");
+ assertFixSuccess("{key: {'$**': 'text'}, weights: {'a.b': 2}}");
+ assertFixSuccess("{key: {'$**': 'text'}, weights: {a: 2, b: 2}}");
+ assertFixSuccess("{key: {'$**': 'text'}, weights: {'$**': 2}}");
+
+ assertFixFailure("{key: {a: 'text'}, weights: 0}");
+ assertFixFailure("{key: {a: 'text'}, weights: []}");
+ assertFixFailure("{key: {a: 'text'}, weights: 'x'}");
+ assertFixFailure("{key: {a: 'text'}, weights: {a: 0}}");
+ assertFixFailure("{key: {a: 'text'}, weights: {a: -1}}");
+ assertFixFailure("{key: {a: 'text'}, weights: {a: 100000}}"); // above max weight
+ assertFixFailure("{key: {a: 'text'}, weights: {a: '1'}}");
+ assertFixFailure("{key: {a: 'text'}, weights: {'': 1}}"); // "invalid" path
+ assertFixFailure("{key: {a: 'text'}, weights: {'a.': 1}}");
+ assertFixFailure("{key: {a: 'text'}, weights: {'.a': 1}}");
+ assertFixFailure("{key: {a: 'text'}, weights: {'a..a': 1}}");
+ assertFixFailure("{key: {a: 'text'}, weights: {$a: 1}}");
+ assertFixFailure("{key: {a: 'text'}, weights: {'a.$a': 1}}");
+ assertFixFailure("{key: {a: 'text'}, weights: {'a.$**': 1}}");
+ }
+
+ TEST( FTSSpec, FixLanguageOverride1 ) {
+ assertFixSuccess("{key: {a: 'text'}, language_override: 'foo'}");
+ assertFixSuccess("{key: {a: 'text'}, language_override: 'foo$bar'}");
+
+ assertFixFailure("{key: {a: 'text'}, language_override: 'foo.bar'}"); // can't have '.'
+ assertFixFailure("{key: {a: 'text'}, language_override: ''}");
+ assertFixFailure("{key: {a: 'text'}, language_override: '$foo'}");
+ }
+
+ TEST( FTSSpec, FixTextIndexVersion1 ) {
+ assertFixSuccess("{key: {a: 'text'}, textIndexVersion: 2.0}}");
+ assertFixSuccess("{key: {a: 'text'}, textIndexVersion: NumberInt(2)}}");
+ assertFixSuccess("{key: {a: 'text'}, textIndexVersion: NumberLong(2)}}");
+
+ assertFixFailure("{key: {a: 'text'}, textIndexVersion: 3}");
+ assertFixFailure("{key: {a: 'text'}, textIndexVersion: '2'}");
+ assertFixFailure("{key: {a: 'text'}, textIndexVersion: {}}");
}
TEST( FTSSpec, ScoreSingleField1 ) {