diff options
author | Jason Rassi <rassi@10gen.com> | 2015-03-12 18:52:56 -0400 |
---|---|---|
committer | Jason Rassi <rassi@10gen.com> | 2015-03-12 18:55:39 -0400 |
commit | 0144f59236f507bf4a75a1e2b698100bcd75e4da (patch) | |
tree | 7debcec36431febf47ca2bb15f6acd912143b04f /src/mongo | |
parent | 86f3f05da7238cabb8d260677e81a07399a0906b (diff) | |
download | mongo-0144f59236f507bf4a75a1e2b698100bcd75e4da.tar.gz |
SERVER-17437 $caseSensitive option for $text query operator
Diffstat (limited to 'src/mongo')
-rw-r--r-- | src/mongo/db/matcher/expression_parser_text.cpp | 25 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_parser_text_test.cpp | 48 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_text.cpp | 18 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_text.h | 4 | ||||
-rw-r--r-- | src/mongo/db/query/planner_access.cpp | 1 | ||||
-rw-r--r-- | src/mongo/db/query/query_planner_test_lib.cpp | 7 | ||||
-rw-r--r-- | src/mongo/db/query/query_planner_text_test.cpp | 8 | ||||
-rw-r--r-- | src/mongo/db/query/query_solution.cpp | 3 | ||||
-rw-r--r-- | src/mongo/db/query/query_solution.h | 1 | ||||
-rw-r--r-- | src/mongo/db/query/stage_builder.cpp | 2 |
10 files changed, 97 insertions, 20 deletions
diff --git a/src/mongo/db/matcher/expression_parser_text.cpp b/src/mongo/db/matcher/expression_parser_text.cpp index ad8114c88e8..0a7cb0b01ab 100644 --- a/src/mongo/db/matcher/expression_parser_text.cpp +++ b/src/mongo/db/matcher/expression_parser_text.cpp @@ -44,16 +44,20 @@ namespace mongo { // Validate queryObj, but defer construction of FTSQuery (which requires access to the // target namespace) until stage building time. + int expectedFieldCount = 1; + if ( mongo::String != queryObj["$search"].type() ) { - return StatusWithMatchExpression( ErrorCodes::BadValue, "$search needs a String" ); + return StatusWithMatchExpression( ErrorCodes::TypeMismatch, + "$search requires a string value" ); } string language = ""; BSONElement languageElt = queryObj["$language"]; if ( !languageElt.eoo() ) { + expectedFieldCount++; if ( mongo::String != languageElt.type() ) { - return StatusWithMatchExpression( ErrorCodes::BadValue, - "$language needs a String" ); + return StatusWithMatchExpression( ErrorCodes::TypeMismatch, + "$language requires a string value" ); } language = languageElt.String(); Status status = @@ -65,12 +69,23 @@ namespace mongo { } string query = queryObj["$search"].String(); - if ( queryObj.nFields() != ( languageElt.eoo() ? 1 : 2 ) ) { + BSONElement caseSensitiveElt = queryObj["$caseSensitive"]; + bool caseSensitive = fts::FTSQuery::caseSensitiveDefault; + if ( !caseSensitiveElt.eoo() ) { + expectedFieldCount++; + if ( mongo::Bool != caseSensitiveElt.type() ) { + return StatusWithMatchExpression( ErrorCodes::TypeMismatch, + "$caseSensitive requires a boolean value" ); + } + caseSensitive = caseSensitiveElt.trueValue(); + } + + if ( queryObj.nFields() != expectedFieldCount ) { return StatusWithMatchExpression( ErrorCodes::BadValue, "extra fields in $text" ); } auto_ptr<TextMatchExpression> e( new TextMatchExpression() ); - Status s = e->init( query, language ); + Status s = e->init( query, language, caseSensitive ); if ( !s.isOK() ) { return StatusWithMatchExpression( s ); } diff --git a/src/mongo/db/matcher/expression_parser_text_test.cpp b/src/mongo/db/matcher/expression_parser_text_test.cpp index b2f42018178..42eb9ce2464 100644 --- a/src/mongo/db/matcher/expression_parser_text_test.cpp +++ b/src/mongo/db/matcher/expression_parser_text_test.cpp @@ -39,23 +39,53 @@ namespace mongo { - TEST( MatchExpressionParserText, Parse1 ) { - BSONObj query = fromjson( "{$text:{$search:\"awesome\", $language:\"english\"}}" ); + TEST( MatchExpressionParserText, Basic ) { + BSONObj query = fromjson( "{$text: {$search:\"awesome\", $language:\"english\"}}" ); StatusWithMatchExpression result = MatchExpressionParser::parse( query ); ASSERT_TRUE( result.isOK() ); - MatchExpression* exp = result.getValue(); - ASSERT_EQUALS( MatchExpression::TEXT, exp->matchType() ); - - TextMatchExpression* textExp = static_cast<TextMatchExpression*>( exp ); + ASSERT_EQUALS( MatchExpression::TEXT, result.getValue()->matchType() ); + boost::scoped_ptr<TextMatchExpression> textExp( + static_cast<TextMatchExpression*>( result.getValue() ) ); ASSERT_EQUALS( textExp->getQuery(), "awesome" ); ASSERT_EQUALS( textExp->getLanguage(), "english" ); - delete exp; + ASSERT_EQUALS( textExp->getCaseSensitive(), fts::FTSQuery::caseSensitiveDefault ); + } + + TEST( MatchExpressionParserText, LanguageError ) { + BSONObj query = fromjson( "{$text: {$search:\"awesome\", $language:\"spanglish\"}}" ); + + StatusWithMatchExpression result = MatchExpressionParser::parse( query ); + ASSERT_FALSE( result.isOK() ); + } + + TEST( MatchExpressionParserText, CaseSensitiveTrue ) { + BSONObj query = fromjson( "{$text: {$search:\"awesome\", $caseSensitive: true}}" ); + + StatusWithMatchExpression result = MatchExpressionParser::parse( query ); + ASSERT_TRUE( result.isOK() ); + + ASSERT_EQUALS( MatchExpression::TEXT, result.getValue()->matchType() ); + boost::scoped_ptr<TextMatchExpression> textExp( + static_cast<TextMatchExpression*>( result.getValue() ) ); + ASSERT_EQUALS( textExp->getCaseSensitive(), true ); + } + + TEST( MatchExpressionParserText, CaseSensitiveFalse ) { + BSONObj query = fromjson( "{$text: {$search:\"awesome\", $caseSensitive: false}}" ); + + StatusWithMatchExpression result = MatchExpressionParser::parse( query ); + ASSERT_TRUE( result.isOK() ); + + ASSERT_EQUALS( MatchExpression::TEXT, result.getValue()->matchType() ); + boost::scoped_ptr<TextMatchExpression> textExp( + static_cast<TextMatchExpression*>( result.getValue() ) ); + ASSERT_EQUALS( textExp->getCaseSensitive(), false ); } - TEST( MatchExpressionParserText, Parse2 ) { - BSONObj query = fromjson( "{$text:{$search:\"awesome\", $language:\"spanglish\"}}" ); + TEST( MatchExpressionParserText, CaseSensitiveError ) { + BSONObj query = fromjson( "{$text:{$search:\"awesome\", $caseSensitive: 0}}" ); StatusWithMatchExpression result = MatchExpressionParser::parse( query ); ASSERT_FALSE( result.isOK() ); diff --git a/src/mongo/db/matcher/expression_text.cpp b/src/mongo/db/matcher/expression_text.cpp index 3754d431f57..320136c8751 100644 --- a/src/mongo/db/matcher/expression_text.cpp +++ b/src/mongo/db/matcher/expression_text.cpp @@ -35,9 +35,12 @@ namespace mongo { using std::string; - Status TextMatchExpression::init( const string& query, const string& language ) { + Status TextMatchExpression::init( const string& query, + const string& language, + bool caseSensitive ) { _query = query; _language = language; + _caseSensitive = caseSensitive; return initPath( "_fts" ); } @@ -50,7 +53,9 @@ namespace mongo { void TextMatchExpression::debugString( StringBuilder& debug, int level ) const { _debugAddSpace(debug, level); - debug << "TEXT : query=" << _query << ", language=" << _language << ", tag="; + debug << "TEXT : query=" << _query << ", language=" + << _language << ", caseSensitive=" + << _caseSensitive << ", tag="; MatchExpression::TagData* td = getTag(); if ( NULL != td ) { td->debugString( &debug ); @@ -62,7 +67,9 @@ namespace mongo { } void TextMatchExpression::toBSON(BSONObjBuilder* out) const { - out->append("$text", BSON("$search" << _query << "$language" << _language)); + out->append("$text", BSON("$search" << _query << + "$language" << _language << + "$caseSensitive" << _caseSensitive)); } bool TextMatchExpression::equivalent( const MatchExpression* other ) const { @@ -79,12 +86,15 @@ namespace mongo { if ( realOther->getLanguage() != _language ) { return false; } + if ( realOther->getCaseSensitive() != _caseSensitive ) { + return false; + } return true; } LeafMatchExpression* TextMatchExpression::shallowClone() const { TextMatchExpression* next = new TextMatchExpression(); - next->init( _query, _language ); + next->init( _query, _language, _caseSensitive ); if ( getTag() ) { next->setTag( getTag()->clone() ); } diff --git a/src/mongo/db/matcher/expression_text.h b/src/mongo/db/matcher/expression_text.h index 6ebea78ba3d..8d853de5621 100644 --- a/src/mongo/db/matcher/expression_text.h +++ b/src/mongo/db/matcher/expression_text.h @@ -41,7 +41,7 @@ namespace mongo { TextMatchExpression() : LeafMatchExpression( TEXT ) {} virtual ~TextMatchExpression() {} - Status init( const std::string& query, const std::string& language ); + Status init( const std::string& query, const std::string& language, bool caseSensitive ); virtual bool matchesSingleElement( const BSONElement& e ) const; @@ -55,9 +55,11 @@ namespace mongo { const std::string& getQuery() const { return _query; } const std::string& getLanguage() const { return _language; } + bool getCaseSensitive() const { return _caseSensitive; } private: std::string _query; std::string _language; + bool _caseSensitive; }; } // namespace mongo diff --git a/src/mongo/db/query/planner_access.cpp b/src/mongo/db/query/planner_access.cpp index aa107cd906d..e41b3559bb9 100644 --- a/src/mongo/db/query/planner_access.cpp +++ b/src/mongo/db/query/planner_access.cpp @@ -151,6 +151,7 @@ namespace mongo { ret->indexKeyPattern = index.keyPattern; ret->query = textExpr->getQuery(); ret->language = textExpr->getLanguage(); + ret->caseSensitive = textExpr->getCaseSensitive(); return ret; } else { diff --git a/src/mongo/db/query/query_planner_test_lib.cpp b/src/mongo/db/query/query_planner_test_lib.cpp index 72784d711f2..570d5da2e81 100644 --- a/src/mongo/db/query/query_planner_test_lib.cpp +++ b/src/mongo/db/query/query_planner_test_lib.cpp @@ -287,6 +287,13 @@ namespace mongo { } } + BSONElement caseSensitiveElt = textObj["caseSensitive"]; + if (!caseSensitiveElt.eoo()) { + if (caseSensitiveElt.trueValue() != node->caseSensitive) { + return false; + } + } + BSONElement indexPrefix = textObj["prefix"]; if (!indexPrefix.eoo()) { if (!indexPrefix.isABSONObj()) { diff --git a/src/mongo/db/query/query_planner_text_test.cpp b/src/mongo/db/query/query_planner_text_test.cpp index 376cf8fd809..672af7ddc41 100644 --- a/src/mongo/db/query/query_planner_text_test.cpp +++ b/src/mongo/db/query/query_planner_text_test.cpp @@ -624,4 +624,12 @@ namespace { "pattern: {other: 1}}}]}}}}"); } + TEST_F(QueryPlannerTest, TextCaseSensitive) { + addIndex(BSON("_fts" << "text" << "_ftsx" << 1)); + runQuery(fromjson("{$text: {$search: 'blah', $caseSensitive: true}}")); + + assertNumSolutions(1); + assertSolutionExists("{text: {search: 'blah', caseSensitive: true}}"); + } + } // namespace diff --git a/src/mongo/db/query/query_solution.cpp b/src/mongo/db/query/query_solution.cpp index 877e58612c3..91785b2d218 100644 --- a/src/mongo/db/query/query_solution.cpp +++ b/src/mongo/db/query/query_solution.cpp @@ -77,6 +77,8 @@ namespace mongo { addIndent(ss, indent + 1); *ss << "language = " << language << '\n'; addIndent(ss, indent + 1); + *ss << "caseSensitive= " << caseSensitive << '\n'; + addIndent(ss, indent + 1); *ss << "indexPrefix = " << indexPrefix.toString() << '\n'; if (NULL != filter) { addIndent(ss, indent + 1); @@ -93,6 +95,7 @@ namespace mongo { copy->indexKeyPattern = this->indexKeyPattern; copy->query = this->query; copy->language = this->language; + copy->caseSensitive = this->caseSensitive; copy->indexPrefix = this->indexPrefix; return copy; diff --git a/src/mongo/db/query/query_solution.h b/src/mongo/db/query/query_solution.h index 544d911c9eb..95afa910864 100644 --- a/src/mongo/db/query/query_solution.h +++ b/src/mongo/db/query/query_solution.h @@ -239,6 +239,7 @@ namespace mongo { BSONObj indexKeyPattern; std::string query; std::string language; + bool caseSensitive; // "Prefix" fields of a text index can handle equality predicates. We group them with the // text node while creating the text leaf node and convert them into a BSONObj index prefix diff --git a/src/mongo/db/query/stage_builder.cpp b/src/mongo/db/query/stage_builder.cpp index eeabcca2bff..f6c1b93da09 100644 --- a/src/mongo/db/query/stage_builder.cpp +++ b/src/mongo/db/query/stage_builder.cpp @@ -266,7 +266,7 @@ namespace mongo { Status parseStatus = params.query.parse(node->query, language, - fts::FTSQuery::caseSensitiveDefault, + node->caseSensitive, fam->getSpec().getTextIndexVersion()); if (!parseStatus.isOK()) { warning() << "Can't parse text search query"; |