diff options
31 files changed, 812 insertions, 314 deletions
diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript index 133e2147427..d36f661e414 100644 --- a/src/mongo/db/commands/SConscript +++ b/src/mongo/db/commands/SConscript @@ -58,7 +58,6 @@ env.Library( '$BUILD_DIR/mongo/db/log_process_details', '$BUILD_DIR/mongo/db/matcher/expressions', '$BUILD_DIR/mongo/db/matcher/expressions_geo', - '$BUILD_DIR/mongo/db/matcher/expressions_text', '$BUILD_DIR/mongo/db/repl/isself', '$BUILD_DIR/mongo/db/repl/repl_coordinator_global', '$BUILD_DIR/mongo/db/server_options', diff --git a/src/mongo/db/exec/plan_stats.h b/src/mongo/db/exec/plan_stats.h index 211b975b9dc..14e07391920 100644 --- a/src/mongo/db/exec/plan_stats.h +++ b/src/mongo/db/exec/plan_stats.h @@ -601,7 +601,7 @@ struct TextStats : public SpecificStats { std::string indexName; - // Human-readable form of the FTSQueryImpl associated with the text stage. + // Human-readable form of the FTSQuery associated with the text stage. BSONObj parsedTextQuery; // Index keys that precede the "text" index key. diff --git a/src/mongo/db/exec/stagedebug_cmd.cpp b/src/mongo/db/exec/stagedebug_cmd.cpp index 623745f0047..378a0d7ba4d 100644 --- a/src/mongo/db/exec/stagedebug_cmd.cpp +++ b/src/mongo/db/exec/stagedebug_cmd.cpp @@ -459,11 +459,11 @@ public: params.spec = fam->getSpec(); - if (!params.query.parse(search, - fam->getSpec().defaultLanguage().str().c_str(), - TextMatchExpressionBase::kCaseSensitiveDefault, - TextMatchExpressionBase::kDiacriticSensitiveDefault, - fam->getSpec().getTextIndexVersion()).isOK()) { + params.query.setQuery(search); + params.query.setLanguage(fam->getSpec().defaultLanguage().str()); + params.query.setCaseSensitive(TextMatchExpressionBase::kCaseSensitiveDefault); + params.query.setDiacriticSensitive(TextMatchExpressionBase::kDiacriticSensitiveDefault); + if (!params.query.parse(fam->getSpec().getTextIndexVersion()).isOK()) { return NULL; } diff --git a/src/mongo/db/fts/SConscript b/src/mongo/db/fts/SConscript index 300f05c04e0..1a752c1cae8 100644 --- a/src/mongo/db/fts/SConscript +++ b/src/mongo/db/fts/SConscript @@ -58,6 +58,16 @@ baseEnv.Library('base', [ "$BUILD_DIR/third_party/shim_stemmer", ]) +env.Library( + target='fts_query_noop', + source=[ + 'fts_query_noop.cpp', + ], + LIBDEPS=[ + '$BUILD_DIR/mongo/base', + ], +) + env.Library( 'server_common', [ 'fts_enabled.cpp' ], @@ -96,6 +106,9 @@ env.CppUnitTest( "fts_matcher_test", "fts_matcher_test.cpp", env.CppUnitTest( "fts_query_impl_test", "fts_query_impl_test.cpp", LIBDEPS=["base"] ) +env.CppUnitTest( "fts_query_noop_test", "fts_query_noop_test.cpp", + LIBDEPS=["fts_query_noop"] ) + env.CppUnitTest( "fts_spec_test", "fts_spec_test.cpp", LIBDEPS=["base"] ) diff --git a/src/mongo/db/fts/fts_matcher_test.cpp b/src/mongo/db/fts/fts_matcher_test.cpp index 02f6c2b2ba0..9b590fa67a3 100644 --- a/src/mongo/db/fts/fts_matcher_test.cpp +++ b/src/mongo/db/fts/fts_matcher_test.cpp @@ -38,7 +38,11 @@ namespace fts { TEST(FTSMatcher, NegWild1) { FTSQueryImpl q; - ASSERT_OK(q.parse("foo -bar", "english", false, false, TEXT_INDEX_VERSION_3)); + q.setQuery("foo -bar"); + q.setLanguage("english"); + q.setCaseSensitive(false); + q.setDiacriticSensitive(false); + ASSERT(q.parse(TEXT_INDEX_VERSION_3).isOK()); FTSMatcher m(q, FTSSpec(FTSSpec::fixSpec(BSON("key" << BSON("$**" << "text"))))); @@ -52,7 +56,11 @@ TEST(FTSMatcher, NegWild1) { // Regression test for SERVER-11994. TEST(FTSMatcher, NegWild2) { FTSQueryImpl q; - ASSERT_OK(q.parse("pizza -restaurant", "english", false, false, TEXT_INDEX_VERSION_3)); + q.setQuery("pizza -restaurant"); + q.setLanguage("english"); + q.setCaseSensitive(false); + q.setDiacriticSensitive(false); + ASSERT(q.parse(TEXT_INDEX_VERSION_3).isOK()); FTSMatcher m(q, FTSSpec(FTSSpec::fixSpec(BSON("key" << BSON("$**" << "text"))))); @@ -65,7 +73,11 @@ TEST(FTSMatcher, NegWild2) { TEST(FTSMatcher, Phrase1) { FTSQueryImpl q; - ASSERT_OK(q.parse("foo \"table top\"", "english", false, false, TEXT_INDEX_VERSION_3)); + q.setQuery("foo \"table top\""); + q.setLanguage("english"); + q.setCaseSensitive(false); + q.setDiacriticSensitive(false); + ASSERT(q.parse(TEXT_INDEX_VERSION_3).isOK()); FTSMatcher m(q, FTSSpec(FTSSpec::fixSpec(BSON("key" << BSON("$**" << "text"))))); @@ -87,7 +99,11 @@ TEST(FTSMatcher, Phrase1) { TEST(FTSMatcher, Phrase2) { FTSQueryImpl q; - ASSERT_OK(q.parse("foo \"table top\"", "english", false, false, TEXT_INDEX_VERSION_3)); + q.setQuery("foo \"table top\""); + q.setLanguage("english"); + q.setCaseSensitive(false); + q.setDiacriticSensitive(false); + ASSERT(q.parse(TEXT_INDEX_VERSION_3).isOK()); FTSMatcher m(q, FTSSpec(FTSSpec::fixSpec(BSON("key" << BSON("x" << "text"))))); @@ -98,7 +114,11 @@ TEST(FTSMatcher, Phrase2) { // language. TEST(FTSMatcher, ParsesUsingDocLanguage) { FTSQueryImpl q; - ASSERT_OK(q.parse("-glad", "none", false, false, TEXT_INDEX_VERSION_3)); + q.setQuery("-glad"); + q.setLanguage("none"); + q.setCaseSensitive(false); + q.setDiacriticSensitive(false); + ASSERT(q.parse(TEXT_INDEX_VERSION_3).isOK()); FTSMatcher m(q, FTSSpec(FTSSpec::fixSpec(BSON("key" << BSON("x" << "text"))))); @@ -112,7 +132,11 @@ TEST(FTSMatcher, ParsesUsingDocLanguage) { // Test the matcher does not filter out stop words from positive terms TEST(FTSMatcher, MatcherDoesNotFilterStopWordsNeg) { FTSQueryImpl q; - ASSERT_OK(q.parse("-the", "none", false, false, TEXT_INDEX_VERSION_3)); + q.setQuery("-the"); + q.setLanguage("none"); + q.setCaseSensitive(false); + q.setDiacriticSensitive(false); + ASSERT(q.parse(TEXT_INDEX_VERSION_3).isOK()); FTSMatcher m(q, FTSSpec(FTSSpec::fixSpec(BSON("key" << BSON("x" << "text"))))); @@ -124,7 +148,11 @@ TEST(FTSMatcher, MatcherDoesNotFilterStopWordsNeg) { // Test the matcher does not filter out stop words from negative terms TEST(FTSMatcher, MatcherDoesNotFilterStopWordsPos) { FTSQueryImpl q; - ASSERT_OK(q.parse("the", "none", false, false, TEXT_INDEX_VERSION_3)); + q.setQuery("the"); + q.setLanguage("none"); + q.setCaseSensitive(false); + q.setDiacriticSensitive(false); + ASSERT(q.parse(TEXT_INDEX_VERSION_3).isOK()); FTSMatcher m(q, FTSSpec(FTSSpec::fixSpec(BSON("key" << BSON("x" << "text"))))); @@ -137,7 +165,11 @@ TEST(FTSMatcher, MatcherDoesNotFilterStopWordsPos) { // case-sensitive text query 'search'. static bool docHasPositiveTermWithCase(const std::string& doc, const std::string& search) { FTSQueryImpl q; - ASSERT_OK(q.parse(search, "english", true, false, TEXT_INDEX_VERSION_3)); + q.setQuery(search); + q.setLanguage("english"); + q.setCaseSensitive(true); + q.setDiacriticSensitive(false); + ASSERT(q.parse(TEXT_INDEX_VERSION_3).isOK()); FTSMatcher m(q, FTSSpec(FTSSpec::fixSpec(BSON("key" << BSON("x" << "text"))))); @@ -164,7 +196,11 @@ TEST(FTSMatcher, HasPositiveTermCaseSensitive) { // case-sensitive text query 'search'. static bool docHasNegativeTermWithCase(const std::string& doc, const std::string& search) { FTSQueryImpl q; - ASSERT_OK(q.parse(search, "english", true, false, TEXT_INDEX_VERSION_3)); + q.setQuery(search); + q.setLanguage("english"); + q.setCaseSensitive(true); + q.setDiacriticSensitive(false); + ASSERT(q.parse(TEXT_INDEX_VERSION_3).isOK()); FTSMatcher m(q, FTSSpec(FTSSpec::fixSpec(BSON("key" << BSON("x" << "text"))))); @@ -191,7 +227,11 @@ TEST(FTSMatcher, HasNegativeTermCaseSensitive) { // from case-sensitive text query 'search'. static bool docPositivePhrasesMatchWithCase(const std::string& doc, const std::string& search) { FTSQueryImpl q; - ASSERT_OK(q.parse(search, "english", true, false, TEXT_INDEX_VERSION_3)); + q.setQuery(search); + q.setLanguage("english"); + q.setCaseSensitive(true); + q.setDiacriticSensitive(false); + ASSERT(q.parse(TEXT_INDEX_VERSION_3).isOK()); FTSMatcher m(q, FTSSpec(FTSSpec::fixSpec(BSON("key" << BSON("x" << "text"))))); @@ -214,7 +254,11 @@ TEST(FTSMatcher, PositivePhrasesMatchWithCase) { // from case-sensitive text query 'search'. static bool docNegativePhrasesMatchWithCase(const std::string& doc, const std::string& search) { FTSQueryImpl q; - ASSERT_OK(q.parse(search, "english", true, false, TEXT_INDEX_VERSION_3)); + q.setQuery(search); + q.setLanguage("english"); + q.setCaseSensitive(true); + q.setDiacriticSensitive(false); + ASSERT(q.parse(TEXT_INDEX_VERSION_3).isOK()); FTSMatcher m(q, FTSSpec(FTSSpec::fixSpec(BSON("key" << BSON("x" << "text"))))); diff --git a/src/mongo/db/fts/fts_query.h b/src/mongo/db/fts/fts_query.h new file mode 100644 index 00000000000..c2b2fd87deb --- /dev/null +++ b/src/mongo/db/fts/fts_query.h @@ -0,0 +1,107 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include <string> + +#include "mongo/db/fts/fts_util.h" + +namespace mongo { +namespace fts { + +/** + * An FTSQuery represents a parsed text search query. + */ +class FTSQuery { +public: + virtual ~FTSQuery() {} + + void setQuery(std::string query) { + _query = std::move(query); + } + + void setLanguage(std::string language) { + _language = std::move(language); + } + + void setCaseSensitive(bool caseSensitive) { + _caseSensitive = caseSensitive; + } + + void setDiacriticSensitive(bool diacriticSensitive) { + _diacriticSensitive = diacriticSensitive; + } + + const std::string& getQuery() const { + return _query; + } + + const std::string& getLanguage() const { + return _language; + } + + bool getCaseSensitive() const { + return _caseSensitive; + } + + bool getDiacriticSensitive() const { + return _diacriticSensitive; + } + + /** + * Returns true iff '*this' and 'other' have the same unparsed form. + */ + bool equivalent(const FTSQuery& other) const { + return _query == other._query && _language == other._language && + _caseSensitive == other._caseSensitive && + _diacriticSensitive == other._diacriticSensitive; + } + + /** + * Parses the text search query. Before parsing, the FTSQuery needs to be initialized with + * the set*() methods above. + * + * Returns Status::OK() if parsing was successful; returns an error Status otherwise. + */ + virtual Status parse(TextIndexVersion textIndexVersion) = 0; + + /** + * Returns a copy of this FTSQuery. + */ + virtual std::unique_ptr<FTSQuery> clone() const = 0; + +private: + std::string _query; + std::string _language; + bool _caseSensitive = false; + bool _diacriticSensitive = false; +}; + +} // namespace fts +} // namespace mongo diff --git a/src/mongo/db/fts/fts_query_impl.cpp b/src/mongo/db/fts/fts_query_impl.cpp index af03d2ed5da..674c8e84ab2 100644 --- a/src/mongo/db/fts/fts_query_impl.cpp +++ b/src/mongo/db/fts/fts_query_impl.cpp @@ -37,6 +37,7 @@ #include "mongo/db/fts/fts_tokenizer.h" #include "mongo/util/mongoutils/str.h" #include "mongo/util/stringutils.h" +#include "mongo/stdx/memory.h" namespace mongo { @@ -49,18 +50,11 @@ using std::string; using std::stringstream; using std::vector; -Status FTSQueryImpl::parse(const string& query, - StringData language, - bool caseSensitive, - bool diacriticSensitive, - TextIndexVersion textIndexVersion) { - StatusWithFTSLanguage swl = FTSLanguage::make(language, textIndexVersion); - if (!swl.getStatus().isOK()) { - return swl.getStatus(); +Status FTSQueryImpl::parse(TextIndexVersion textIndexVersion) { + StatusWithFTSLanguage ftsLanguage = FTSLanguage::make(getLanguage(), textIndexVersion); + if (!ftsLanguage.getStatus().isOK()) { + return ftsLanguage.getStatus(); } - _language = swl.getValue(); - _caseSensitive = caseSensitive; - _diacriticSensitive = diacriticSensitive; // Build a space delimited list of words to have the FtsTokenizer tokenize string positiveTermSentence; @@ -71,7 +65,7 @@ Status FTSQueryImpl::parse(const string& query, unsigned quoteOffset = 0; - FTSQueryParser i(query); + FTSQueryParser i(getQuery()); while (i.more()) { QueryToken t = i.next(); @@ -105,7 +99,7 @@ Status FTSQueryImpl::parse(const string& query, // end of a phrase unsigned phraseStart = quoteOffset + 1; unsigned phraseLength = t.offset - phraseStart; - StringData phrase = StringData(query).substr(phraseStart, phraseLength); + StringData phrase = StringData(getQuery()).substr(phraseStart, phraseLength); if (inNegation) { _negatedPhrases.push_back(phrase.toString()); } else { @@ -124,7 +118,7 @@ Status FTSQueryImpl::parse(const string& query, } } - std::unique_ptr<FTSTokenizer> tokenizer(_language->createTokenizer()); + std::unique_ptr<FTSTokenizer> tokenizer(ftsLanguage.getValue()->createTokenizer()); _addTerms(tokenizer.get(), positiveTermSentence, false); _addTerms(tokenizer.get(), negativeTermSentence, true); @@ -132,6 +126,20 @@ Status FTSQueryImpl::parse(const string& query, return Status::OK(); } +std::unique_ptr<FTSQuery> FTSQueryImpl::clone() const { + auto clonedQuery = stdx::make_unique<FTSQueryImpl>(); + clonedQuery->setQuery(getQuery()); + clonedQuery->setLanguage(getLanguage()); + clonedQuery->setCaseSensitive(getCaseSensitive()); + clonedQuery->setDiacriticSensitive(getDiacriticSensitive()); + clonedQuery->_positiveTerms = _positiveTerms; + clonedQuery->_negatedTerms = _negatedTerms; + clonedQuery->_positivePhrases = _positivePhrases; + clonedQuery->_negatedPhrases = _negatedPhrases; + clonedQuery->_termsForBounds = _termsForBounds; + return std::move(clonedQuery); +} + void FTSQueryImpl::_addTerms(FTSTokenizer* tokenizer, const string& sentence, bool negated) { tokenizer->reset(sentence.c_str(), FTSTokenizer::kFilterStopWords); @@ -150,21 +158,21 @@ void FTSQueryImpl::_addTerms(FTSTokenizer* tokenizer, const string& sentence, bo // Compute the string corresponding to 'token' that will be used for the matcher. // For case and diacritic insensitive queries, this is the same string as 'boundsTerm' // computed above. - if (!_caseSensitive && !_diacriticSensitive) { + if (!getCaseSensitive() && !getDiacriticSensitive()) { activeTerms.insert(word); } } - if (!_caseSensitive && !_diacriticSensitive) { + if (!getCaseSensitive() && !getDiacriticSensitive()) { return; } FTSTokenizer::Options newOptions = FTSTokenizer::kFilterStopWords; - if (_caseSensitive) { + if (getCaseSensitive()) { newOptions |= FTSTokenizer::kGenerateCaseSensitiveTokens; } - if (_diacriticSensitive) { + if (getDiacriticSensitive()) { newOptions |= FTSTokenizer::kGenerateDiacriticSensitiveTokens; } diff --git a/src/mongo/db/fts/fts_query_impl.h b/src/mongo/db/fts/fts_query_impl.h index dbd266983d3..888cec08e13 100644 --- a/src/mongo/db/fts/fts_query_impl.h +++ b/src/mongo/db/fts/fts_query_impl.h @@ -30,31 +30,23 @@ #pragma once +#include <set> #include <string> #include <vector> -#include "mongo/base/status.h" -#include "mongo/db/fts/stemmer.h" -#include "mongo/db/fts/stop_words.h" -#include "mongo/util/stringutils.h" +#include "mongo/db/fts/fts_query.h" namespace mongo { namespace fts { -class FTSQueryImpl { +class FTSTokenizer; + +class FTSQueryImpl final : public FTSQuery { public: - // Initializes an FTSQueryImpl. Note that the parsing of "language" depends on the text - // index version, since a query which doesn't specify a language and is against a - // version 1 text index with a version 1 default language string needs to be parsed as - // version 1 (see fts_language.cpp for a list of language strings specific to version - // 1). Note that the diacritic sensitive option has no effect on FTS queries below index version - // 3. - Status parse(const std::string& query, - StringData language, - bool caseSensitive, - bool diacriticSensitive, - TextIndexVersion textIndexVersion); + Status parse(TextIndexVersion textIndexVersion) final; + + std::unique_ptr<FTSQuery> clone() const final; const std::set<std::string>& getPositiveTerms() const { return _positiveTerms; @@ -73,16 +65,6 @@ public: return _termsForBounds; } - const FTSLanguage& getLanguage() const { - return *_language; - } - bool getCaseSensitive() const { - return _caseSensitive; - } - bool getDiacriticSensitive() const { - return _diacriticSensitive; - } - std::string toString() const; std::string debugString() const; @@ -92,23 +74,10 @@ public: private: void _addTerms(FTSTokenizer* tokenizer, const std::string& tokens, bool negated); - const FTSLanguage* _language; - bool _caseSensitive; - bool _diacriticSensitive; - - // Positive terms. std::set<std::string> _positiveTerms; - - // Negated terms. std::set<std::string> _negatedTerms; - - // Positive phrases. std::vector<std::string> _positivePhrases; - - // Negated phrases. std::vector<std::string> _negatedPhrases; - - // Terms for bounds. std::set<std::string> _termsForBounds; }; } diff --git a/src/mongo/db/fts/fts_query_impl_test.cpp b/src/mongo/db/fts/fts_query_impl_test.cpp index 9f336e3c75b..538ee3755bc 100644 --- a/src/mongo/db/fts/fts_query_impl_test.cpp +++ b/src/mongo/db/fts/fts_query_impl_test.cpp @@ -37,7 +37,11 @@ namespace fts { TEST(FTSQueryImpl, Basic1) { FTSQueryImpl q; - ASSERT(q.parse("this is fun", "english", false, false, TEXT_INDEX_VERSION_3).isOK()); + q.setQuery("this is fun"); + q.setLanguage("english"); + q.setCaseSensitive(false); + q.setDiacriticSensitive(false); + ASSERT(q.parse(TEXT_INDEX_VERSION_3).isOK()); ASSERT_EQUALS(false, q.getCaseSensitive()); ASSERT_EQUALS(1U, q.getPositiveTerms().size()); @@ -50,7 +54,11 @@ TEST(FTSQueryImpl, Basic1) { TEST(FTSQueryImpl, ParsePunctuation) { FTSQueryImpl q; - ASSERT(q.parse("hello.world", "english", false, false, TEXT_INDEX_VERSION_3).isOK()); + q.setQuery("hello.world"); + q.setLanguage("english"); + q.setCaseSensitive(false); + q.setDiacriticSensitive(false); + ASSERT(q.parse(TEXT_INDEX_VERSION_3).isOK()); ASSERT_EQUALS(false, q.getCaseSensitive()); ASSERT_EQUALS(2U, q.getPositiveTerms().size()); @@ -64,7 +72,11 @@ TEST(FTSQueryImpl, ParsePunctuation) { TEST(FTSQueryImpl, Neg1) { FTSQueryImpl q; - ASSERT(q.parse("this is -really fun", "english", false, false, TEXT_INDEX_VERSION_3).isOK()); + q.setQuery("this is -really fun"); + q.setLanguage("english"); + q.setCaseSensitive(false); + q.setDiacriticSensitive(false); + ASSERT(q.parse(TEXT_INDEX_VERSION_3).isOK()); ASSERT_EQUALS(1U, q.getPositiveTerms().size()); ASSERT_EQUALS("fun", *q.getPositiveTerms().begin()); @@ -75,8 +87,11 @@ TEST(FTSQueryImpl, Neg1) { TEST(FTSQueryImpl, Phrase1) { FTSQueryImpl q; - ASSERT(q.parse("doing a \"phrase test\" for fun", "english", false, false, TEXT_INDEX_VERSION_3) - .isOK()); + q.setQuery("doing a \"phrase test\" for fun"); + q.setLanguage("english"); + q.setCaseSensitive(false); + q.setDiacriticSensitive(false); + ASSERT(q.parse(TEXT_INDEX_VERSION_3).isOK()); ASSERT_EQUALS(3U, q.getPositiveTerms().size()); ASSERT_EQUALS(0U, q.getNegatedTerms().size()); @@ -90,29 +105,42 @@ TEST(FTSQueryImpl, Phrase1) { TEST(FTSQueryImpl, Phrase2) { FTSQueryImpl q; - ASSERT(q.parse("doing a \"phrase-test\" for fun", "english", false, false, TEXT_INDEX_VERSION_3) - .isOK()); + q.setQuery("doing a \"phrase-test\" for fun"); + q.setLanguage("english"); + q.setCaseSensitive(false); + q.setDiacriticSensitive(false); + ASSERT(q.parse(TEXT_INDEX_VERSION_3).isOK()); ASSERT_EQUALS(1U, q.getPositivePhr().size()); ASSERT_EQUALS("phrase-test", q.getPositivePhr()[0]); } TEST(FTSQueryImpl, NegPhrase1) { FTSQueryImpl q; - ASSERT( - q.parse("doing a -\"phrase test\" for fun", "english", false, false, TEXT_INDEX_VERSION_3) - .isOK()); + q.setQuery("doing a -\"phrase test\" for fun"); + q.setLanguage("english"); + q.setCaseSensitive(false); + q.setDiacriticSensitive(false); + ASSERT(q.parse(TEXT_INDEX_VERSION_3).isOK()); ASSERT_EQUALS("fun||||||phrase test", q.debugString()); } TEST(FTSQueryImpl, CaseSensitiveOption) { FTSQueryImpl q; - ASSERT(q.parse("this is fun", "english", true, false, TEXT_INDEX_VERSION_3).isOK()); + q.setQuery("this is fun"); + q.setLanguage("english"); + q.setCaseSensitive(true); + q.setDiacriticSensitive(false); + ASSERT(q.parse(TEXT_INDEX_VERSION_3).isOK()); ASSERT_EQUALS(true, q.getCaseSensitive()); } TEST(FTSQueryImpl, CaseSensitivePositiveTerms) { FTSQueryImpl q; - ASSERT(q.parse("This is Positively fun", "english", true, false, TEXT_INDEX_VERSION_3).isOK()); + q.setQuery("This is Positively fun"); + q.setLanguage("english"); + q.setCaseSensitive(true); + q.setDiacriticSensitive(false); + ASSERT(q.parse(TEXT_INDEX_VERSION_3).isOK()); ASSERT_EQUALS(2U, q.getTermsForBounds().size()); ASSERT_EQUALS(1, @@ -128,8 +156,11 @@ TEST(FTSQueryImpl, CaseSensitivePositiveTerms) { TEST(FTSQueryImpl, CaseSensitiveNegativeTerms) { FTSQueryImpl q; - ASSERT(q.parse("-This -is -Negatively -miserable", "english", true, false, TEXT_INDEX_VERSION_3) - .isOK()); + q.setQuery("-This -is -Negatively -miserable"); + q.setLanguage("english"); + q.setCaseSensitive(true); + q.setDiacriticSensitive(false); + ASSERT(q.parse(TEXT_INDEX_VERSION_3).isOK()); ASSERT_EQUALS(0U, q.getPositiveTerms().size()); ASSERT_EQUALS(0U, q.getTermsForBounds().size()); @@ -142,8 +173,11 @@ TEST(FTSQueryImpl, CaseSensitiveNegativeTerms) { TEST(FTSQueryImpl, CaseSensitivePositivePhrases) { FTSQueryImpl q; - ASSERT(q.parse("doing a \"Phrase Test\" for fun", "english", true, false, TEXT_INDEX_VERSION_3) - .isOK()); + q.setQuery("doing a \"Phrase Test\" for fun"); + q.setLanguage("english"); + q.setCaseSensitive(true); + q.setDiacriticSensitive(false); + ASSERT(q.parse(TEXT_INDEX_VERSION_3).isOK()); ASSERT_EQUALS(1U, q.getPositivePhr().size()); ASSERT_EQUALS(0U, q.getNegatedPhr().size()); @@ -152,8 +186,11 @@ TEST(FTSQueryImpl, CaseSensitivePositivePhrases) { TEST(FTSQueryImpl, CaseSensitiveNegativePhrases) { FTSQueryImpl q; - ASSERT(q.parse("doing a -\"Phrase Test\" for fun", "english", true, false, TEXT_INDEX_VERSION_3) - .isOK()); + q.setQuery("doing a -\"Phrase Test\" for fun"); + q.setLanguage("english"); + q.setCaseSensitive(true); + q.setDiacriticSensitive(false); + ASSERT(q.parse(TEXT_INDEX_VERSION_3).isOK()); ASSERT_EQUALS(0U, q.getPositivePhr().size()); ASSERT_EQUALS(1U, q.getNegatedPhr().size()); @@ -162,17 +199,34 @@ TEST(FTSQueryImpl, CaseSensitiveNegativePhrases) { TEST(FTSQueryImpl, Mix1) { FTSQueryImpl q; - ASSERT( - q.parse("\"industry\" -Melbourne -Physics", "english", false, false, TEXT_INDEX_VERSION_3) - .isOK()); + q.setQuery("\"industry\" -Melbourne -Physics"); + q.setLanguage("english"); + q.setCaseSensitive(false); + q.setDiacriticSensitive(false); + ASSERT(q.parse(TEXT_INDEX_VERSION_3).isOK()); ASSERT_EQUALS("industri||melbourn|physic||industry||", q.debugString()); } TEST(FTSQueryImpl, NegPhrase2) { FTSQueryImpl q1, q2, q3; - ASSERT(q1.parse("foo \"bar\"", "english", false, false, TEXT_INDEX_VERSION_3).isOK()); - ASSERT(q2.parse("foo \"-bar\"", "english", false, false, TEXT_INDEX_VERSION_3).isOK()); - ASSERT(q3.parse("foo \" -bar\"", "english", false, false, TEXT_INDEX_VERSION_3).isOK()); + + q1.setQuery("foo \"bar\""); + q1.setLanguage("english"); + q1.setCaseSensitive(false); + q1.setDiacriticSensitive(false); + ASSERT(q1.parse(TEXT_INDEX_VERSION_3).isOK()); + + q2.setQuery("foo \"-bar\""); + q2.setLanguage("english"); + q2.setCaseSensitive(false); + q2.setDiacriticSensitive(false); + ASSERT(q2.parse(TEXT_INDEX_VERSION_3).isOK()); + + q3.setQuery("foo \" -bar\""); + q3.setLanguage("english"); + q3.setCaseSensitive(false); + q3.setDiacriticSensitive(false); + ASSERT(q3.parse(TEXT_INDEX_VERSION_3).isOK()); ASSERT_EQUALS(2U, q1.getPositiveTerms().size()); ASSERT_EQUALS(2U, q2.getPositiveTerms().size()); @@ -193,9 +247,24 @@ TEST(FTSQueryImpl, NegPhrase2) { TEST(FTSQueryImpl, NegPhrase3) { FTSQueryImpl q1, q2, q3; - ASSERT(q1.parse("foo -\"bar\"", "english", false, false, TEXT_INDEX_VERSION_3).isOK()); - ASSERT(q2.parse("foo -\"-bar\"", "english", false, false, TEXT_INDEX_VERSION_3).isOK()); - ASSERT(q3.parse("foo -\" -bar\"", "english", false, false, TEXT_INDEX_VERSION_3).isOK()); + + q1.setQuery("foo -\"bar\""); + q1.setLanguage("english"); + q1.setCaseSensitive(false); + q1.setDiacriticSensitive(false); + ASSERT(q1.parse(TEXT_INDEX_VERSION_3).isOK()); + + q2.setQuery("foo -\"-bar\""); + q2.setLanguage("english"); + q2.setCaseSensitive(false); + q2.setDiacriticSensitive(false); + ASSERT(q2.parse(TEXT_INDEX_VERSION_3).isOK()); + + q3.setQuery("foo -\" -bar\""); + q3.setLanguage("english"); + q3.setCaseSensitive(false); + q3.setDiacriticSensitive(false); + ASSERT(q3.parse(TEXT_INDEX_VERSION_3).isOK()); ASSERT_EQUALS(1U, q1.getPositiveTerms().size()); ASSERT_EQUALS(1U, q2.getPositiveTerms().size()); @@ -218,7 +287,11 @@ TEST(FTSQueryImpl, NegPhrase3) { // stemmer and stopword list. TEST(FTSQueryImpl, TextIndexVersion1LanguageEnglish) { FTSQueryImpl q; - ASSERT(q.parse("the running", "english", false, false, TEXT_INDEX_VERSION_1).isOK()); + q.setQuery("the running"); + q.setLanguage("english"); + q.setCaseSensitive(false); + q.setDiacriticSensitive(false); + ASSERT(q.parse(TEXT_INDEX_VERSION_1).isOK()); ASSERT_EQUALS(1U, q.getPositiveTerms().size()); ASSERT_EQUALS("run", *q.getPositiveTerms().begin()); ASSERT_EQUALS(0U, q.getNegatedTerms().size()); @@ -230,7 +303,11 @@ TEST(FTSQueryImpl, TextIndexVersion1LanguageEnglish) { // no stopword list. TEST(FTSQueryImpl, TextIndexVersion1LanguageEng) { FTSQueryImpl q; - ASSERT(q.parse("the running", "eng", false, false, TEXT_INDEX_VERSION_1).isOK()); + q.setQuery("the running"); + q.setLanguage("eng"); + q.setCaseSensitive(false); + q.setDiacriticSensitive(false); + ASSERT(q.parse(TEXT_INDEX_VERSION_1).isOK()); ASSERT_EQUALS(2U, q.getPositiveTerms().size()); ASSERT_EQUALS(1, std::count(q.getPositiveTerms().begin(), q.getPositiveTerms().end(), "the")); ASSERT_EQUALS(1, std::count(q.getPositiveTerms().begin(), q.getPositiveTerms().end(), "run")); @@ -243,7 +320,11 @@ TEST(FTSQueryImpl, TextIndexVersion1LanguageEng) { // and no stopword list will be used. TEST(FTSQueryImpl, TextIndexVersion1LanguageInvalid) { FTSQueryImpl q; - ASSERT(q.parse("the running", "invalid", false, false, TEXT_INDEX_VERSION_1).isOK()); + q.setQuery("the running"); + q.setLanguage("invalid"); + q.setCaseSensitive(false); + q.setDiacriticSensitive(false); + ASSERT(q.parse(TEXT_INDEX_VERSION_1).isOK()); ASSERT_EQUALS(2U, q.getPositiveTerms().size()); ASSERT_EQUALS(1, std::count(q.getPositiveTerms().begin(), q.getPositiveTerms().end(), "the")); ASSERT_EQUALS(1, @@ -252,5 +333,45 @@ TEST(FTSQueryImpl, TextIndexVersion1LanguageInvalid) { ASSERT_EQUALS(0U, q.getPositivePhr().size()); ASSERT_EQUALS(0U, q.getNegatedPhr().size()); } + +TEST(FTSQueryImpl, CloneUnparsedQuery) { + FTSQueryImpl q; + q.setQuery("foo"); + q.setLanguage("bar"); + q.setCaseSensitive(true); + q.setDiacriticSensitive(true); + + auto clone = q.clone(); + ASSERT_EQUALS(clone->getQuery(), q.getQuery()); + ASSERT_EQUALS(clone->getLanguage(), q.getLanguage()); + ASSERT_EQUALS(clone->getCaseSensitive(), q.getCaseSensitive()); + ASSERT_EQUALS(clone->getDiacriticSensitive(), q.getDiacriticSensitive()); +} + +TEST(FTSQueryImpl, CloneParsedQuery) { + FTSQueryImpl q; + q.setQuery("Foo -bar \"baz\" -\"quux\""); + q.setLanguage("english"); + q.setCaseSensitive(true); + q.setDiacriticSensitive(true); + ASSERT_OK(q.parse(TEXT_INDEX_VERSION_3)); + ASSERT(std::set<std::string>({"Foo", "baz"}) == q.getPositiveTerms()); + ASSERT(std::set<std::string>({"bar"}) == q.getNegatedTerms()); + ASSERT(std::vector<std::string>({"baz"}) == q.getPositivePhr()); + ASSERT(std::vector<std::string>({"quux"}) == q.getNegatedPhr()); + ASSERT(std::set<std::string>({"foo", "baz"}) == q.getTermsForBounds()); + + auto clone = q.clone(); + ASSERT_EQUALS(clone->getQuery(), q.getQuery()); + ASSERT_EQUALS(clone->getLanguage(), q.getLanguage()); + ASSERT_EQUALS(clone->getCaseSensitive(), q.getCaseSensitive()); + ASSERT_EQUALS(clone->getDiacriticSensitive(), q.getDiacriticSensitive()); + FTSQueryImpl* castedClone = static_cast<FTSQueryImpl*>(clone.get()); + ASSERT(castedClone->getPositiveTerms() == q.getPositiveTerms()); + ASSERT(castedClone->getNegatedTerms() == q.getNegatedTerms()); + ASSERT(castedClone->getPositivePhr() == q.getPositivePhr()); + ASSERT(castedClone->getNegatedPhr() == q.getNegatedPhr()); + ASSERT(castedClone->getTermsForBounds() == q.getTermsForBounds()); +} } } diff --git a/src/mongo/db/fts/fts_query_noop.cpp b/src/mongo/db/fts/fts_query_noop.cpp new file mode 100644 index 00000000000..d6da988a3e0 --- /dev/null +++ b/src/mongo/db/fts/fts_query_noop.cpp @@ -0,0 +1,48 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/db/fts/fts_query_noop.h" + +#include "mongo/stdx/memory.h" + +namespace mongo { +namespace fts { + +std::unique_ptr<FTSQuery> FTSQueryNoop::clone() const { + auto clonedQuery = stdx::make_unique<FTSQueryNoop>(); + clonedQuery->setQuery(getQuery()); + clonedQuery->setLanguage(getLanguage()); + clonedQuery->setCaseSensitive(getCaseSensitive()); + clonedQuery->setDiacriticSensitive(getDiacriticSensitive()); + return std::move(clonedQuery); +} + +} // namespace fts +} // namespace mongo diff --git a/src/mongo/db/fts/fts_query_noop.h b/src/mongo/db/fts/fts_query_noop.h new file mode 100644 index 00000000000..b24a8f572a1 --- /dev/null +++ b/src/mongo/db/fts/fts_query_noop.h @@ -0,0 +1,49 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include "mongo/db/fts/fts_query.h" + +namespace mongo { +namespace fts { + +/** + * A no-op implementation of FTSQuery. + */ +class FTSQueryNoop final : public FTSQuery { +public: + Status parse(TextIndexVersion textIndexVersion) final { + return Status::OK(); + } + + std::unique_ptr<FTSQuery> clone() const final; +}; + +} // namespace fts +} // namespace mongo diff --git a/src/mongo/db/fts/fts_query_noop_test.cpp b/src/mongo/db/fts/fts_query_noop_test.cpp new file mode 100644 index 00000000000..6876893c6e9 --- /dev/null +++ b/src/mongo/db/fts/fts_query_noop_test.cpp @@ -0,0 +1,57 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/db/fts/fts_query_noop.h" +#include "mongo/unittest/unittest.h" + +namespace mongo { +namespace fts { + +TEST(FTSQueryNoop, Parse) { + FTSQueryNoop q; + ASSERT_OK(q.parse(TEXT_INDEX_VERSION_INVALID)); +} + +TEST(FTSQueryNoop, Clone) { + FTSQueryNoop q; + q.setQuery("foo"); + q.setLanguage("bar"); + q.setCaseSensitive(true); + q.setDiacriticSensitive(true); + + auto clone = q.clone(); + ASSERT_EQUALS(clone->getQuery(), q.getQuery()); + ASSERT_EQUALS(clone->getLanguage(), q.getLanguage()); + ASSERT_EQUALS(clone->getCaseSensitive(), q.getCaseSensitive()); + ASSERT_EQUALS(clone->getDiacriticSensitive(), q.getDiacriticSensitive()); +} + +} // namespace fts +} // namespace mongo diff --git a/src/mongo/db/fts/fts_util.h b/src/mongo/db/fts/fts_util.h index b9fed70a8e8..7286e6fc7a6 100644 --- a/src/mongo/db/fts/fts_util.h +++ b/src/mongo/db/fts/fts_util.h @@ -42,9 +42,10 @@ extern const std::string WILDCARD; extern const std::string INDEX_NAME; enum TextIndexVersion { - TEXT_INDEX_VERSION_1 = 1, // Legacy index format. Deprecated. - TEXT_INDEX_VERSION_2 = 2, // Index format with ASCII support and murmur hashing. - TEXT_INDEX_VERSION_3 = 3, // Current index format with basic Unicode support. + TEXT_INDEX_VERSION_INVALID = 0, // Invalid value. + TEXT_INDEX_VERSION_1 = 1, // Legacy index format. Deprecated. + TEXT_INDEX_VERSION_2 = 2, // Index format with ASCII support and murmur hashing. + TEXT_INDEX_VERSION_3 = 3, // Current index format with basic Unicode support. }; } } diff --git a/src/mongo/db/matcher/SConscript b/src/mongo/db/matcher/SConscript index af686b9f96b..d8c4fbd8299 100644 --- a/src/mongo/db/matcher/SConscript +++ b/src/mongo/db/matcher/SConscript @@ -47,6 +47,7 @@ env.Library( '$BUILD_DIR/mongo/base', '$BUILD_DIR/mongo/bson/util/bson_extract', '$BUILD_DIR/mongo/db/common', + '$BUILD_DIR/mongo/db/fts/fts_query_noop', '$BUILD_DIR/third_party/shim_pcrecpp', 'path', ], @@ -123,26 +124,19 @@ env.CppUnitTest( ) env.Library( - target='expressions_text', - source=[ - 'expression_text.cpp', - ], - LIBDEPS=[ - '$BUILD_DIR/mongo/db/fts/base', - 'expressions', - ], -) - -env.Library( target='expressions_mongod_only', source=[ 'extensions_callback_real.cpp', + 'expression_text.cpp', 'expression_where.cpp', ], LIBDEPS=[ '$BUILD_DIR/mongo/db/auth/authorization_manager_global', '$BUILD_DIR/mongo/scripting/scripting_server', 'expressions', - 'expressions_text', + ], + LIBDEPS_TAGS=[ + # Depends on symbols from serverOnlyFiles. + 'incomplete', ], ) diff --git a/src/mongo/db/matcher/expression_text.cpp b/src/mongo/db/matcher/expression_text.cpp index 8ee279efc1d..b97c2ce2780 100644 --- a/src/mongo/db/matcher/expression_text.cpp +++ b/src/mongo/db/matcher/expression_text.cpp @@ -32,34 +32,76 @@ #include "mongo/db/matcher/expression_text.h" +#include "mongo/db/catalog/collection.h" +#include "mongo/db/db_raii.h" #include "mongo/db/fts/fts_language.h" +#include "mongo/db/fts/fts_spec.h" +#include "mongo/db/index/fts_access_method.h" #include "mongo/stdx/memory.h" namespace mongo { -TextMatchExpression::TextMatchExpression(TextParams params) - : TextMatchExpressionBase(std::move(params)) {} +Status TextMatchExpression::init(OperationContext* txn, + const NamespaceString& nss, + TextParams params) { + _ftsQuery.setQuery(std::move(params.query)); + _ftsQuery.setLanguage(std::move(params.language)); + _ftsQuery.setCaseSensitive(params.caseSensitive); + _ftsQuery.setDiacriticSensitive(params.diacriticSensitive); -Status TextMatchExpression::init() { - // Validate language, but defer construction of FTSQueryImpl (which requires access to the - // target namespace) until stage building time. - if (!getLanguage().empty()) { - if (!fts::FTSLanguage::make(getLanguage(), fts::TEXT_INDEX_VERSION_2).isOK()) { - return {ErrorCodes::BadValue, "$language specifies unsupported language"}; + fts::TextIndexVersion version; + { + // Find text index. + ScopedTransaction transaction(txn, MODE_IS); + AutoGetDb autoDb(txn, nss.db(), MODE_IS); + Lock::CollectionLock collLock(txn->lockState(), nss.ns(), MODE_IS); + Database* db = autoDb.getDb(); + if (!db) { + return {ErrorCodes::IndexNotFound, + str::stream() << "text index required for $text query (no such collection '" + << nss.ns() << "')"}; } + Collection* collection = db->getCollection(nss); + if (!collection) { + return {ErrorCodes::IndexNotFound, + str::stream() << "text index required for $text query (no such collection '" + << nss.ns() << "')"}; + } + std::vector<IndexDescriptor*> idxMatches; + collection->getIndexCatalog()->findIndexByType(txn, IndexNames::TEXT, idxMatches); + if (idxMatches.empty()) { + return {ErrorCodes::IndexNotFound, "text index required for $text query"}; + } + if (idxMatches.size() > 1) { + return {ErrorCodes::IndexNotFound, "more than one text index found for $text query"}; + } + invariant(idxMatches.size() == 1); + IndexDescriptor* index = idxMatches[0]; + const FTSAccessMethod* fam = + static_cast<FTSAccessMethod*>(collection->getIndexCatalog()->getIndex(index)); + invariant(fam); + + // Extract version and default language from text index. + version = fam->getSpec().getTextIndexVersion(); + if (_ftsQuery.getLanguage().empty()) { + _ftsQuery.setLanguage(fam->getSpec().defaultLanguage().str()); + } + } + + Status parseStatus = _ftsQuery.parse(version); + if (!parseStatus.isOK()) { + return parseStatus; } return initPath("_fts"); } std::unique_ptr<MatchExpression> TextMatchExpression::shallowClone() const { - TextParams params; - params.query = getQuery(); - params.language = getLanguage(); - params.caseSensitive = getCaseSensitive(); - params.diacriticSensitive = getDiacriticSensitive(); - auto expr = stdx::make_unique<TextMatchExpression>(std::move(params)); - expr->init(); + auto expr = stdx::make_unique<TextMatchExpression>(); + // We initialize _ftsQuery here directly rather than calling init(), to avoid needing to examine + // the index catalog. + expr->_ftsQuery = _ftsQuery; + invariantOK(expr->initPath("_fts")); if (getTag()) { expr->setTag(getTag()->clone()); } diff --git a/src/mongo/db/matcher/expression_text.h b/src/mongo/db/matcher/expression_text.h index dfe4cbd8204..3bd44b29da3 100644 --- a/src/mongo/db/matcher/expression_text.h +++ b/src/mongo/db/matcher/expression_text.h @@ -30,17 +30,27 @@ #pragma once +#include "mongo/db/fts/fts_query_impl.h" #include "mongo/db/matcher/expression_text_base.h" +#include "mongo/db/namespace_string.h" namespace mongo { +class NamespaceString; +class OperationContext; + class TextMatchExpression : public TextMatchExpressionBase { public: - TextMatchExpression(TextParams params); + Status init(OperationContext* txn, const NamespaceString& nss, TextParams params); - Status init(); + const fts::FTSQuery& getFTSQuery() const final { + return _ftsQuery; + } std::unique_ptr<MatchExpression> shallowClone() const final; + +private: + fts::FTSQueryImpl _ftsQuery; }; } // namespace mongo diff --git a/src/mongo/db/matcher/expression_text_base.cpp b/src/mongo/db/matcher/expression_text_base.cpp index 04b62ecd6b1..82fa7f9fd3e 100644 --- a/src/mongo/db/matcher/expression_text_base.cpp +++ b/src/mongo/db/matcher/expression_text_base.cpp @@ -30,23 +30,21 @@ #include "mongo/db/matcher/expression_text_base.h" +#include "mongo/db/fts/fts_query.h" + namespace mongo { const bool TextMatchExpressionBase::kCaseSensitiveDefault = false; const bool TextMatchExpressionBase::kDiacriticSensitiveDefault = false; -TextMatchExpressionBase::TextMatchExpressionBase(TextParams params) - : LeafMatchExpression(TEXT), - _query(std::move(params.query)), - _language(std::move(params.language)), - _caseSensitive(params.caseSensitive), - _diacriticSensitive(params.diacriticSensitive) {} +TextMatchExpressionBase::TextMatchExpressionBase() : LeafMatchExpression(TEXT) {} void TextMatchExpressionBase::debugString(StringBuilder& debug, int level) const { + const fts::FTSQuery& ftsQuery = getFTSQuery(); _debugAddSpace(debug, level); - debug << "TEXT : query=" << _query << ", language=" << _language - << ", caseSensitive=" << _caseSensitive << ", diacriticSensitive=" << _diacriticSensitive - << ", tag="; + debug << "TEXT : query=" << ftsQuery.getQuery() << ", language=" << ftsQuery.getLanguage() + << ", caseSensitive=" << ftsQuery.getCaseSensitive() + << ", diacriticSensitive=" << ftsQuery.getDiacriticSensitive() << ", tag="; MatchExpression::TagData* td = getTag(); if (NULL != td) { td->debugString(&debug); @@ -57,9 +55,11 @@ void TextMatchExpressionBase::debugString(StringBuilder& debug, int level) const } void TextMatchExpressionBase::toBSON(BSONObjBuilder* out) const { + const fts::FTSQuery& ftsQuery = getFTSQuery(); out->append("$text", - BSON("$search" << _query << "$language" << _language << "$caseSensitive" - << _caseSensitive << "$diacriticSensitive" << _diacriticSensitive)); + BSON("$search" << ftsQuery.getQuery() << "$language" << ftsQuery.getLanguage() + << "$caseSensitive" << ftsQuery.getCaseSensitive() + << "$diacriticSensitive" << ftsQuery.getDiacriticSensitive())); } bool TextMatchExpressionBase::equivalent(const MatchExpression* other) const { @@ -68,19 +68,7 @@ bool TextMatchExpressionBase::equivalent(const MatchExpression* other) const { } const TextMatchExpressionBase* realOther = static_cast<const TextMatchExpressionBase*>(other); - if (realOther->getQuery() != _query) { - return false; - } - if (realOther->getLanguage() != _language) { - return false; - } - if (realOther->getCaseSensitive() != _caseSensitive) { - return false; - } - if (realOther->getDiacriticSensitive() != _diacriticSensitive) { - return false; - } - return true; + return getFTSQuery().equivalent(realOther->getFTSQuery()); } } // namespace mongo diff --git a/src/mongo/db/matcher/expression_text_base.h b/src/mongo/db/matcher/expression_text_base.h index 8ca25cf60eb..187a91bb2d4 100644 --- a/src/mongo/db/matcher/expression_text_base.h +++ b/src/mongo/db/matcher/expression_text_base.h @@ -32,6 +32,10 @@ namespace mongo { +namespace fts { +class FTSQuery; +} // namespace fts + /** * Common base class for $text match expression implementations. */ @@ -47,23 +51,12 @@ public: static const bool kCaseSensitiveDefault; static const bool kDiacriticSensitiveDefault; - TextMatchExpressionBase(TextParams params); + TextMatchExpressionBase(); - const std::string& getQuery() const { - return _query; - } - - const std::string& getLanguage() const { - return _language; - } - - bool getCaseSensitive() const { - return _caseSensitive; - } - - bool getDiacriticSensitive() const { - return _diacriticSensitive; - } + /** + * Returns a reference to the parsed text query that this TextMatchExpressionBase owns. + */ + virtual const fts::FTSQuery& getFTSQuery() const = 0; // // Methods inherited from MatchExpression. @@ -89,12 +82,6 @@ public: void toBSON(BSONObjBuilder* out) const final; bool equivalent(const MatchExpression* other) const final; - -private: - const std::string _query; - const std::string _language; - const bool _caseSensitive; - const bool _diacriticSensitive; }; } // namespace mongo diff --git a/src/mongo/db/matcher/expression_text_noop.cpp b/src/mongo/db/matcher/expression_text_noop.cpp index 7d2fb5c84b7..bfd61a54081 100644 --- a/src/mongo/db/matcher/expression_text_noop.cpp +++ b/src/mongo/db/matcher/expression_text_noop.cpp @@ -34,21 +34,24 @@ namespace mongo { -TextNoOpMatchExpression::TextNoOpMatchExpression(TextParams params) - : TextMatchExpressionBase(std::move(params)) {} - -Status TextNoOpMatchExpression::init() { +Status TextNoOpMatchExpression::init(TextParams params) { + _ftsQuery.setQuery(std::move(params.query)); + _ftsQuery.setLanguage(std::move(params.language)); + _ftsQuery.setCaseSensitive(params.caseSensitive); + _ftsQuery.setDiacriticSensitive(params.diacriticSensitive); + invariantOK(_ftsQuery.parse(fts::TEXT_INDEX_VERSION_INVALID)); return initPath("_fts"); } std::unique_ptr<MatchExpression> TextNoOpMatchExpression::shallowClone() const { TextParams params; - params.query = getQuery(); - params.language = getLanguage(); - params.caseSensitive = getCaseSensitive(); - params.diacriticSensitive = getDiacriticSensitive(); - auto expr = stdx::make_unique<TextNoOpMatchExpression>(std::move(params)); - expr->init(); + params.query = _ftsQuery.getQuery(); + params.language = _ftsQuery.getLanguage(); + params.caseSensitive = _ftsQuery.getCaseSensitive(); + params.diacriticSensitive = _ftsQuery.getDiacriticSensitive(); + + auto expr = stdx::make_unique<TextNoOpMatchExpression>(); + invariantOK(expr->init(std::move(params))); if (getTag()) { expr->setTag(getTag()->clone()); } diff --git a/src/mongo/db/matcher/expression_text_noop.h b/src/mongo/db/matcher/expression_text_noop.h index de02bcfa3cf..8e01d40e70b 100644 --- a/src/mongo/db/matcher/expression_text_noop.h +++ b/src/mongo/db/matcher/expression_text_noop.h @@ -28,17 +28,23 @@ #pragma once +#include "mongo/db/fts/fts_query_noop.h" #include "mongo/db/matcher/expression_text_base.h" namespace mongo { class TextNoOpMatchExpression : public TextMatchExpressionBase { public: - TextNoOpMatchExpression(TextParams params); + Status init(TextParams params); - Status init(); + const fts::FTSQuery& getFTSQuery() const final { + return _ftsQuery; + } std::unique_ptr<MatchExpression> shallowClone() const final; + +private: + fts::FTSQueryNoop _ftsQuery; }; } // namespace mongo diff --git a/src/mongo/db/matcher/extensions_callback_noop.cpp b/src/mongo/db/matcher/extensions_callback_noop.cpp index e5941f73562..10026370e64 100644 --- a/src/mongo/db/matcher/extensions_callback_noop.cpp +++ b/src/mongo/db/matcher/extensions_callback_noop.cpp @@ -41,8 +41,8 @@ StatusWithMatchExpression ExtensionsCallbackNoop::parseText(BSONElement text) co return textParams.getStatus(); } - auto expr = stdx::make_unique<TextNoOpMatchExpression>(std::move(textParams.getValue())); - Status initStatus = expr->init(); + auto expr = stdx::make_unique<TextNoOpMatchExpression>(); + Status initStatus = expr->init(std::move(textParams.getValue())); if (!initStatus.isOK()) { return initStatus; } diff --git a/src/mongo/db/matcher/extensions_callback_real.cpp b/src/mongo/db/matcher/extensions_callback_real.cpp index 161e0fb2e79..0b7e815ca58 100644 --- a/src/mongo/db/matcher/extensions_callback_real.cpp +++ b/src/mongo/db/matcher/extensions_callback_real.cpp @@ -45,8 +45,8 @@ StatusWithMatchExpression ExtensionsCallbackReal::parseText(BSONElement text) co return textParams.getStatus(); } - auto exp = stdx::make_unique<TextMatchExpression>(std::move(textParams.getValue())); - Status status = exp->init(); + auto exp = stdx::make_unique<TextMatchExpression>(); + Status status = exp->init(_txn, *_nss, std::move(textParams.getValue())); if (!status.isOK()) { return status; } diff --git a/src/mongo/db/query/SConscript b/src/mongo/db/query/SConscript index d25d0a0a909..cc4fcec81c6 100644 --- a/src/mongo/db/query/SConscript +++ b/src/mongo/db/query/SConscript @@ -26,7 +26,6 @@ env.Library( "$BUILD_DIR/mongo/db/index/expression_params", "$BUILD_DIR/mongo/db/matcher/expression_algo", "$BUILD_DIR/mongo/db/matcher/expressions", - "$BUILD_DIR/mongo/db/matcher/expressions_text", "$BUILD_DIR/mongo/db/index_names", "$BUILD_DIR/mongo/db/server_parameters", "command_request_response", @@ -199,7 +198,6 @@ env.CppUnitTest( ], LIBDEPS=[ "query_planner", - "$BUILD_DIR/mongo/db/matcher/expressions_text", ], ) diff --git a/src/mongo/db/query/planner_access.cpp b/src/mongo/db/query/planner_access.cpp index 72f6fca93ca..54bcc0b2311 100644 --- a/src/mongo/db/query/planner_access.cpp +++ b/src/mongo/db/query/planner_access.cpp @@ -194,13 +194,10 @@ QuerySolutionNode* QueryPlannerAccess::makeLeafNode( } else if (MatchExpression::TEXT == expr->matchType()) { // We must not keep the expression node around. *tightnessOut = IndexBoundsBuilder::EXACT; - TextMatchExpression* textExpr = static_cast<TextMatchExpression*>(expr); + TextMatchExpressionBase* textExpr = static_cast<TextMatchExpressionBase*>(expr); TextNode* ret = new TextNode(); ret->indexKeyPattern = index.keyPattern; - ret->query = textExpr->getQuery(); - ret->language = textExpr->getLanguage(); - ret->caseSensitive = textExpr->getCaseSensitive(); - ret->diacriticSensitive = textExpr->getDiacriticSensitive(); + ret->ftsQuery = textExpr->getFTSQuery().clone(); return ret; } else { // Note that indexKeyPattern.firstElement().fieldName() may not equal expr->path() diff --git a/src/mongo/db/query/planner_ixselect.h b/src/mongo/db/query/planner_ixselect.h index b782e6d8560..2928b708677 100644 --- a/src/mongo/db/query/planner_ixselect.h +++ b/src/mongo/db/query/planner_ixselect.h @@ -31,6 +31,7 @@ #include "mongo/db/query/canonical_query.h" #include "mongo/db/query/index_entry.h" #include "mongo/db/query/query_solution.h" +#include "mongo/platform/unordered_set.h" namespace mongo { diff --git a/src/mongo/db/query/query_planner_test_lib.cpp b/src/mongo/db/query/query_planner_test_lib.cpp index 774b00368a9..557bde5f252 100644 --- a/src/mongo/db/query/query_planner_test_lib.cpp +++ b/src/mongo/db/query/query_planner_test_lib.cpp @@ -297,28 +297,28 @@ bool QueryPlannerTestLib::solutionMatches(const BSONObj& testSoln, BSONElement searchElt = textObj["search"]; if (!searchElt.eoo()) { - if (searchElt.String() != node->query) { + if (searchElt.String() != node->ftsQuery->getQuery()) { return false; } } BSONElement languageElt = textObj["language"]; if (!languageElt.eoo()) { - if (languageElt.String() != node->language) { + if (languageElt.String() != node->ftsQuery->getLanguage()) { return false; } } BSONElement caseSensitiveElt = textObj["caseSensitive"]; if (!caseSensitiveElt.eoo()) { - if (caseSensitiveElt.trueValue() != node->caseSensitive) { + if (caseSensitiveElt.trueValue() != node->ftsQuery->getCaseSensitive()) { return false; } } BSONElement diacriticSensitiveElt = textObj["diacriticSensitive"]; if (!diacriticSensitiveElt.eoo()) { - if (diacriticSensitiveElt.trueValue() != node->diacriticSensitive) { + if (diacriticSensitiveElt.trueValue() != node->ftsQuery->getDiacriticSensitive()) { return false; } } diff --git a/src/mongo/db/query/query_solution.cpp b/src/mongo/db/query/query_solution.cpp index 345a4132b0c..a80f9199b33 100644 --- a/src/mongo/db/query/query_solution.cpp +++ b/src/mongo/db/query/query_solution.cpp @@ -73,13 +73,13 @@ void TextNode::appendToString(mongoutils::str::stream* ss, int indent) const { addIndent(ss, indent + 1); *ss << "keyPattern = " << indexKeyPattern.toString() << '\n'; addIndent(ss, indent + 1); - *ss << "query = " << query << '\n'; + *ss << "query = " << ftsQuery->getQuery() << '\n'; addIndent(ss, indent + 1); - *ss << "language = " << language << '\n'; + *ss << "language = " << ftsQuery->getLanguage() << '\n'; addIndent(ss, indent + 1); - *ss << "caseSensitive= " << caseSensitive << '\n'; + *ss << "caseSensitive= " << ftsQuery->getCaseSensitive() << '\n'; addIndent(ss, indent + 1); - *ss << "diacriticSensitive= " << diacriticSensitive << '\n'; + *ss << "diacriticSensitive= " << ftsQuery->getDiacriticSensitive() << '\n'; addIndent(ss, indent + 1); *ss << "indexPrefix = " << indexPrefix.toString() << '\n'; if (NULL != filter) { @@ -95,10 +95,7 @@ QuerySolutionNode* TextNode::clone() const { copy->_sort = this->_sort; copy->indexKeyPattern = this->indexKeyPattern; - copy->query = this->query; - copy->language = this->language; - copy->caseSensitive = this->caseSensitive; - copy->diacriticSensitive = this->diacriticSensitive; + copy->ftsQuery = this->ftsQuery->clone(); 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 e3fd9d9dd3c..4df3e3e8665 100644 --- a/src/mongo/db/query/query_solution.h +++ b/src/mongo/db/query/query_solution.h @@ -32,15 +32,13 @@ #include "mongo/db/jsobj.h" #include "mongo/db/matcher/expression.h" -#include "mongo/db/fts/fts_query_impl.h" +#include "mongo/db/fts/fts_query.h" #include "mongo/db/query/index_bounds.h" #include "mongo/db/query/plan_cache.h" #include "mongo/db/query/stage_types.h" namespace mongo { -using mongo::fts::FTSQueryImpl; - class GeoNearExpression; /** @@ -247,10 +245,7 @@ struct TextNode : public QuerySolutionNode { BSONObjSet _sort; BSONObj indexKeyPattern; - std::string query; - std::string language; - bool caseSensitive; - bool diacriticSensitive; + std::unique_ptr<fts::FTSQuery> ftsQuery; // "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 d23c16ad38b..f3344921e6b 100644 --- a/src/mongo/db/query/stage_builder.cpp +++ b/src/mongo/db/query/stage_builder.cpp @@ -256,40 +256,22 @@ PlanStage* buildStages(OperationContext* txn, return new GeoNear2DSphereStage(params, txn, ws, collection, s2Index); } else if (STAGE_TEXT == root->getType()) { const TextNode* node = static_cast<const TextNode*>(root); - - if (NULL == collection) { - warning() << "Null collection for text"; - return NULL; - } - vector<IndexDescriptor*> idxMatches; - collection->getIndexCatalog()->findIndexByType(txn, "text", idxMatches); - if (1 != idxMatches.size()) { - warning() << "No text index, or more than one text index"; - return NULL; - } - IndexDescriptor* index = idxMatches[0]; + IndexDescriptor* desc = + collection->getIndexCatalog()->findIndexByKeyPattern(txn, node->indexKeyPattern); + invariant(desc); const FTSAccessMethod* fam = - static_cast<FTSAccessMethod*>(collection->getIndexCatalog()->getIndex(index)); - TextStageParams params(fam->getSpec()); + static_cast<FTSAccessMethod*>(collection->getIndexCatalog()->getIndex(desc)); + invariant(fam); - // params.collection = collection; - params.index = index; + TextStageParams params(fam->getSpec()); + params.index = desc; params.spec = fam->getSpec(); params.indexPrefix = node->indexPrefix; - - const std::string& language = - ("" == node->language ? fam->getSpec().defaultLanguage().str() : node->language); - - Status parseStatus = params.query.parse(node->query, - language, - node->caseSensitive, - node->diacriticSensitive, - fam->getSpec().getTextIndexVersion()); - if (!parseStatus.isOK()) { - warning() << "Can't parse text search query"; - return NULL; - } - + // We assume here that node->ftsQuery is an FTSQueryImpl, not an FTSQueryNoop. In practice, + // this means that it is illegal to use the StageBuilder on a QuerySolution created by + // planning a query that contains "no-op" expressions. TODO: make StageBuilder::build() + // fail in this case (this improvement is being tracked by SERVER-21510). + params.query = static_cast<FTSQueryImpl&>(*node->ftsQuery); return new TextStage(txn, params, ws, node->filter.get()); } else if (STAGE_SHARDING_FILTER == root->getType()) { const ShardingFilterNode* fn = static_cast<const ShardingFilterNode*>(root); diff --git a/src/mongo/dbtests/extensions_callback_real_test.cpp b/src/mongo/dbtests/extensions_callback_real_test.cpp index b4c2291810f..56465cb2c7e 100644 --- a/src/mongo/dbtests/extensions_callback_real_test.cpp +++ b/src/mongo/dbtests/extensions_callback_real_test.cpp @@ -28,196 +28,280 @@ #include "mongo/platform/basic.h" +#include "mongo/db/db_raii.h" #include "mongo/db/json.h" #include "mongo/db/matcher/expression_text.h" #include "mongo/db/matcher/extensions_callback_real.h" #include "mongo/db/namespace_string.h" #include "mongo/db/operation_context_impl.h" +#include "mongo/dbtests/dbtests.h" #include "mongo/unittest/unittest.h" namespace mongo { namespace { -const NamespaceString nss("unittests.extensions_callback_real_test"); - // // $text parsing tests. // -TEST(ExtensionsCallbackReal, TextBasic) { - OperationContextImpl txn; +class ExtensionsCallbackRealTest : public unittest::Test { +public: + ExtensionsCallbackRealTest() : _nss("unittests.extensions_callback_real_test") {} + + void setUp() final { + AutoGetOrCreateDb autoDb(&_txn, _nss.db(), MODE_X); + Database* database = autoDb.getDb(); + { + WriteUnitOfWork wunit(&_txn); + ASSERT(database->createCollection(&_txn, _nss.ns())); + wunit.commit(); + } + } + + void tearDown() final { + AutoGetDb autoDb(&_txn, _nss.db(), MODE_X); + Database* database = autoDb.getDb(); + if (!database) { + return; + } + { + WriteUnitOfWork wunit(&_txn); + static_cast<void>(database->dropCollection(&_txn, _nss.ns())); + wunit.commit(); + } + } + +protected: + OperationContextImpl _txn; + const NamespaceString _nss; +}; + +TEST_F(ExtensionsCallbackRealTest, TextNoIndex) { + BSONObj query = fromjson("{$text: {$search:\"awesome\"}}"); + StatusWithMatchExpression result = + ExtensionsCallbackReal(&_txn, &_nss).parseText(query.firstElement()); + + ASSERT_NOT_OK(result.getStatus()); + ASSERT_EQ(ErrorCodes::IndexNotFound, result.getStatus()); +} + +TEST_F(ExtensionsCallbackRealTest, TextBasic) { + ASSERT_OK(dbtests::createIndex(&_txn, + _nss.ns(), + BSON("a" + << "text"), + false)); // isUnique + BSONObj query = fromjson("{$text: {$search:\"awesome\", $language:\"english\"}}"); auto expr = - unittest::assertGet(ExtensionsCallbackReal(&txn, &nss).parseText(query.firstElement())); + unittest::assertGet(ExtensionsCallbackReal(&_txn, &_nss).parseText(query.firstElement())); ASSERT_EQUALS(MatchExpression::TEXT, expr->matchType()); std::unique_ptr<TextMatchExpression> textExpr( static_cast<TextMatchExpression*>(expr.release())); - ASSERT_EQUALS(textExpr->getQuery(), "awesome"); - ASSERT_EQUALS(textExpr->getLanguage(), "english"); - ASSERT_EQUALS(textExpr->getCaseSensitive(), TextMatchExpressionBase::kCaseSensitiveDefault); - ASSERT_EQUALS(textExpr->getDiacriticSensitive(), + ASSERT_EQUALS(textExpr->getFTSQuery().getQuery(), "awesome"); + ASSERT_EQUALS(textExpr->getFTSQuery().getLanguage(), "english"); + ASSERT_EQUALS(textExpr->getFTSQuery().getCaseSensitive(), + TextMatchExpressionBase::kCaseSensitiveDefault); + ASSERT_EQUALS(textExpr->getFTSQuery().getDiacriticSensitive(), TextMatchExpressionBase::kDiacriticSensitiveDefault); } -TEST(ExtensionsCallbackReal, TextLanguageError) { - OperationContextImpl txn; +TEST_F(ExtensionsCallbackRealTest, TextLanguageError) { + ASSERT_OK(dbtests::createIndex(&_txn, + _nss.ns(), + BSON("a" + << "text"), + false)); // isUnique + BSONObj query = fromjson("{$text: {$search:\"awesome\", $language:\"spanglish\"}}"); StatusWithMatchExpression result = - ExtensionsCallbackReal(&txn, &nss).parseText(query.firstElement()); + ExtensionsCallbackReal(&_txn, &_nss).parseText(query.firstElement()); ASSERT_NOT_OK(result.getStatus()); } -TEST(ExtensionsCallbackReal, TextCaseSensitiveTrue) { - OperationContextImpl txn; +TEST_F(ExtensionsCallbackRealTest, TextCaseSensitiveTrue) { + ASSERT_OK(dbtests::createIndex(&_txn, + _nss.ns(), + BSON("a" + << "text"), + false)); // isUnique + BSONObj query = fromjson("{$text: {$search:\"awesome\", $caseSensitive: true}}"); auto expr = - unittest::assertGet(ExtensionsCallbackReal(&txn, &nss).parseText(query.firstElement())); + unittest::assertGet(ExtensionsCallbackReal(&_txn, &_nss).parseText(query.firstElement())); ASSERT_EQUALS(MatchExpression::TEXT, expr->matchType()); std::unique_ptr<TextMatchExpression> textExpr( static_cast<TextMatchExpression*>(expr.release())); - ASSERT_EQUALS(textExpr->getCaseSensitive(), true); + ASSERT_EQUALS(textExpr->getFTSQuery().getCaseSensitive(), true); } -TEST(ExtensionsCallbackReal, TextCaseSensitiveFalse) { - OperationContextImpl txn; +TEST_F(ExtensionsCallbackRealTest, TextCaseSensitiveFalse) { + ASSERT_OK(dbtests::createIndex(&_txn, + _nss.ns(), + BSON("a" + << "text"), + false)); // isUnique + BSONObj query = fromjson("{$text: {$search:\"awesome\", $caseSensitive: false}}"); auto expr = - unittest::assertGet(ExtensionsCallbackReal(&txn, &nss).parseText(query.firstElement())); + unittest::assertGet(ExtensionsCallbackReal(&_txn, &_nss).parseText(query.firstElement())); ASSERT_EQUALS(MatchExpression::TEXT, expr->matchType()); std::unique_ptr<TextMatchExpression> textExpr( static_cast<TextMatchExpression*>(expr.release())); - ASSERT_EQUALS(textExpr->getCaseSensitive(), false); + ASSERT_EQUALS(textExpr->getFTSQuery().getCaseSensitive(), false); } -TEST(ExtensionsCallbackReal, TextCaseSensitiveError) { - OperationContextImpl txn; +TEST_F(ExtensionsCallbackRealTest, TextCaseSensitiveError) { + ASSERT_OK(dbtests::createIndex(&_txn, + _nss.ns(), + BSON("a" + << "text"), + false)); // isUnique + BSONObj query = fromjson("{$text:{$search:\"awesome\", $caseSensitive: 0}}"); StatusWithMatchExpression result = - ExtensionsCallbackReal(&txn, &nss).parseText(query.firstElement()); + ExtensionsCallbackReal(&_txn, &_nss).parseText(query.firstElement()); ASSERT_NOT_OK(result.getStatus()); } -TEST(ExtensionsCallbackReal, TextDiacriticSensitiveTrue) { - OperationContextImpl txn; +TEST_F(ExtensionsCallbackRealTest, TextDiacriticSensitiveTrue) { + ASSERT_OK(dbtests::createIndex(&_txn, + _nss.ns(), + BSON("a" + << "text"), + false)); // isUnique + BSONObj query = fromjson("{$text: {$search:\"awesome\", $diacriticSensitive: true}}"); auto expr = - unittest::assertGet(ExtensionsCallbackReal(&txn, &nss).parseText(query.firstElement())); + unittest::assertGet(ExtensionsCallbackReal(&_txn, &_nss).parseText(query.firstElement())); ASSERT_EQUALS(MatchExpression::TEXT, expr->matchType()); std::unique_ptr<TextMatchExpression> textExpr( static_cast<TextMatchExpression*>(expr.release())); - ASSERT_EQUALS(textExpr->getDiacriticSensitive(), true); + ASSERT_EQUALS(textExpr->getFTSQuery().getDiacriticSensitive(), true); } -TEST(ExtensionsCallbackReal, TextDiacriticSensitiveFalse) { - OperationContextImpl txn; +TEST_F(ExtensionsCallbackRealTest, TextDiacriticSensitiveFalse) { + ASSERT_OK(dbtests::createIndex(&_txn, + _nss.ns(), + BSON("a" + << "text"), + false)); // isUnique + BSONObj query = fromjson("{$text: {$search:\"awesome\", $diacriticSensitive: false}}"); auto expr = - unittest::assertGet(ExtensionsCallbackReal(&txn, &nss).parseText(query.firstElement())); + unittest::assertGet(ExtensionsCallbackReal(&_txn, &_nss).parseText(query.firstElement())); ASSERT_EQUALS(MatchExpression::TEXT, expr->matchType()); std::unique_ptr<TextMatchExpression> textExpr( static_cast<TextMatchExpression*>(expr.release())); - ASSERT_EQUALS(textExpr->getDiacriticSensitive(), false); + ASSERT_EQUALS(textExpr->getFTSQuery().getDiacriticSensitive(), false); } -TEST(ExtensionsCallbackReal, TextDiacriticSensitiveError) { - OperationContextImpl txn; +TEST_F(ExtensionsCallbackRealTest, TextDiacriticSensitiveError) { + ASSERT_OK(dbtests::createIndex(&_txn, + _nss.ns(), + BSON("a" + << "text"), + false)); // isUnique + BSONObj query = fromjson("{$text:{$search:\"awesome\", $diacriticSensitive: 0}}"); StatusWithMatchExpression result = - ExtensionsCallbackReal(&txn, &nss).parseText(query.firstElement()); + ExtensionsCallbackReal(&_txn, &_nss).parseText(query.firstElement()); ASSERT_NOT_OK(result.getStatus()); } -TEST(ExtensionsCallbackReal, TextDiacriticSensitiveAndCaseSensitiveTrue) { - OperationContextImpl txn; +TEST_F(ExtensionsCallbackRealTest, TextDiacriticSensitiveAndCaseSensitiveTrue) { + ASSERT_OK(dbtests::createIndex(&_txn, + _nss.ns(), + BSON("a" + << "text"), + false)); // isUnique + BSONObj query = fromjson("{$text: {$search:\"awesome\", $diacriticSensitive: true, $caseSensitive: true}}"); auto expr = - unittest::assertGet(ExtensionsCallbackReal(&txn, &nss).parseText(query.firstElement())); + unittest::assertGet(ExtensionsCallbackReal(&_txn, &_nss).parseText(query.firstElement())); ASSERT_EQUALS(MatchExpression::TEXT, expr->matchType()); std::unique_ptr<TextMatchExpression> textExpr( static_cast<TextMatchExpression*>(expr.release())); - ASSERT_EQUALS(textExpr->getDiacriticSensitive(), true); - ASSERT_EQUALS(textExpr->getCaseSensitive(), true); + ASSERT_EQUALS(textExpr->getFTSQuery().getDiacriticSensitive(), true); + ASSERT_EQUALS(textExpr->getFTSQuery().getCaseSensitive(), true); } // // $where parsing tests. // -TEST(ExtensionsCallbackReal, WhereExpressionsWithSameScopeHaveSameBSONRepresentation) { - OperationContextImpl txn; +TEST_F(ExtensionsCallbackRealTest, WhereExpressionsWithSameScopeHaveSameBSONRepresentation) { const char code[] = "function(){ return a; }"; BSONObj query1 = BSON("$where" << BSONCodeWScope(code, BSON("a" << true))); auto expr1 = - unittest::assertGet(ExtensionsCallbackReal(&txn, &nss).parseWhere(query1.firstElement())); + unittest::assertGet(ExtensionsCallbackReal(&_txn, &_nss).parseWhere(query1.firstElement())); BSONObjBuilder builder1; expr1->toBSON(&builder1); BSONObj query2 = BSON("$where" << BSONCodeWScope(code, BSON("a" << true))); auto expr2 = - unittest::assertGet(ExtensionsCallbackReal(&txn, &nss).parseWhere(query2.firstElement())); + unittest::assertGet(ExtensionsCallbackReal(&_txn, &_nss).parseWhere(query2.firstElement())); BSONObjBuilder builder2; expr2->toBSON(&builder2); ASSERT_EQ(builder1.obj(), builder2.obj()); } -TEST(ExtensionsCallbackReal, WhereExpressionsWithDifferentScopesHaveDifferentBSONRepresentations) { - OperationContextImpl txn; +TEST_F(ExtensionsCallbackRealTest, + WhereExpressionsWithDifferentScopesHaveDifferentBSONRepresentations) { const char code[] = "function(){ return a; }"; BSONObj query1 = BSON("$where" << BSONCodeWScope(code, BSON("a" << true))); auto expr1 = - unittest::assertGet(ExtensionsCallbackReal(&txn, &nss).parseWhere(query1.firstElement())); + unittest::assertGet(ExtensionsCallbackReal(&_txn, &_nss).parseWhere(query1.firstElement())); BSONObjBuilder builder1; expr1->toBSON(&builder1); BSONObj query2 = BSON("$where" << BSONCodeWScope(code, BSON("a" << false))); auto expr2 = - unittest::assertGet(ExtensionsCallbackReal(&txn, &nss).parseWhere(query2.firstElement())); + unittest::assertGet(ExtensionsCallbackReal(&_txn, &_nss).parseWhere(query2.firstElement())); BSONObjBuilder builder2; expr2->toBSON(&builder2); ASSERT_NE(builder1.obj(), builder2.obj()); } -TEST(ExtensionsCallbackReal, WhereExpressionsWithSameScopeAreEquivalent) { - OperationContextImpl txn; +TEST_F(ExtensionsCallbackRealTest, WhereExpressionsWithSameScopeAreEquivalent) { const char code[] = "function(){ return a; }"; BSONObj query1 = BSON("$where" << BSONCodeWScope(code, BSON("a" << true))); auto expr1 = - unittest::assertGet(ExtensionsCallbackReal(&txn, &nss).parseWhere(query1.firstElement())); + unittest::assertGet(ExtensionsCallbackReal(&_txn, &_nss).parseWhere(query1.firstElement())); BSONObj query2 = BSON("$where" << BSONCodeWScope(code, BSON("a" << true))); auto expr2 = - unittest::assertGet(ExtensionsCallbackReal(&txn, &nss).parseWhere(query2.firstElement())); + unittest::assertGet(ExtensionsCallbackReal(&_txn, &_nss).parseWhere(query2.firstElement())); ASSERT(expr1->equivalent(expr2.get())); ASSERT(expr2->equivalent(expr1.get())); } -TEST(ExtensionsCallbackReal, WhereExpressionsWithDifferentScopesAreNotEquivalent) { - OperationContextImpl txn; +TEST_F(ExtensionsCallbackRealTest, WhereExpressionsWithDifferentScopesAreNotEquivalent) { const char code[] = "function(){ return a; }"; BSONObj query1 = BSON("$where" << BSONCodeWScope(code, BSON("a" << true))); auto expr1 = - unittest::assertGet(ExtensionsCallbackReal(&txn, &nss).parseWhere(query1.firstElement())); + unittest::assertGet(ExtensionsCallbackReal(&_txn, &_nss).parseWhere(query1.firstElement())); BSONObj query2 = BSON("$where" << BSONCodeWScope(code, BSON("a" << false))); auto expr2 = - unittest::assertGet(ExtensionsCallbackReal(&txn, &nss).parseWhere(query2.firstElement())); + unittest::assertGet(ExtensionsCallbackReal(&_txn, &_nss).parseWhere(query2.firstElement())); ASSERT_FALSE(expr1->equivalent(expr2.get())); ASSERT_FALSE(expr2->equivalent(expr1.get())); diff --git a/src/mongo/s/chunk_manager.cpp b/src/mongo/s/chunk_manager.cpp index 223ad2b2825..2343924383d 100644 --- a/src/mongo/s/chunk_manager.cpp +++ b/src/mongo/s/chunk_manager.cpp @@ -560,10 +560,8 @@ void ChunkManager::getAllShardIds(set<ShardId>* all) const { IndexBounds ChunkManager::getIndexBoundsForQuery(const BSONObj& key, const CanonicalQuery& canonicalQuery) { - // $text is not allowed in planning since we don't have text index on mongos. - // - // TODO: Treat $text query as a no-op in planning. So with shard key {a: 1}, - // the query { a: 2, $text: { ... } } will only target to {a: 2}. + // TODO: special-casing TEXT here is no longer necessary. The work to remove this special case + // is being tracked at SERVER-21511. if (QueryPlannerCommon::hasNode(canonicalQuery.root(), MatchExpression::TEXT)) { IndexBounds bounds; IndexBoundsBuilder::allValuesBounds(key, &bounds); // [minKey, maxKey] |