diff options
-rw-r--r-- | jstests/core/fts_querylang.js | 188 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_serialization_test.cpp | 16 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_tree.cpp | 11 |
3 files changed, 111 insertions, 104 deletions
diff --git a/jstests/core/fts_querylang.js b/jstests/core/fts_querylang.js index 5fb1b8e606c..de27b65ba5b 100644 --- a/jstests/core/fts_querylang.js +++ b/jstests/core/fts_querylang.js @@ -1,104 +1,86 @@ +// Test the $text query operator. // @tags: [requires_non_retryable_writes] - -// Test $text query operator. - -var t = db.getSiblingDB("test").getCollection("fts_querylang"); -var cursor; -var results; - -t.drop(); - -t.insert({_id: 0, unindexedField: 0, a: "textual content"}); -t.insert({_id: 1, unindexedField: 1, a: "additional content"}); -t.insert({_id: 2, unindexedField: 2, a: "irrelevant content"}); -t.ensureIndex({a: "text"}); - -// Test text query with no results. -assert.eq(false, t.find({$text: {$search: "words"}}).hasNext()); - -// Test basic text query. -results = t.find({$text: {$search: "textual content -irrelevant"}}).toArray(); -assert.eq(results.length, 2); -assert.neq(results[0]._id, 2); -assert.neq(results[1]._id, 2); - -// Test sort with basic text query. -results = - t.find({$text: {$search: "textual content -irrelevant"}}).sort({unindexedField: 1}).toArray(); -assert.eq(results.length, 2); -assert.eq(results[0]._id, 0); -assert.eq(results[1]._id, 1); - -// Test skip with basic text query. -results = t.find({$text: {$search: "textual content -irrelevant"}}) - .sort({unindexedField: 1}) - .skip(1) - .toArray(); -assert.eq(results.length, 1); -assert.eq(results[0]._id, 1); - -// Test limit with basic text query. -results = t.find({$text: {$search: "textual content -irrelevant"}}) - .sort({unindexedField: 1}) - .limit(1) - .toArray(); -assert.eq(results.length, 1); -assert.eq(results[0]._id, 0); - -// TODO Test basic text query with sort, once sort is enabled in the new query framework. - -// TODO Test basic text query with projection, once projection is enabled in the new query -// framework. - -// Test $and of basic text query with indexed expression. -results = t.find({$text: {$search: "content -irrelevant"}, _id: 1}).toArray(); -assert.eq(results.length, 1); -assert.eq(results[0]._id, 1); - -// Test $and of basic text query with indexed expression, and bad language -assert.throws(function() { - t.find({$text: {$search: "content -irrelevant", $language: "spanglish"}, _id: 1}).itcount(); -}); - -// Test $and of basic text query with unindexed expression. -results = t.find({$text: {$search: "content -irrelevant"}, unindexedField: 1}).toArray(); -assert.eq(results.length, 1); -assert.eq(results[0]._id, 1); - -// TODO Test invalid inputs for $text, $search, $language. - -// Test $language. -cursor = t.find({$text: {$search: "contents", $language: "none"}}); -assert.eq(false, cursor.hasNext()); - -cursor = t.find({$text: {$search: "contents", $language: "EN"}}); -assert.eq(true, cursor.hasNext()); - -cursor = t.find({$text: {$search: "contents", $language: "spanglish"}}); -assert.throws(function() { - cursor.next(); -}); - -// TODO Test $and of basic text query with geo expression. - -// Test update with $text. -t.update({$text: {$search: "textual content -irrelevant"}}, {$set: {b: 1}}, {multi: true}); -assert.eq(2, t.find({b: 1}).itcount(), 'incorrect number of documents updated'); - -// TODO Test remove with $text, once it is enabled with the new query framework. - -// TODO Test count with $text, once it is enabled with the new query framework. - -// TODO Test findAndModify with $text, once it is enabled with the new query framework. - -// TODO Test aggregate with $text, once it is enabled with the new query framework. - -// TODO Test that old query framework rejects $text queries. - -// TODO Test that $text fails without a text index. - -// TODO Test that $text accepts a hint of the text index. - -// TODO Test that $text fails if a different index is hinted. - -// TODO Test $text with {$natural:1} sort, {$natural:1} hint. +(function() { + "use strict"; + + const coll = db.getCollection("fts_querylang"); + coll.drop(); + + assert.commandWorked(coll.insert({_id: 0, unindexedField: 0, a: "textual content"})); + assert.commandWorked(coll.insert({_id: 1, unindexedField: 1, a: "additional content"})); + assert.commandWorked(coll.insert({_id: 2, unindexedField: 2, a: "irrelevant content"})); + assert.commandWorked(coll.createIndex({a: "text"})); + + // Test text query with no results. + assert.eq(false, coll.find({$text: {$search: "words"}}).hasNext()); + + // Test basic text query. + let results = coll.find({$text: {$search: "textual content -irrelevant"}}).toArray(); + assert.eq(results.length, 2, results); + assert.neq(results[0]._id, 2, results); + assert.neq(results[1]._id, 2, results); + + // Test sort with basic text query. + results = coll.find({$text: {$search: "textual content -irrelevant"}}) + .sort({unindexedField: 1}) + .toArray(); + assert.eq(results.length, 2, results); + assert.eq(results[0]._id, 0, results); + assert.eq(results[1]._id, 1, results); + + // Test skip with basic text query. + results = coll.find({$text: {$search: "textual content -irrelevant"}}) + .sort({unindexedField: 1}) + .skip(1) + .toArray(); + assert.eq(results.length, 1, results); + assert.eq(results[0]._id, 1, results); + + // Test limit with basic text query. + results = coll.find({$text: {$search: "textual content -irrelevant"}}) + .sort({unindexedField: 1}) + .limit(1) + .toArray(); + assert.eq(results.length, 1, results); + assert.eq(results[0]._id, 0, results); + + // Test $and of basic text query with indexed expression. + results = coll.find({$text: {$search: "content -irrelevant"}, _id: 1}).toArray(); + assert.eq(results.length, 1, results); + assert.eq(results[0]._id, 1, results); + + // Test $and of basic text query with indexed expression and bad language. + assert.commandFailedWithCode(assert.throws(function() { + coll.find({$text: {$search: "content -irrelevant", $language: "spanglish"}, _id: 1}) + .itcount(); + }), + ErrorCodes.BadValue); + + // Test $and of basic text query with unindexed expression. + results = coll.find({$text: {$search: "content -irrelevant"}, unindexedField: 1}).toArray(); + assert.eq(results.length, 1, results); + assert.eq(results[0]._id, 1, results); + + // Test $language. + let cursor = coll.find({$text: {$search: "contents", $language: "none"}}); + assert.eq(false, cursor.hasNext()); + + cursor = coll.find({$text: {$search: "contents", $language: "EN"}}); + assert.eq(true, cursor.hasNext()); + + cursor = coll.find({$text: {$search: "contents", $language: "spanglish"}}); + assert.commandFailedWithCode(assert.throws(function() { + cursor.next(); + }), + ErrorCodes.BadValue); + + // Test update with $text. + coll.update({$text: {$search: "textual content -irrelevant"}}, {$set: {b: 1}}, {multi: true}); + assert.eq(2, coll.find({b: 1}).itcount(), 'incorrect number of documents updated'); + + // $text cannot be contained within a $nor. + assert.commandFailedWithCode(assert.throws(function() { + coll.find({$nor: [{$text: {$search: 'a'}}]}).itcount(); + }), + ErrorCodes.BadValue); +}()); diff --git a/src/mongo/db/matcher/expression_serialization_test.cpp b/src/mongo/db/matcher/expression_serialization_test.cpp index 63f238593ae..74a8d3b8f74 100644 --- a/src/mongo/db/matcher/expression_serialization_test.cpp +++ b/src/mongo/db/matcher/expression_serialization_test.cpp @@ -1377,6 +1377,22 @@ TEST(SerializeBasic, ExpressionTextSerializesCorrectly) { ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); } +TEST(SerializeBasic, ExpressionNorWithTextSerializesCorrectly) { + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + Matcher original(fromjson("{$nor: [{$text: {$search: 'x'}}]}"), + expCtx, + ExtensionsCallbackNoop(), + MatchExpressionParser::kAllowAllSpecialFeatures); + Matcher reserialized(serialize(original.getMatchExpression()), + expCtx, + ExtensionsCallbackNoop(), + MatchExpressionParser::kAllowAllSpecialFeatures); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), + fromjson("{$nor: [{$text: {$search: 'x', $language: '', $caseSensitive: " + "false, $diacriticSensitive: false}}]}")); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); +} + TEST(SerializeBasic, ExpressionTextWithDefaultLanguageSerializesCorrectly) { boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); Matcher original(fromjson("{$text: {$search: 'a', $caseSensitive: false}}"), diff --git a/src/mongo/db/matcher/expression_tree.cpp b/src/mongo/db/matcher/expression_tree.cpp index c24749ea7b3..2229f602895 100644 --- a/src/mongo/db/matcher/expression_tree.cpp +++ b/src/mongo/db/matcher/expression_tree.cpp @@ -37,6 +37,7 @@ #include "mongo/bson/bsonobjbuilder.h" #include "mongo/db/matcher/expression_always_boolean.h" #include "mongo/db/matcher/expression_path.h" +#include "mongo/db/matcher/expression_text_base.h" namespace mongo { @@ -324,6 +325,14 @@ void NotMatchExpression::debugString(StringBuilder& debug, int level) const { boost::optional<StringData> NotMatchExpression::getPathIfNotWithSinglePathMatchExpressionTree( MatchExpression* exp) { if (auto pathMatch = dynamic_cast<PathMatchExpression*>(exp)) { + if (dynamic_cast<TextMatchExpressionBase*>(exp)) { + // While TextMatchExpressionBase derives from PathMatchExpression, text match + // expressions cannot be serialized in the same manner as other PathMatchExpression + // derivatives. This is because the path for a TextMatchExpression is embedded within + // the $text object, whereas for other PathMatchExpressions it is on the left-hand-side, + // for example {x: {$eq: 1}}. + return boost::none; + } return pathMatch->path(); } @@ -331,7 +340,7 @@ boost::optional<StringData> NotMatchExpression::getPathIfNotWithSinglePathMatchE boost::optional<StringData> path; for (size_t i = 0; i < exp->numChildren(); ++i) { auto pathMatchChild = dynamic_cast<PathMatchExpression*>(exp->getChild(i)); - if (!pathMatchChild) { + if (!pathMatchChild || dynamic_cast<TextMatchExpressionBase*>(exp->getChild(i))) { return boost::none; } |