From 0144f59236f507bf4a75a1e2b698100bcd75e4da Mon Sep 17 00:00:00 2001 From: Jason Rassi Date: Thu, 12 Mar 2015 18:52:56 -0400 Subject: SERVER-17437 $caseSensitive option for $text query operator --- src/mongo/db/matcher/expression_parser_text.cpp | 25 ++++++++--- .../db/matcher/expression_parser_text_test.cpp | 48 ++++++++++++++++++---- src/mongo/db/matcher/expression_text.cpp | 18 ++++++-- src/mongo/db/matcher/expression_text.h | 4 +- 4 files changed, 76 insertions(+), 19 deletions(-) (limited to 'src/mongo/db/matcher') 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 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( exp ); + ASSERT_EQUALS( MatchExpression::TEXT, result.getValue()->matchType() ); + boost::scoped_ptr textExp( + static_cast( 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 textExp( + static_cast( 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 textExp( + static_cast( 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 -- cgit v1.2.1