summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Rassi <rassi@10gen.com>2015-11-05 17:46:37 -0500
committerJason Rassi <rassi@10gen.com>2015-11-12 16:28:55 -0500
commitbb62b1223f72c3ad8af0b77019c2ce3390d677a1 (patch)
tree758b09bc306ae24f8c25aa2fd59f81bccc981e58
parent0307dcde4cf50c4d245a6181e426343874950b4d (diff)
downloadmongo-bb62b1223f72c3ad8af0b77019c2ce3390d677a1.tar.gz
SERVER-19510 Refactor $text match expression parsing
- Text parsing no longer uses dedicated callback handle; responsibility moved to ExtensionsCallback. - Introduces TextMatchExpressionBase (new base class for existing class TextMatchExpression and new class TextNoOpMatchExpression).
-rw-r--r--src/mongo/base/error_codes.err2
-rw-r--r--src/mongo/db/exec/projection.h2
-rw-r--r--src/mongo/db/exec/stagedebug_cmd.cpp5
-rw-r--r--src/mongo/db/fts/fts_query.cpp3
-rw-r--r--src/mongo/db/fts/fts_query.h3
-rw-r--r--src/mongo/db/matcher/SConscript17
-rw-r--r--src/mongo/db/matcher/expression_parser.cpp83
-rw-r--r--src/mongo/db/matcher/expression_parser.h26
-rw-r--r--src/mongo/db/matcher/expression_parser_text.cpp109
-rw-r--r--src/mongo/db/matcher/expression_parser_text_test.cpp139
-rw-r--r--src/mongo/db/matcher/expression_text.cpp91
-rw-r--r--src/mongo/db/matcher/expression_text.h43
-rw-r--r--src/mongo/db/matcher/expression_text_base.cpp86
-rw-r--r--src/mongo/db/matcher/expression_text_base.h100
-rw-r--r--src/mongo/db/matcher/expression_text_noop.cpp58
-rw-r--r--src/mongo/db/matcher/expression_text_noop.h44
-rw-r--r--src/mongo/db/matcher/extensions_callback_noop.cpp16
-rw-r--r--src/mongo/db/matcher/extensions_callback_noop.h12
-rw-r--r--src/mongo/db/matcher/extensions_callback_real.cpp15
-rw-r--r--src/mongo/db/matcher/extensions_callback_real.h11
-rw-r--r--src/mongo/db/ops/modifier_pull.cpp4
-rw-r--r--src/mongo/db/ops/update_driver.cpp6
-rw-r--r--src/mongo/db/pipeline/document_source_match.cpp5
-rw-r--r--src/mongo/db/query/canonical_query_test.cpp3
-rw-r--r--src/mongo/db/query/query_planner_test_fixture.cpp9
-rw-r--r--src/mongo/dbtests/SConscript2
-rw-r--r--src/mongo/dbtests/expression_where_test.cpp116
-rw-r--r--src/mongo/dbtests/extensions_callback_real_test.cpp227
-rw-r--r--src/mongo/dbtests/query_stage_subplan.cpp4
29 files changed, 712 insertions, 529 deletions
diff --git a/src/mongo/base/error_codes.err b/src/mongo/base/error_codes.err
index 494b953aab7..966797e6944 100644
--- a/src/mongo/base/error_codes.err
+++ b/src/mongo/base/error_codes.err
@@ -80,7 +80,7 @@ error_code("OperationIncomplete", 77)
error_code("CommandResultSchemaViolation", 78)
error_code("UnknownReplWriteConcern", 79)
error_code("RoleDataInconsistent", 80)
-error_code("NoWhereParseContext", 81)
+error_code("NoMatchParseContext", 81)
error_code("NoProgressMade", 82)
error_code("RemoteResultsUnavailable", 83)
error_code("DuplicateKeyValue", 84)
diff --git a/src/mongo/db/exec/projection.h b/src/mongo/db/exec/projection.h
index 389abcc2123..87a3f97a2fd 100644
--- a/src/mongo/db/exec/projection.h
+++ b/src/mongo/db/exec/projection.h
@@ -66,7 +66,7 @@ struct ProjectionStageParams {
// from. Otherwise, this field is ignored.
BSONObj coveredKeyObj;
- // Used for creating context for the $where clause processing. Not owned.
+ // Used for creating context for the match extensions processing. Not owned.
const MatchExpressionParser::ExtensionsCallback* extensionsCallback;
};
diff --git a/src/mongo/db/exec/stagedebug_cmd.cpp b/src/mongo/db/exec/stagedebug_cmd.cpp
index 261df368104..623745f0047 100644
--- a/src/mongo/db/exec/stagedebug_cmd.cpp
+++ b/src/mongo/db/exec/stagedebug_cmd.cpp
@@ -54,6 +54,7 @@
#include "mongo/db/index/fts_access_method.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/matcher/expression_parser.h"
+#include "mongo/db/matcher/expression_text_base.h"
#include "mongo/db/matcher/extensions_callback_real.h"
#include "mongo/db/query/plan_executor.h"
#include "mongo/stdx/memory.h"
@@ -460,8 +461,8 @@ public:
if (!params.query.parse(search,
fam->getSpec().defaultLanguage().str().c_str(),
- fts::FTSQuery::caseSensitiveDefault,
- fts::FTSQuery::diacriticSensitiveDefault,
+ TextMatchExpressionBase::kCaseSensitiveDefault,
+ TextMatchExpressionBase::kDiacriticSensitiveDefault,
fam->getSpec().getTextIndexVersion()).isOK()) {
return NULL;
}
diff --git a/src/mongo/db/fts/fts_query.cpp b/src/mongo/db/fts/fts_query.cpp
index f162481066b..3e77701f79f 100644
--- a/src/mongo/db/fts/fts_query.cpp
+++ b/src/mongo/db/fts/fts_query.cpp
@@ -49,9 +49,6 @@ using std::string;
using std::stringstream;
using std::vector;
-const bool FTSQuery::caseSensitiveDefault = false;
-const bool FTSQuery::diacriticSensitiveDefault = false;
-
Status FTSQuery::parse(const string& query,
StringData language,
bool caseSensitive,
diff --git a/src/mongo/db/fts/fts_query.h b/src/mongo/db/fts/fts_query.h
index ea1882e4baf..ae05100ea37 100644
--- a/src/mongo/db/fts/fts_query.h
+++ b/src/mongo/db/fts/fts_query.h
@@ -89,9 +89,6 @@ public:
BSONObj toBSON() const;
- static const bool caseSensitiveDefault;
- static const bool diacriticSensitiveDefault;
-
private:
void _addTerms(FTSTokenizer* tokenizer, const std::string& tokens, bool negated);
diff --git a/src/mongo/db/matcher/SConscript b/src/mongo/db/matcher/SConscript
index b4b62c29b55..506e2ba1cbc 100644
--- a/src/mongo/db/matcher/SConscript
+++ b/src/mongo/db/matcher/SConscript
@@ -32,16 +32,19 @@ env.Library(
'expression_leaf.cpp',
'expression_parser.cpp',
'expression_parser_tree.cpp',
+ 'expression_text_base.cpp',
+ 'expression_text_noop.cpp',
'expression_tree.cpp',
'expression_where_base.cpp',
'expression_where_noop.cpp',
'extensions_callback_noop.cpp',
'match_details.cpp',
'matchable.cpp',
- "matcher.cpp",
+ 'matcher.cpp',
],
LIBDEPS=[
'$BUILD_DIR/mongo/base',
+ '$BUILD_DIR/mongo/bson/util/bson_extract',
'$BUILD_DIR/mongo/db/common',
'$BUILD_DIR/third_party/shim_pcrecpp',
'path',
@@ -122,7 +125,6 @@ env.Library(
target='expressions_text',
source=[
'expression_text.cpp',
- 'expression_parser_text.cpp',
],
LIBDEPS=[
'$BUILD_DIR/mongo/db/fts/base',
@@ -130,16 +132,6 @@ env.Library(
],
)
-env.CppUnitTest(
- target='expression_text_test',
- source=[
- 'expression_parser_text_test.cpp',
- ],
- LIBDEPS=[
- 'expressions_text',
- ],
-)
-
env.Library(
target='expressions_mongod_only',
source=[
@@ -150,5 +142,6 @@ env.Library(
'$BUILD_DIR/mongo/db/auth/authorization_manager_global',
'$BUILD_DIR/mongo/scripting/scripting_server',
'expressions',
+ 'expressions_text',
],
)
diff --git a/src/mongo/db/matcher/expression_parser.cpp b/src/mongo/db/matcher/expression_parser.cpp
index afbff931422..6692e41aead 100644
--- a/src/mongo/db/matcher/expression_parser.cpp
+++ b/src/mongo/db/matcher/expression_parser.cpp
@@ -33,6 +33,7 @@
#include "mongo/bson/bsonmisc.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/bson/bsonobjbuilder.h"
+#include "mongo/bson/util/bson_extract.h"
#include "mongo/db/matcher/expression_array.h"
#include "mongo/db/matcher/expression_leaf.h"
#include "mongo/db/matcher/expression_tree.h"
@@ -332,10 +333,7 @@ StatusWithMatchExpression MatchExpressionParser::_parse(const BSONObj& obj, int
return s;
root->add(s.getValue().release());
} else if (mongoutils::str::equals("text", rest)) {
- if (e.type() != Object) {
- return {Status(ErrorCodes::BadValue, "$text expects an object")};
- }
- StatusWithMatchExpression s = expressionParserTextCallback(e.Obj());
+ StatusWithMatchExpression s = _extensionsCallback->parseText(e);
if (!s.isOK()) {
return s;
}
@@ -957,7 +955,12 @@ StatusWith<std::vector<uint32_t>> MatchExpressionParser::_parseBitPositionsArray
StatusWithMatchExpression MatchExpressionParser::ExtensionsCallback::parseWhere(
BSONElement where) const {
- return {Status(ErrorCodes::NoWhereParseContext, "no context for parsing $where")};
+ return {Status(ErrorCodes::NoMatchParseContext, "no context for parsing $where")};
+}
+
+StatusWithMatchExpression MatchExpressionParser::ExtensionsCallback::parseText(
+ BSONElement text) const {
+ return {Status(ErrorCodes::NoMatchParseContext, "no context for parsing $text")};
}
StatusWith<WhereMatchExpressionBase::WhereParams>
@@ -985,6 +988,68 @@ MatchExpressionParser::ExtensionsCallback::extractWhereMatchExpressionParams(BSO
return params;
}
+StatusWith<TextMatchExpressionBase::TextParams>
+MatchExpressionParser::ExtensionsCallback::extractTextMatchExpressionParams(BSONElement text) {
+ TextMatchExpressionBase::TextParams params;
+ if (text.type() != Object) {
+ return {ErrorCodes::BadValue, "$text expects an object"};
+ }
+ BSONObj queryObj = text.Obj();
+
+ //
+ // Parse required fields.
+ //
+
+ Status queryStatus = bsonExtractStringField(queryObj, "$search", &params.query);
+ if (!queryStatus.isOK()) {
+ return queryStatus;
+ }
+
+ //
+ // Parse optional fields.
+ //
+
+ int expectedFieldCount = 1;
+
+ Status languageStatus = bsonExtractStringField(queryObj, "$language", &params.language);
+ if (languageStatus == ErrorCodes::TypeMismatch) {
+ return languageStatus;
+ } else if (languageStatus == ErrorCodes::NoSuchKey) {
+ params.language = string();
+ } else {
+ invariantOK(languageStatus);
+ expectedFieldCount++;
+ }
+
+ Status caseSensitiveStatus =
+ bsonExtractBooleanField(queryObj, "$caseSensitive", &params.caseSensitive);
+ if (caseSensitiveStatus == ErrorCodes::TypeMismatch) {
+ return caseSensitiveStatus;
+ } else if (caseSensitiveStatus == ErrorCodes::NoSuchKey) {
+ params.caseSensitive = TextMatchExpressionBase::kCaseSensitiveDefault;
+ } else {
+ invariantOK(caseSensitiveStatus);
+ expectedFieldCount++;
+ }
+
+ Status diacriticSensitiveStatus =
+ bsonExtractBooleanField(queryObj, "$diacriticSensitive", &params.diacriticSensitive);
+ if (diacriticSensitiveStatus == ErrorCodes::TypeMismatch) {
+ return diacriticSensitiveStatus;
+ } else if (diacriticSensitiveStatus == ErrorCodes::NoSuchKey) {
+ params.diacriticSensitive = TextMatchExpressionBase::kDiacriticSensitiveDefault;
+ } else {
+ invariantOK(diacriticSensitiveStatus);
+ expectedFieldCount++;
+ }
+
+ if (queryObj.nFields() != expectedFieldCount) {
+ return {ErrorCodes::BadValue, "extra fields in $text"};
+ }
+
+ return {std::move(params)};
+}
+
// Geo
StatusWithMatchExpression expressionParserGeoCallbackDefault(const char* name,
int type,
@@ -993,12 +1058,4 @@ StatusWithMatchExpression expressionParserGeoCallbackDefault(const char* name,
}
MatchExpressionParserGeoCallback expressionParserGeoCallback = expressionParserGeoCallbackDefault;
-
-// Text
-StatusWithMatchExpression expressionParserTextCallbackDefault(const BSONObj& queryObj) {
- return {Status(ErrorCodes::BadValue, "$text not linked in")};
-}
-
-MatchExpressionParserTextCallback expressionParserTextCallback =
- expressionParserTextCallbackDefault;
}
diff --git a/src/mongo/db/matcher/expression_parser.h b/src/mongo/db/matcher/expression_parser.h
index 4b7bf4a751c..5af324291da 100644
--- a/src/mongo/db/matcher/expression_parser.h
+++ b/src/mongo/db/matcher/expression_parser.h
@@ -35,6 +35,7 @@
#include "mongo/db/matcher/expression.h"
#include "mongo/db/matcher/expression_leaf.h"
#include "mongo/db/matcher/expression_tree.h"
+#include "mongo/db/matcher/expression_text_base.h"
#include "mongo/db/matcher/expression_where_base.h"
#include "mongo/stdx/functional.h"
@@ -47,21 +48,30 @@ typedef StatusWith<std::unique_ptr<MatchExpression>> StatusWithMatchExpression;
class MatchExpressionParser {
public:
/**
- * In general, expression parsing and matching should not require context, but the $where
- * clause is an exception in that it needs to read the sys.js collection.
+ * Certain match clauses (the "extension" clauses, namely $text and $where) require context in
+ * order to perform parsing. This context is captured inside of an ExtensionsCallback object.
*
- * The default behaviour is to return an error status that $where context is not present.
- *
- * Do not use this class to pass-in generic context as it should only be used for $where.
+ * The default implementations of parseText() and parseWhere() simply return an error Status.
+ * Instead of constructing an ExtensionsCallback object directly, an instance of one of the
+ * derived classes (ExtensionsCallbackReal or ExtensionsCallbackNoop) should generally be used
+ * instead.
*/
class ExtensionsCallback {
public:
+ virtual StatusWithMatchExpression parseText(BSONElement text) const;
+
virtual StatusWithMatchExpression parseWhere(BSONElement where) const;
virtual ~ExtensionsCallback() {}
protected:
/**
+ * Helper method which extracts parameters from the given $text element.
+ */
+ static StatusWith<TextMatchExpressionBase::TextParams> extractTextMatchExpressionParams(
+ BSONElement text);
+
+ /**
* Helper method which extracts parameters from the given $where element.
*/
static StatusWith<WhereMatchExpressionBase::WhereParams> extractWhereMatchExpressionParams(
@@ -170,7 +180,7 @@ private:
// The maximum allowed depth of a query tree. Just to guard against stack overflow.
static const int kMaximumTreeDepth;
- // Performs parsing for the $where clause. We do not own this pointer - it has to live
+ // Performs parsing for the match extensions. We do not own this pointer - it has to live
// as long as the parser is active.
const ExtensionsCallback* _extensionsCallback;
};
@@ -178,8 +188,4 @@ private:
typedef stdx::function<StatusWithMatchExpression(
const char* name, int type, const BSONObj& section)> MatchExpressionParserGeoCallback;
extern MatchExpressionParserGeoCallback expressionParserGeoCallback;
-
-typedef stdx::function<StatusWithMatchExpression(const BSONObj& queryObj)>
- MatchExpressionParserTextCallback;
-extern MatchExpressionParserTextCallback expressionParserTextCallback;
}
diff --git a/src/mongo/db/matcher/expression_parser_text.cpp b/src/mongo/db/matcher/expression_parser_text.cpp
deleted file mode 100644
index 6968dc6c0cb..00000000000
--- a/src/mongo/db/matcher/expression_parser_text.cpp
+++ /dev/null
@@ -1,109 +0,0 @@
-// expression_parser_text.cpp
-
-/**
- * Copyright (C) 2013 10gen 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/base/init.h"
-#include "mongo/db/fts/fts_language.h"
-#include "mongo/db/fts/fts_spec.h"
-#include "mongo/db/jsobj.h"
-#include "mongo/db/matcher/expression_parser.h"
-#include "mongo/db/matcher/expression_text.h"
-
-namespace mongo {
-
-using std::unique_ptr;
-using std::string;
-
-StatusWithMatchExpression expressionParserTextCallbackReal(const BSONObj& queryObj) {
- // 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::TypeMismatch,
- "$search requires a string value");
- }
-
- string language = "";
- BSONElement languageElt = queryObj["$language"];
- if (!languageElt.eoo()) {
- expectedFieldCount++;
- if (mongo::String != languageElt.type()) {
- return StatusWithMatchExpression(ErrorCodes::TypeMismatch,
- "$language requires a string value");
- }
- language = languageElt.String();
- Status status = fts::FTSLanguage::make(language, fts::TEXT_INDEX_VERSION_2).getStatus();
- if (!status.isOK()) {
- return StatusWithMatchExpression(ErrorCodes::BadValue,
- "$language specifies unsupported language");
- }
- }
- string query = queryObj["$search"].String();
-
- 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();
- }
-
- BSONElement diacriticSensitiveElt = queryObj["$diacriticSensitive"];
- bool diacriticSensitive = fts::FTSQuery::diacriticSensitiveDefault;
- if (!diacriticSensitiveElt.eoo()) {
- expectedFieldCount++;
- if (mongo::Bool != diacriticSensitiveElt.type()) {
- return StatusWithMatchExpression(ErrorCodes::TypeMismatch,
- "$diacriticSensitive requires a boolean value");
- }
- diacriticSensitive = diacriticSensitiveElt.trueValue();
- }
-
- if (queryObj.nFields() != expectedFieldCount) {
- return StatusWithMatchExpression(ErrorCodes::BadValue, "extra fields in $text");
- }
-
- unique_ptr<TextMatchExpression> e(new TextMatchExpression());
- Status s = e->init(query, language, caseSensitive, diacriticSensitive);
- if (!s.isOK()) {
- return StatusWithMatchExpression(s);
- }
- return {std::move(e)};
-}
-
-MONGO_INITIALIZER(MatchExpressionParserText)(::mongo::InitializerContext* context) {
- expressionParserTextCallback = expressionParserTextCallbackReal;
- return Status::OK();
-}
-}
diff --git a/src/mongo/db/matcher/expression_parser_text_test.cpp b/src/mongo/db/matcher/expression_parser_text_test.cpp
deleted file mode 100644
index 3d51604f7ef..00000000000
--- a/src/mongo/db/matcher/expression_parser_text_test.cpp
+++ /dev/null
@@ -1,139 +0,0 @@
-// expression_parser_text_test.cpp
-
-/**
- * Copyright (C) 2013 10gen 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/unittest/unittest.h"
-
-#include "mongo/db/matcher/expression_parser.h"
-
-#include "mongo/db/jsobj.h"
-#include "mongo/db/json.h"
-#include "mongo/db/matcher/expression.h"
-#include "mongo/db/matcher/expression_text.h"
-
-namespace mongo {
-
-TEST(MatchExpressionParserText, Basic) {
- BSONObj query = fromjson("{$text: {$search:\"awesome\", $language:\"english\"}}");
-
- StatusWithMatchExpression result = MatchExpressionParser::parse(query);
- ASSERT_TRUE(result.isOK());
-
- ASSERT_EQUALS(MatchExpression::TEXT, result.getValue()->matchType());
- std::unique_ptr<TextMatchExpression> textExp(
- static_cast<TextMatchExpression*>(result.getValue().release()));
- ASSERT_EQUALS(textExp->getQuery(), "awesome");
- ASSERT_EQUALS(textExp->getLanguage(), "english");
- ASSERT_EQUALS(textExp->getCaseSensitive(), fts::FTSQuery::caseSensitiveDefault);
- ASSERT_EQUALS(textExp->getDiacriticSensitive(), fts::FTSQuery::diacriticSensitiveDefault);
-}
-
-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());
- std::unique_ptr<TextMatchExpression> textExp(
- static_cast<TextMatchExpression*>(result.getValue().release()));
- 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());
- std::unique_ptr<TextMatchExpression> textExp(
- static_cast<TextMatchExpression*>(result.getValue().release()));
- ASSERT_EQUALS(textExp->getCaseSensitive(), false);
-}
-
-TEST(MatchExpressionParserText, CaseSensitiveError) {
- BSONObj query = fromjson("{$text:{$search:\"awesome\", $caseSensitive: 0}}");
-
- StatusWithMatchExpression result = MatchExpressionParser::parse(query);
- ASSERT_FALSE(result.isOK());
-}
-
-TEST(MatchExpressionParserText, DiacriticSensitiveTrue) {
- BSONObj query = fromjson("{$text: {$search:\"awesome\", $diacriticSensitive: true}}");
-
- StatusWithMatchExpression result = MatchExpressionParser::parse(query);
- ASSERT_TRUE(result.isOK());
-
- ASSERT_EQUALS(MatchExpression::TEXT, result.getValue()->matchType());
- std::unique_ptr<TextMatchExpression> textExp(
- static_cast<TextMatchExpression*>(result.getValue().release()));
- ASSERT_EQUALS(textExp->getDiacriticSensitive(), true);
-}
-
-TEST(MatchExpressionParserText, DiacriticSensitiveFalse) {
- BSONObj query = fromjson("{$text: {$search:\"awesome\", $diacriticSensitive: false}}");
-
- StatusWithMatchExpression result = MatchExpressionParser::parse(query);
- ASSERT_TRUE(result.isOK());
-
- ASSERT_EQUALS(MatchExpression::TEXT, result.getValue()->matchType());
- std::unique_ptr<TextMatchExpression> textExp(
- static_cast<TextMatchExpression*>(result.getValue().release()));
- ASSERT_EQUALS(textExp->getDiacriticSensitive(), false);
-}
-
-TEST(MatchExpressionParserText, DiacriticSensitiveError) {
- BSONObj query = fromjson("{$text:{$search:\"awesome\", $diacriticSensitive: 0}}");
-
- StatusWithMatchExpression result = MatchExpressionParser::parse(query);
- ASSERT_FALSE(result.isOK());
-}
-
-TEST(MatchExpressionParserText, DiacriticSensitiveAndCaseSensitiveTrue) {
- BSONObj query =
- fromjson("{$text: {$search:\"awesome\", $diacriticSensitive: true, $caseSensitive: true}}");
-
- StatusWithMatchExpression result = MatchExpressionParser::parse(query);
- ASSERT_TRUE(result.isOK());
-
- ASSERT_EQUALS(MatchExpression::TEXT, result.getValue()->matchType());
- std::unique_ptr<TextMatchExpression> textExp(
- static_cast<TextMatchExpression*>(result.getValue().release()));
- ASSERT_EQUALS(textExp->getDiacriticSensitive(), true);
- ASSERT_EQUALS(textExp->getCaseSensitive(), true);
-}
-}
diff --git a/src/mongo/db/matcher/expression_text.cpp b/src/mongo/db/matcher/expression_text.cpp
index b5ccd9f40bd..b371357970d 100644
--- a/src/mongo/db/matcher/expression_text.cpp
+++ b/src/mongo/db/matcher/expression_text.cpp
@@ -29,82 +29,41 @@
*/
#include "mongo/platform/basic.h"
+
#include "mongo/db/matcher/expression_text.h"
+
+#include "mongo/db/fts/fts_language.h"
#include "mongo/stdx/memory.h"
namespace mongo {
-using std::string;
-using std::unique_ptr;
-using stdx::make_unique;
-
-Status TextMatchExpression::init(const string& query,
- const string& language,
- bool caseSensitive,
- bool diacriticSensitive) {
- _query = query;
- _language = language;
- _caseSensitive = caseSensitive;
- _diacriticSensitive = diacriticSensitive;
- return initPath("_fts");
-}
-
-bool TextMatchExpression::matchesSingleElement(const BSONElement& e) const {
- // See ops/update.cpp.
- // This node is removed by the query planner. It's only ever called if we're getting an
- // elemMatchKey.
- return true;
-}
+TextMatchExpression::TextMatchExpression(TextParams params)
+ : TextMatchExpressionBase(std::move(params)) {}
-void TextMatchExpression::debugString(StringBuilder& debug, int level) const {
- _debugAddSpace(debug, level);
- debug << "TEXT : query=" << _query << ", language=" << _language
- << ", caseSensitive=" << _caseSensitive << ", diacriticSensitive=" << _diacriticSensitive
- << ", tag=";
- MatchExpression::TagData* td = getTag();
- if (NULL != td) {
- td->debugString(&debug);
- } else {
- debug << "NULL";
+Status TextMatchExpression::init() {
+ // Validate language, but defer construction of FTSQuery (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"};
+ }
}
- debug << "\n";
-}
-
-void TextMatchExpression::toBSON(BSONObjBuilder* out) const {
- out->append("$text",
- BSON("$search" << _query << "$language" << _language << "$caseSensitive"
- << _caseSensitive << "$diacriticSensitive" << _diacriticSensitive));
-}
-bool TextMatchExpression::equivalent(const MatchExpression* other) const {
- if (matchType() != other->matchType()) {
- return false;
- }
- const TextMatchExpression* realOther = static_cast<const TextMatchExpression*>(other);
-
- // TODO This is way too crude. It looks for string equality, but it should be looking for
- // common parsed form
- 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 initPath("_fts");
}
-unique_ptr<MatchExpression> TextMatchExpression::shallowClone() const {
- unique_ptr<TextMatchExpression> next = make_unique<TextMatchExpression>();
- next->init(_query, _language, _caseSensitive, _diacriticSensitive);
+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();
if (getTag()) {
- next->setTag(getTag()->clone());
+ expr->setTag(getTag()->clone());
}
- return std::move(next);
-}
+ return std::move(expr);
}
+
+} // namespace mongo
diff --git a/src/mongo/db/matcher/expression_text.h b/src/mongo/db/matcher/expression_text.h
index 371ad037111..dfe4cbd8204 100644
--- a/src/mongo/db/matcher/expression_text.h
+++ b/src/mongo/db/matcher/expression_text.h
@@ -30,50 +30,17 @@
#pragma once
-#include "mongo/db/fts/fts_query.h"
-#include "mongo/db/matcher/expression.h"
-#include "mongo/db/matcher/expression_leaf.h"
+#include "mongo/db/matcher/expression_text_base.h"
namespace mongo {
-class TextMatchExpression : public LeafMatchExpression {
+class TextMatchExpression : public TextMatchExpressionBase {
public:
- TextMatchExpression() : LeafMatchExpression(TEXT) {}
- virtual ~TextMatchExpression() {}
+ TextMatchExpression(TextParams params);
- Status init(const std::string& query,
- const std::string& language,
- bool caseSensitive,
- bool diacriticSensitive);
+ Status init();
- virtual bool matchesSingleElement(const BSONElement& e) const;
-
- virtual void debugString(StringBuilder& debug, int level = 0) const;
-
- virtual void toBSON(BSONObjBuilder* out) const;
-
- virtual bool equivalent(const MatchExpression* other) const;
-
- virtual std::unique_ptr<MatchExpression> shallowClone() const;
-
- const std::string& getQuery() const {
- return _query;
- }
- const std::string& getLanguage() const {
- return _language;
- }
- bool getCaseSensitive() const {
- return _caseSensitive;
- }
- bool getDiacriticSensitive() const {
- return _diacriticSensitive;
- }
-
-private:
- std::string _query;
- std::string _language;
- bool _caseSensitive;
- bool _diacriticSensitive;
+ std::unique_ptr<MatchExpression> shallowClone() const final;
};
} // namespace mongo
diff --git a/src/mongo/db/matcher/expression_text_base.cpp b/src/mongo/db/matcher/expression_text_base.cpp
new file mode 100644
index 00000000000..04b62ecd6b1
--- /dev/null
+++ b/src/mongo/db/matcher/expression_text_base.cpp
@@ -0,0 +1,86 @@
+/**
+ * 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/matcher/expression_text_base.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) {}
+
+void TextMatchExpressionBase::debugString(StringBuilder& debug, int level) const {
+ _debugAddSpace(debug, level);
+ debug << "TEXT : query=" << _query << ", language=" << _language
+ << ", caseSensitive=" << _caseSensitive << ", diacriticSensitive=" << _diacriticSensitive
+ << ", tag=";
+ MatchExpression::TagData* td = getTag();
+ if (NULL != td) {
+ td->debugString(&debug);
+ } else {
+ debug << "NULL";
+ }
+ debug << "\n";
+}
+
+void TextMatchExpressionBase::toBSON(BSONObjBuilder* out) const {
+ out->append("$text",
+ BSON("$search" << _query << "$language" << _language << "$caseSensitive"
+ << _caseSensitive << "$diacriticSensitive" << _diacriticSensitive));
+}
+
+bool TextMatchExpressionBase::equivalent(const MatchExpression* other) const {
+ if (matchType() != other->matchType()) {
+ return false;
+ }
+ 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;
+}
+
+} // namespace mongo
diff --git a/src/mongo/db/matcher/expression_text_base.h b/src/mongo/db/matcher/expression_text_base.h
new file mode 100644
index 00000000000..8ca25cf60eb
--- /dev/null
+++ b/src/mongo/db/matcher/expression_text_base.h
@@ -0,0 +1,100 @@
+/**
+ * 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/matcher/expression_leaf.h"
+
+namespace mongo {
+
+/**
+ * Common base class for $text match expression implementations.
+ */
+class TextMatchExpressionBase : public LeafMatchExpression {
+public:
+ struct TextParams {
+ std::string query;
+ std::string language;
+ bool caseSensitive;
+ bool diacriticSensitive;
+ };
+
+ static const bool kCaseSensitiveDefault;
+ static const bool kDiacriticSensitiveDefault;
+
+ TextMatchExpressionBase(TextParams params);
+
+ const std::string& getQuery() const {
+ return _query;
+ }
+
+ const std::string& getLanguage() const {
+ return _language;
+ }
+
+ bool getCaseSensitive() const {
+ return _caseSensitive;
+ }
+
+ bool getDiacriticSensitive() const {
+ return _diacriticSensitive;
+ }
+
+ //
+ // Methods inherited from MatchExpression.
+ //
+
+ bool matchesSingleElement(const BSONElement& e) const final {
+ // Text match expressions force the selection of the text index and always generate EXACT
+ // index bounds (which causes the MatchExpression node to be trimmed), so we don't currently
+ // implement any explicit text matching logic here. SERVER-17648 tracks the work to
+ // implement a real text matcher.
+ //
+ // TODO: simply returning 'true' here isn't quite correct. First, we should be overriding
+ // matches() instead of matchesSingleElement(), because the latter is only ever called if
+ // the matched document has an element with path "_fts". Second, there are scenarios where
+ // we will use the untrimmed expression tree for matching (for example, when deciding
+ // whether or not to retry an operation that throws WriteConflictException); in those cases,
+ // we won't be able to correctly determine whether or not the object matches the expression.
+ return true;
+ }
+
+ void debugString(StringBuilder& debug, int level = 0) const final;
+
+ 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
new file mode 100644
index 00000000000..7d2fb5c84b7
--- /dev/null
+++ b/src/mongo/db/matcher/expression_text_noop.cpp
@@ -0,0 +1,58 @@
+/**
+ * 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/matcher/expression_text_noop.h"
+
+#include "mongo/stdx/memory.h"
+
+namespace mongo {
+
+TextNoOpMatchExpression::TextNoOpMatchExpression(TextParams params)
+ : TextMatchExpressionBase(std::move(params)) {}
+
+Status TextNoOpMatchExpression::init() {
+ 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();
+ if (getTag()) {
+ expr->setTag(getTag()->clone());
+ }
+ return std::move(expr);
+}
+
+} // namespace mongo
diff --git a/src/mongo/db/matcher/expression_text_noop.h b/src/mongo/db/matcher/expression_text_noop.h
new file mode 100644
index 00000000000..de02bcfa3cf
--- /dev/null
+++ b/src/mongo/db/matcher/expression_text_noop.h
@@ -0,0 +1,44 @@
+/**
+ * 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/matcher/expression_text_base.h"
+
+namespace mongo {
+
+class TextNoOpMatchExpression : public TextMatchExpressionBase {
+public:
+ TextNoOpMatchExpression(TextParams params);
+
+ Status init();
+
+ std::unique_ptr<MatchExpression> shallowClone() const final;
+};
+
+} // namespace mongo
diff --git a/src/mongo/db/matcher/extensions_callback_noop.cpp b/src/mongo/db/matcher/extensions_callback_noop.cpp
index ca51ae22b76..e5941f73562 100644
--- a/src/mongo/db/matcher/extensions_callback_noop.cpp
+++ b/src/mongo/db/matcher/extensions_callback_noop.cpp
@@ -30,10 +30,26 @@
#include "mongo/db/matcher/extensions_callback_noop.h"
+#include "mongo/db/matcher/expression_text_noop.h"
#include "mongo/db/matcher/expression_where_noop.h"
namespace mongo {
+StatusWithMatchExpression ExtensionsCallbackNoop::parseText(BSONElement text) const {
+ auto textParams = extractTextMatchExpressionParams(text);
+ if (!textParams.isOK()) {
+ return textParams.getStatus();
+ }
+
+ auto expr = stdx::make_unique<TextNoOpMatchExpression>(std::move(textParams.getValue()));
+ Status initStatus = expr->init();
+ if (!initStatus.isOK()) {
+ return initStatus;
+ }
+
+ return {std::move(expr)};
+}
+
StatusWithMatchExpression ExtensionsCallbackNoop::parseWhere(BSONElement where) const {
auto whereParams = extractWhereMatchExpressionParams(where);
if (!whereParams.isOK()) {
diff --git a/src/mongo/db/matcher/extensions_callback_noop.h b/src/mongo/db/matcher/extensions_callback_noop.h
index 9ac019e3947..b66955add43 100644
--- a/src/mongo/db/matcher/extensions_callback_noop.h
+++ b/src/mongo/db/matcher/extensions_callback_noop.h
@@ -33,10 +33,20 @@
namespace mongo {
/**
- * This is just a pass-through implementation, used by sharding only.
+ * ExtensionsCallbackNoop does not capture any context, and produces "no op" expressions that can't
+ * be used for matching. It should be used when parsing context is not available (for example, when
+ * the relevant namespace does not exist, or in mongos).
*/
class ExtensionsCallbackNoop : public MatchExpressionParser::ExtensionsCallback {
public:
+ /**
+ * Returns a TextNoOpMatchExpression, or an error Status if parsing fails.
+ */
+ StatusWithMatchExpression parseText(BSONElement text) const final;
+
+ /**
+ * Returns a WhereNoOpMatchExpression, or an error Status if parsing fails.
+ */
StatusWithMatchExpression parseWhere(BSONElement where) const final;
};
diff --git a/src/mongo/db/matcher/extensions_callback_real.cpp b/src/mongo/db/matcher/extensions_callback_real.cpp
index e07a098fc87..161e0fb2e79 100644
--- a/src/mongo/db/matcher/extensions_callback_real.cpp
+++ b/src/mongo/db/matcher/extensions_callback_real.cpp
@@ -30,6 +30,7 @@
#include "mongo/db/matcher/extensions_callback_real.h"
+#include "mongo/db/matcher/expression_text.h"
#include "mongo/db/matcher/expression_where.h"
#include "mongo/db/namespace_string.h"
@@ -38,6 +39,20 @@ namespace mongo {
ExtensionsCallbackReal::ExtensionsCallbackReal(OperationContext* txn, const NamespaceString* nss)
: _txn(txn), _nss(nss) {}
+StatusWithMatchExpression ExtensionsCallbackReal::parseText(BSONElement text) const {
+ auto textParams = extractTextMatchExpressionParams(text);
+ if (!textParams.isOK()) {
+ return textParams.getStatus();
+ }
+
+ auto exp = stdx::make_unique<TextMatchExpression>(std::move(textParams.getValue()));
+ Status status = exp->init();
+ if (!status.isOK()) {
+ return status;
+ }
+ return {std::move(exp)};
+}
+
StatusWithMatchExpression ExtensionsCallbackReal::parseWhere(BSONElement where) const {
auto whereParams = extractWhereMatchExpressionParams(where);
if (!whereParams.isOK()) {
diff --git a/src/mongo/db/matcher/extensions_callback_real.h b/src/mongo/db/matcher/extensions_callback_real.h
index b820a300dec..6d4caf5c286 100644
--- a/src/mongo/db/matcher/extensions_callback_real.h
+++ b/src/mongo/db/matcher/extensions_callback_real.h
@@ -35,7 +35,8 @@ namespace mongo {
class NamespaceString;
/**
- * This implementation is used for the server-side code.
+ * ExtensionsCallbackReal uses the provided OperationContext and namespace to capture context
+ * necessary for parsing $text and $where clauses.
*/
class ExtensionsCallbackReal : public MatchExpressionParser::ExtensionsCallback {
public:
@@ -48,6 +49,14 @@ public:
*/
ExtensionsCallbackReal(OperationContext* txn, const NamespaceString* nss);
+ /**
+ * Returns a TextMatchExpression, or an error Status if parsing fails.
+ */
+ StatusWithMatchExpression parseText(BSONElement text) const final;
+
+ /**
+ * Returns a WhereMatchExpression, or an error Status if parsing fails.
+ */
StatusWithMatchExpression parseWhere(BSONElement where) const final;
private:
diff --git a/src/mongo/db/ops/modifier_pull.cpp b/src/mongo/db/ops/modifier_pull.cpp
index 2772b7632b0..530ecfffb5e 100644
--- a/src/mongo/db/ops/modifier_pull.cpp
+++ b/src/mongo/db/ops/modifier_pull.cpp
@@ -115,8 +115,8 @@ Status ModifierPull::init(const BSONElement& modExpr, const Options& opts, bool*
_exprObj = _exprElt.wrap("");
}
- // Build the matcher around the object we built above. Currently, we do not allow
- // $pull operations to contain $where clauses, so preserving this behaviour.
+ // Build the matcher around the object we built above. Currently, we do not allow $pull
+ // operations to contain $text/$where clauses, so preserving this behaviour.
StatusWithMatchExpression parseResult =
MatchExpressionParser::parse(_exprObj, MatchExpressionParser::ExtensionsCallback());
if (!parseResult.isOK()) {
diff --git a/src/mongo/db/ops/update_driver.cpp b/src/mongo/db/ops/update_driver.cpp
index 251d2aabae4..c0c11082a2e 100644
--- a/src/mongo/db/ops/update_driver.cpp
+++ b/src/mongo/db/ops/update_driver.cpp
@@ -170,9 +170,9 @@ inline Status UpdateDriver::addAndParse(const modifiertable::ModifierType type,
Status UpdateDriver::populateDocumentWithQueryFields(const BSONObj& query,
const vector<FieldRef*>* immutablePaths,
mutablebson::Document& doc) const {
- // We canonicalize the query to collapse $and/$or, and the first arg (ns) is not needed
- // Also, because this is for the upsert case, where we insert a new document if one was
- // not found, the $where clause does not make sense, hence empty ExtensionsCallback.
+ // We canonicalize the query to collapse $and/$or, and the first arg (ns) is not needed. Also,
+ // because this is for the upsert case, where we insert a new document if one was not found, the
+ // $where/$text clauses do not make sense, hence empty ExtensionsCallback.
auto statusWithCQ =
CanonicalQuery::canonicalize(NamespaceString(""), query, ExtensionsCallbackNoop());
if (!statusWithCQ.isOK()) {
diff --git a/src/mongo/db/pipeline/document_source_match.cpp b/src/mongo/db/pipeline/document_source_match.cpp
index e300114f0a7..0458a7474be 100644
--- a/src/mongo/db/pipeline/document_source_match.cpp
+++ b/src/mongo/db/pipeline/document_source_match.cpp
@@ -31,6 +31,7 @@
#include <cctype>
#include "mongo/db/jsobj.h"
+#include "mongo/db/matcher/extensions_callback_noop.h"
#include "mongo/db/matcher/matcher.h"
#include "mongo/db/pipeline/document.h"
#include "mongo/db/pipeline/document_source.h"
@@ -96,7 +97,7 @@ bool DocumentSourceMatch::coalesce(const intrusive_ptr<DocumentSource>& nextSour
// Replace our matcher with the $and of ours and theirs.
matcher.reset(new Matcher(BSON("$and" << BSON_ARRAY(getQuery() << otherMatch->getQuery())),
- MatchExpressionParser::ExtensionsCallback()));
+ ExtensionsCallbackNoop()));
return true;
}
@@ -359,6 +360,6 @@ BSONObj DocumentSourceMatch::getQuery() const {
DocumentSourceMatch::DocumentSourceMatch(const BSONObj& query,
const intrusive_ptr<ExpressionContext>& pExpCtx)
: DocumentSource(pExpCtx),
- matcher(new Matcher(query.getOwned(), MatchExpressionParser::ExtensionsCallback())),
+ matcher(new Matcher(query.getOwned(), ExtensionsCallbackNoop())),
_isTextQuery(isTextQuery(query)) {}
}
diff --git a/src/mongo/db/query/canonical_query_test.cpp b/src/mongo/db/query/canonical_query_test.cpp
index eff6201663d..ec650b1f1cc 100644
--- a/src/mongo/db/query/canonical_query_test.cpp
+++ b/src/mongo/db/query/canonical_query_test.cpp
@@ -29,6 +29,7 @@
#include "mongo/db/query/canonical_query.h"
#include "mongo/db/json.h"
+#include "mongo/db/matcher/extensions_callback_noop.h"
#include "mongo/db/namespace_string.h"
#include "mongo/unittest/unittest.h"
@@ -46,7 +47,7 @@ static const NamespaceString nss("testdb.testcoll");
* and return the MatchExpression*.
*/
MatchExpression* parseMatchExpression(const BSONObj& obj) {
- StatusWithMatchExpression status = MatchExpressionParser::parse(obj);
+ StatusWithMatchExpression status = MatchExpressionParser::parse(obj, ExtensionsCallbackNoop());
if (!status.isOK()) {
mongoutils::str::stream ss;
ss << "failed to parse query: " << obj.toString()
diff --git a/src/mongo/db/query/query_planner_test_fixture.cpp b/src/mongo/db/query/query_planner_test_fixture.cpp
index 8d559f24c7f..4be1ee2f140 100644
--- a/src/mongo/db/query/query_planner_test_fixture.cpp
+++ b/src/mongo/db/query/query_planner_test_fixture.cpp
@@ -176,7 +176,8 @@ void QueryPlannerTest::runQueryFull(const BSONObj& query,
minObj,
maxObj,
snapshot,
- false); // explain
+ false, // explain
+ ExtensionsCallbackNoop());
ASSERT_OK(statusWithCQ.getStatus());
ASSERT_OK(QueryPlanner::plan(*statusWithCQ.getValue(), params, &solns.mutableVector()));
@@ -241,7 +242,8 @@ void QueryPlannerTest::runInvalidQueryFull(const BSONObj& query,
minObj,
maxObj,
snapshot,
- false); // explain
+ false, // explain
+ ExtensionsCallbackNoop());
ASSERT_OK(statusWithCQ.getStatus());
Status s = QueryPlanner::plan(*statusWithCQ.getValue(), params, &solns.mutableVector());
@@ -257,8 +259,7 @@ void QueryPlannerTest::runQueryAsCommand(const BSONObj& cmdObj) {
std::unique_ptr<LiteParsedQuery> lpq(
assertGet(LiteParsedQuery::makeFromFindCommand(nss, cmdObj, isExplain)));
- ExtensionsCallbackNoop extensionsCallback;
- auto statusWithCQ = CanonicalQuery::canonicalize(lpq.release(), extensionsCallback);
+ auto statusWithCQ = CanonicalQuery::canonicalize(lpq.release(), ExtensionsCallbackNoop());
ASSERT_OK(statusWithCQ.getStatus());
Status s = QueryPlanner::plan(*statusWithCQ.getValue(), params, &solns.mutableVector());
diff --git a/src/mongo/dbtests/SConscript b/src/mongo/dbtests/SConscript
index b353987ac96..05456b37e6a 100644
--- a/src/mongo/dbtests/SConscript
+++ b/src/mongo/dbtests/SConscript
@@ -69,7 +69,7 @@ dbtest = env.Program(
'directclienttests.cpp',
'documentsourcetests.cpp',
'executor_registry.cpp',
- 'expression_where_test.cpp',
+ 'extensions_callback_real_test.cpp',
'gle_test.cpp',
'indexcatalogtests.cpp',
'indexupdatetests.cpp',
diff --git a/src/mongo/dbtests/expression_where_test.cpp b/src/mongo/dbtests/expression_where_test.cpp
deleted file mode 100644
index 047027022e0..00000000000
--- a/src/mongo/dbtests/expression_where_test.cpp
+++ /dev/null
@@ -1,116 +0,0 @@
-/**
- * Copyright (C) 2015 10gen 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/json.h"
-#include "mongo/db/matcher/expression.h"
-#include "mongo/db/matcher/expression_parser.h"
-#include "mongo/db/matcher/expression_where.h"
-#include "mongo/db/matcher/extensions_callback_real.h"
-#include "mongo/db/namespace_string.h"
-#include "mongo/db/operation_context_impl.h"
-#include "mongo/unittest/unittest.h"
-
-namespace mongo {
-namespace {
-
-const NamespaceString nss("unittests.expression_where_test");
-
-TEST(ExpressionWhere, WhereExpressionsWithSameScopeHaveSameBSONRepresentation) {
- OperationContextImpl txn;
- const char code[] = "function(){ return a; }";
-
- BSONObj query1 = BSON("$where" << BSONCodeWScope(code, BSON("a" << true)));
- auto expr1 = unittest::assertGet(
- MatchExpressionParser::parse(query1, ExtensionsCallbackReal(&txn, &nss)));
- BSONObjBuilder builder1;
- expr1->toBSON(&builder1);
-
- BSONObj query2 = BSON("$where" << BSONCodeWScope(code, BSON("a" << true)));
- auto expr2 = unittest::assertGet(
- MatchExpressionParser::parse(query2, ExtensionsCallbackReal(&txn, &nss)));
- BSONObjBuilder builder2;
- expr2->toBSON(&builder2);
-
- ASSERT_EQ(builder1.obj(), builder2.obj());
-}
-
-TEST(ExpressionWhere, WhereExpressionsWithDifferentScopesHaveDifferentBSONRepresentations) {
- OperationContextImpl txn;
- const char code[] = "function(){ return a; }";
-
- BSONObj query1 = BSON("$where" << BSONCodeWScope(code, BSON("a" << true)));
- auto expr1 = unittest::assertGet(
- MatchExpressionParser::parse(query1, ExtensionsCallbackReal(&txn, &nss)));
- BSONObjBuilder builder1;
- expr1->toBSON(&builder1);
-
- BSONObj query2 = BSON("$where" << BSONCodeWScope(code, BSON("a" << false)));
- auto expr2 = unittest::assertGet(
- MatchExpressionParser::parse(query2, ExtensionsCallbackReal(&txn, &nss)));
- BSONObjBuilder builder2;
- expr2->toBSON(&builder2);
-
- ASSERT_NE(builder1.obj(), builder2.obj());
-}
-
-TEST(ExpressionWhere, WhereExpressionsWithSameScopeAreEquivalent) {
- OperationContextImpl txn;
- const char code[] = "function(){ return a; }";
-
- BSONObj query1 = BSON("$where" << BSONCodeWScope(code, BSON("a" << true)));
- auto expr1 = unittest::assertGet(
- MatchExpressionParser::parse(query1, ExtensionsCallbackReal(&txn, &nss)));
-
- BSONObj query2 = BSON("$where" << BSONCodeWScope(code, BSON("a" << true)));
- auto expr2 = unittest::assertGet(
- MatchExpressionParser::parse(query2, ExtensionsCallbackReal(&txn, &nss)));
-
- ASSERT(expr1->equivalent(expr2.get()));
- ASSERT(expr2->equivalent(expr1.get()));
-}
-
-TEST(ExpressionWhere, WhereExpressionsWithDifferentScopesAreNotEquivalent) {
- OperationContextImpl txn;
- const char code[] = "function(){ return a; }";
-
- BSONObj query1 = BSON("$where" << BSONCodeWScope(code, BSON("a" << true)));
- auto expr1 = unittest::assertGet(
- MatchExpressionParser::parse(query1, ExtensionsCallbackReal(&txn, &nss)));
-
- BSONObj query2 = BSON("$where" << BSONCodeWScope(code, BSON("a" << false)));
- auto expr2 = unittest::assertGet(
- MatchExpressionParser::parse(query2, ExtensionsCallbackReal(&txn, &nss)));
-
- ASSERT_FALSE(expr1->equivalent(expr2.get()));
- ASSERT_FALSE(expr2->equivalent(expr1.get()));
-}
-
-} // namespace
-} // namespace mongo
diff --git a/src/mongo/dbtests/extensions_callback_real_test.cpp b/src/mongo/dbtests/extensions_callback_real_test.cpp
new file mode 100644
index 00000000000..b4c2291810f
--- /dev/null
+++ b/src/mongo/dbtests/extensions_callback_real_test.cpp
@@ -0,0 +1,227 @@
+/**
+ * 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/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/unittest/unittest.h"
+
+namespace mongo {
+namespace {
+
+const NamespaceString nss("unittests.extensions_callback_real_test");
+
+//
+// $text parsing tests.
+//
+
+TEST(ExtensionsCallbackReal, TextBasic) {
+ OperationContextImpl txn;
+ BSONObj query = fromjson("{$text: {$search:\"awesome\", $language:\"english\"}}");
+ auto expr =
+ 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(),
+ TextMatchExpressionBase::kDiacriticSensitiveDefault);
+}
+
+TEST(ExtensionsCallbackReal, TextLanguageError) {
+ OperationContextImpl txn;
+ BSONObj query = fromjson("{$text: {$search:\"awesome\", $language:\"spanglish\"}}");
+ StatusWithMatchExpression result =
+ ExtensionsCallbackReal(&txn, &nss).parseText(query.firstElement());
+
+ ASSERT_NOT_OK(result.getStatus());
+}
+
+TEST(ExtensionsCallbackReal, TextCaseSensitiveTrue) {
+ OperationContextImpl txn;
+ BSONObj query = fromjson("{$text: {$search:\"awesome\", $caseSensitive: true}}");
+ auto expr =
+ 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);
+}
+
+TEST(ExtensionsCallbackReal, TextCaseSensitiveFalse) {
+ OperationContextImpl txn;
+ BSONObj query = fromjson("{$text: {$search:\"awesome\", $caseSensitive: false}}");
+ auto expr =
+ 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);
+}
+
+TEST(ExtensionsCallbackReal, TextCaseSensitiveError) {
+ OperationContextImpl txn;
+ BSONObj query = fromjson("{$text:{$search:\"awesome\", $caseSensitive: 0}}");
+ StatusWithMatchExpression result =
+ ExtensionsCallbackReal(&txn, &nss).parseText(query.firstElement());
+
+ ASSERT_NOT_OK(result.getStatus());
+}
+
+TEST(ExtensionsCallbackReal, TextDiacriticSensitiveTrue) {
+ OperationContextImpl txn;
+ BSONObj query = fromjson("{$text: {$search:\"awesome\", $diacriticSensitive: true}}");
+ auto expr =
+ 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);
+}
+
+TEST(ExtensionsCallbackReal, TextDiacriticSensitiveFalse) {
+ OperationContextImpl txn;
+ BSONObj query = fromjson("{$text: {$search:\"awesome\", $diacriticSensitive: false}}");
+ auto expr =
+ 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);
+}
+
+TEST(ExtensionsCallbackReal, TextDiacriticSensitiveError) {
+ OperationContextImpl txn;
+ BSONObj query = fromjson("{$text:{$search:\"awesome\", $diacriticSensitive: 0}}");
+ StatusWithMatchExpression result =
+ ExtensionsCallbackReal(&txn, &nss).parseText(query.firstElement());
+
+ ASSERT_NOT_OK(result.getStatus());
+}
+
+TEST(ExtensionsCallbackReal, TextDiacriticSensitiveAndCaseSensitiveTrue) {
+ OperationContextImpl txn;
+ BSONObj query =
+ fromjson("{$text: {$search:\"awesome\", $diacriticSensitive: true, $caseSensitive: true}}");
+ auto expr =
+ 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);
+}
+
+//
+// $where parsing tests.
+//
+
+TEST(ExtensionsCallbackReal, WhereExpressionsWithSameScopeHaveSameBSONRepresentation) {
+ OperationContextImpl txn;
+ 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()));
+ BSONObjBuilder builder1;
+ expr1->toBSON(&builder1);
+
+ BSONObj query2 = BSON("$where" << BSONCodeWScope(code, BSON("a" << true)));
+ auto expr2 =
+ unittest::assertGet(ExtensionsCallbackReal(&txn, &nss).parseWhere(query2.firstElement()));
+ BSONObjBuilder builder2;
+ expr2->toBSON(&builder2);
+
+ ASSERT_EQ(builder1.obj(), builder2.obj());
+}
+
+TEST(ExtensionsCallbackReal, WhereExpressionsWithDifferentScopesHaveDifferentBSONRepresentations) {
+ OperationContextImpl txn;
+ 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()));
+ BSONObjBuilder builder1;
+ expr1->toBSON(&builder1);
+
+ BSONObj query2 = BSON("$where" << BSONCodeWScope(code, BSON("a" << false)));
+ auto expr2 =
+ unittest::assertGet(ExtensionsCallbackReal(&txn, &nss).parseWhere(query2.firstElement()));
+ BSONObjBuilder builder2;
+ expr2->toBSON(&builder2);
+
+ ASSERT_NE(builder1.obj(), builder2.obj());
+}
+
+TEST(ExtensionsCallbackReal, WhereExpressionsWithSameScopeAreEquivalent) {
+ OperationContextImpl txn;
+ 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()));
+
+ BSONObj query2 = BSON("$where" << BSONCodeWScope(code, BSON("a" << true)));
+ auto expr2 =
+ unittest::assertGet(ExtensionsCallbackReal(&txn, &nss).parseWhere(query2.firstElement()));
+
+ ASSERT(expr1->equivalent(expr2.get()));
+ ASSERT(expr2->equivalent(expr1.get()));
+}
+
+TEST(ExtensionsCallbackReal, WhereExpressionsWithDifferentScopesAreNotEquivalent) {
+ OperationContextImpl txn;
+ 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()));
+
+ BSONObj query2 = BSON("$where" << BSONCodeWScope(code, BSON("a" << false)));
+ auto expr2 =
+ unittest::assertGet(ExtensionsCallbackReal(&txn, &nss).parseWhere(query2.firstElement()));
+
+ ASSERT_FALSE(expr1->equivalent(expr2.get()));
+ ASSERT_FALSE(expr2->equivalent(expr1.get()));
+}
+
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/dbtests/query_stage_subplan.cpp b/src/mongo/dbtests/query_stage_subplan.cpp
index fd27b7c8b19..624405619a4 100644
--- a/src/mongo/dbtests/query_stage_subplan.cpp
+++ b/src/mongo/dbtests/query_stage_subplan.cpp
@@ -37,6 +37,7 @@
#include "mongo/db/exec/subplan.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/json.h"
+#include "mongo/db/matcher/extensions_callback_noop.h"
#include "mongo/db/operation_context_impl.h"
#include "mongo/db/query/canonical_query.h"
#include "mongo/db/query/get_executor.h"
@@ -74,7 +75,8 @@ protected:
auto lpq =
unittest::assertGet(LiteParsedQuery::makeFromFindCommand(nss, cmdObj, isExplain));
- auto cq = unittest::assertGet(CanonicalQuery::canonicalize(lpq.release()));
+ auto cq = unittest::assertGet(
+ CanonicalQuery::canonicalize(lpq.release(), ExtensionsCallbackNoop()));
return cq;
}