/** * 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 . * * 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. */ /** * This file contains tests for mongo/db/query/planner_ixselect.cpp */ #include "mongo/db/query/planner_ixselect.h" #include "mongo/db/json.h" #include "mongo/db/matcher/expression_parser.h" #include "mongo/db/matcher/extensions_callback_disallow_extensions.h" #include "mongo/db/query/collation/collator_interface_mock.h" #include "mongo/db/query/index_tag.h" #include "mongo/unittest/unittest.h" #include "mongo/util/text.h" #include using namespace mongo; namespace { using std::unique_ptr; using std::string; using std::vector; /** * Utility function to create MatchExpression */ unique_ptr parseMatchExpression(const BSONObj& obj) { const CollatorInterface* collator = nullptr; StatusWithMatchExpression status = MatchExpressionParser::parse(obj, ExtensionsCallbackDisallowExtensions(), collator); ASSERT_TRUE(status.isOK()); return std::move(status.getValue()); } /** * Utility function to join elements in iterator range with comma */ template string toString(Iter begin, Iter end) { mongoutils::str::stream ss; ss << "["; for (Iter i = begin; i != end; i++) { if (i != begin) { ss << " "; } ss << *i; } ss << "]"; return ss; } /** * Test function for getFields() * Parses query string to obtain MatchExpression which is passed together with prefix * to QueryPlannerIXSelect::getFields() * Results are compared with expected fields (parsed from expectedFieldsStr) */ void testGetFields(const char* query, const char* prefix, const char* expectedFieldsStr) { BSONObj obj = fromjson(query); unique_ptr expr(parseMatchExpression(obj)); unordered_set fields; QueryPlannerIXSelect::getFields(expr.get(), prefix, &fields); // Verify results // First, check that results contain a superset of expected fields. vector expectedFields = StringSplitter::split(expectedFieldsStr, ","); for (vector::const_iterator i = expectedFields.begin(); i != expectedFields.end(); i++) { if (fields.find(*i) == fields.end()) { mongoutils::str::stream ss; ss << "getFields(query=" << query << ", prefix=" << prefix << "): unable to find " << *i << " in result: " << toString(fields.begin(), fields.end()); FAIL(ss); } } // Next, confirm that results do not contain any unexpected fields. if (fields.size() != expectedFields.size()) { mongoutils::str::stream ss; ss << "getFields(query=" << query << ", prefix=" << prefix << "): unexpected fields in result. expected: " << toString(expectedFields.begin(), expectedFields.end()) << ". actual: " << toString(fields.begin(), fields.end()); FAIL(ss); } } /** * Basic test cases for getFields() * Includes logical operators */ TEST(QueryPlannerIXSelectTest, GetFieldsBasic) { // Arguments to test function: query, prefix, comma-delimited list of expected fields testGetFields("{}", "", ""); testGetFields("{a: 1}", "", "a"); testGetFields("{a: 1}", "c.", "c.a"); testGetFields("{a: 1, b: 1}", "", "a,b"); testGetFields("{a: {$in: [1]}}", "", "a"); testGetFields("{$or: [{a: 1}, {b: 1}]}", "", "a,b"); } /** * Array test cases for getFields */ TEST(QueryPlannerIXSelectTest, GetFieldsArray) { testGetFields("{a: {$elemMatch: {b: 1}}}", "", "a.b"); testGetFields("{a: {$all: [{$elemMatch: {b: 1}}]}}", "", "a.b"); } /** * Negation test cases for getFields() * $ne, $nin, $nor */ TEST(QueryPlannerIXSelectTest, GetFieldsNegation) { testGetFields("{a: {$ne: 1}}", "", "a"); testGetFields("{a: {$nin: [1]}}", "", "a"); testGetFields("{$nor: [{a: 1}, {b: 1}]}", "", ""); testGetFields("{$and: [{a: 1}, {a: {$ne: 2}}]}", "", "a"); } /** * Array negation test cases for getFields */ TEST(QueryPlannerIXSelectTest, GetFieldsArrayNegation) { testGetFields("{a: {$elemMatch: {b: {$ne: 1}}}}", "", "a.b"); testGetFields("{a: {$all: [{$elemMatch: {b: {$ne: 1}}}]}}", "", "a.b"); } /** * Performs a pre-order traversal of expression tree. Validates * that all tagged nodes contain an instance of RelevantTag. * Finds all indices included in RelevantTags, and returns them in the 'indices' out-parameter. */ void findRelevantTaggedNodePathsAndIndices(MatchExpression* root, vector* paths, std::set* indices) { MatchExpression::TagData* tag = root->getTag(); if (tag) { StringBuilder buf; tag->debugString(&buf); RelevantTag* r = dynamic_cast(tag); if (!r) { mongoutils::str::stream ss; ss << "tag is not instance of RelevantTag. tree: " << root->toString() << "; tag: " << buf.str(); FAIL(ss); } paths->push_back(r->path); for (auto const& index : r->first) { indices->insert(index); } for (auto const& index : r->notFirst) { indices->insert(index); } } for (size_t i = 0; i < root->numChildren(); ++i) { findRelevantTaggedNodePathsAndIndices(root->getChild(i), paths, indices); } } /** * Parses a MatchExpression from query string and passes that along with prefix, collator, and * indices to rateIndices. Verifies results against list of expected paths and expected indices. In * future, we may expand this test function to validate which indices are assigned to which node. */ void testRateIndices(const char* query, const char* prefix, const CollatorInterface* collator, const vector& indices, const char* expectedPathsStr, const std::set& expectedIndices) { // Parse and rate query. Some of the nodes in the rated tree // will be tagged after the rating process. BSONObj obj = fromjson(query); unique_ptr expr(parseMatchExpression(obj)); QueryPlannerIXSelect::rateIndices(expr.get(), prefix, indices, collator); // Retrieve a list of paths and a set of indices embedded in // tagged nodes. vector paths; std::set actualIndices; findRelevantTaggedNodePathsAndIndices(expr.get(), &paths, &actualIndices); // Compare the expected indices with the actual indices. if (actualIndices != expectedIndices) { mongoutils::str::stream ss; ss << "rateIndices(query=" << query << ", prefix=" << prefix << "): expected indices did not match actual indices. expected: " << toString(expectedIndices.begin(), expectedIndices.end()) << ". actual: " << toString(actualIndices.begin(), actualIndices.end()); FAIL(ss); } // Compare with expected list of paths. // First verify number of paths retrieved. vector expectedPaths = StringSplitter::split(expectedPathsStr, ","); if (paths.size() != expectedPaths.size()) { mongoutils::str::stream ss; ss << "rateIndices(query=" << query << ", prefix=" << prefix << "): unexpected number of tagged nodes found. expected: " << toString(expectedPaths.begin(), expectedPaths.end()) << ". actual: " << toString(paths.begin(), paths.end()); FAIL(ss); } // Next, check that value and order of each element match between the two lists. for (vector::const_iterator i = paths.begin(), j = expectedPaths.begin(); i != paths.end(); i++, j++) { if (*i == *j) { continue; } mongoutils::str::stream ss; ss << "rateIndices(query=" << query << ", prefix=" << prefix << "): unexpected path found. expected: " << *j << " " << toString(expectedPaths.begin(), expectedPaths.end()) << ". actual: " << *i << " " << toString(paths.begin(), paths.end()); FAIL(ss); } } /** * Calls testRateIndices with an empty set of indices and a null collation, so we only test which * nodes are tagged. */ void testRateIndicesTaggedNodePaths(const char* query, const char* prefix, const char* expectedPathsStr) { // Currently, we tag every indexable node even when no compatible // index is available. Hence, it is fine to pass an empty vector of // indices to rateIndices(). vector indices; std::set expectedIndices; testRateIndices(query, prefix, nullptr, indices, expectedPathsStr, expectedIndices); } /** * Basic test cases for rateIndices(). * Includes logical operators. */ TEST(QueryPlannerIXSelectTest, RateIndicesTaggedNodePathsBasic) { // Test arguments: query, prefix, comma-delimited list of expected paths testRateIndicesTaggedNodePaths("{}", "", ""); testRateIndicesTaggedNodePaths("{a: 1}", "", "a"); testRateIndicesTaggedNodePaths("{a: 1}", "c.", "c.a"); testRateIndicesTaggedNodePaths("{a: 1, b: 1}", "", "a,b"); testRateIndicesTaggedNodePaths("{a: {$in: [1]}}", "", "a"); testRateIndicesTaggedNodePaths("{$or: [{a: 1}, {b: 1}]}", "", "a,b"); } /** * Array test cases for rateIndices(). */ TEST(QueryPlannerIXSelectTest, RateIndicesTaggedNodePathArray) { testRateIndicesTaggedNodePaths("{a: {$elemMatch: {b: 1}}}", "", "a.b"); testRateIndicesTaggedNodePaths("{a: {$all: [{$elemMatch: {b: 1}}]}}", "", "a.b"); } /** * Negation test cases for rateIndices(). */ TEST(QueryPlannerIXSelectTest, RateIndicesTaggedNodePathsNegation) { testRateIndicesTaggedNodePaths("{a: {$ne: 1}}", "", "a,a"); testRateIndicesTaggedNodePaths("{a: {$nin: [1]}}", "", "a,a"); testRateIndicesTaggedNodePaths("{$nor: [{a: 1}, {b: 1}]}", "", ""); testRateIndicesTaggedNodePaths("{$and: [{a: 1}, {a: {$ne: 2}}]}", "", "a,a,a"); } /** * Array negation test cases for rateIndices(). */ TEST(QueryPlannerIXSelectTest, RateIndicesTaggedNodePathArrayNegation) { testRateIndicesTaggedNodePaths("{a: {$elemMatch: {b: {$ne: 1}}}}", "", "a.b,a.b"); testRateIndicesTaggedNodePaths("{a: {$all: [{$elemMatch: {b: {$ne: 1}}}]}}", "", "a.b,a.b"); } /** * If the collator is null, we select the relevant index with a null collator. */ TEST(QueryPlannerIXSelectTest, NullCollatorsMatch) { std::vector indices; indices.push_back(IndexEntry(BSON("a" << 1))); std::set expectedIndices = {0}; testRateIndices("{a: 'string'}", "", nullptr, indices, "a", expectedIndices); } /** * If the collator is not null, we do not select the relevant index with a null collator. */ TEST(QueryPlannerIXSelectTest, NonNullCollatorDoesNotMatchIndexWithNullCollator) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); std::vector indices; indices.push_back(IndexEntry(BSON("a" << 1))); std::set expectedIndices; testRateIndices("{a: 'string'}", "", &collator, indices, "a", expectedIndices); } /** * If the collator is null, we do not select the relevant index with a non-null collator. */ TEST(QueryPlannerIXSelectTest, NullCollatorDoesNotMatchIndexWithNonNullCollator) { IndexEntry index(BSON("a" << 1)); CollatorInterfaceMock indexCollator(CollatorInterfaceMock::MockType::kAlwaysEqual); index.collator = &indexCollator; std::vector indices; indices.push_back(index); std::set expectedIndices; testRateIndices("{a: 'string'}", "", nullptr, indices, "a", expectedIndices); } /** * If the collator is non-null, we select the relevant index with an equal collator. */ TEST(QueryPlannerIXSelectTest, EqualCollatorsMatch) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); CollatorInterfaceMock indexCollator(CollatorInterfaceMock::MockType::kAlwaysEqual); index.collator = &indexCollator; std::vector indices; indices.push_back(index); std::set expectedIndices = {0}; testRateIndices("{a: 'string'}", "", &collator, indices, "a", expectedIndices); } /** * If the collator is non-null, we do not select the relevant index with an unequal collator. */ TEST(QueryPlannerIXSelectTest, UnequalCollatorsDoNotMatch) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); CollatorInterfaceMock indexCollator(CollatorInterfaceMock::MockType::kReverseString); index.collator = &indexCollator; std::vector indices; indices.push_back(index); std::set expectedIndices; testRateIndices("{a: 'string'}", "", &collator, indices, "a", expectedIndices); } /** * If no string comparison is done, unequal collators are allowed. */ TEST(QueryPlannerIXSelectTest, NoStringComparison) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); CollatorInterfaceMock indexCollator(CollatorInterfaceMock::MockType::kReverseString); index.collator = &indexCollator; std::vector indices; indices.push_back(index); std::set expectedIndices = {0}; testRateIndices("{a: 1}", "", &collator, indices, "a", expectedIndices); } /** * $gt string comparison requires matching collator. */ TEST(QueryPlannerIXSelectTest, StringGTUnequalCollators) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); CollatorInterfaceMock indexCollator(CollatorInterfaceMock::MockType::kReverseString); index.collator = &indexCollator; std::vector indices; indices.push_back(index); std::set expectedIndices; testRateIndices("{a: {$gt: 'string'}}", "", &collator, indices, "a", expectedIndices); } /** * $gt string comparison requires matching collator. */ TEST(QueryPlannerIXSelectTest, StringGTEqualCollators) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); index.collator = &collator; std::vector indices; indices.push_back(index); std::set expectedIndices = {0}; testRateIndices("{a: {$gt: 'string'}}", "", &collator, indices, "a", expectedIndices); } /** * $gt array comparison requires matching collator. */ TEST(QueryPlannerIXSelectTest, ArrayGTUnequalCollators) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); CollatorInterfaceMock indexCollator(CollatorInterfaceMock::MockType::kReverseString); index.collator = &indexCollator; std::vector indices; indices.push_back(index); std::set expectedIndices; testRateIndices("{a: {$gt: ['string']}}", "", &collator, indices, "a", expectedIndices); } /** * $gt array comparison requires matching collator. */ TEST(QueryPlannerIXSelectTest, ArrayGTEqualCollators) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); index.collator = &collator; std::vector indices; indices.push_back(index); std::set expectedIndices = {0}; testRateIndices("{a: {$gt: ['string']}}", "", &collator, indices, "a", expectedIndices); } /** * $gt object comparison requires matching collator. */ TEST(QueryPlannerIXSelectTest, NestedObjectGTUnequalCollators) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); CollatorInterfaceMock indexCollator(CollatorInterfaceMock::MockType::kReverseString); index.collator = &indexCollator; std::vector indices; indices.push_back(index); std::set expectedIndices; testRateIndices("{a: {$gt: {b: 'string'}}}", "", &collator, indices, "a", expectedIndices); } /** * $gt object comparison requires matching collator. */ TEST(QueryPlannerIXSelectTest, NestedObjectGTEqualCollators) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); index.collator = &collator; std::vector indices; indices.push_back(index); std::set expectedIndices = {0}; testRateIndices("{a: {$gt: {b : 'string'}}}", "", &collator, indices, "a", expectedIndices); } /** * $gte string comparison requires matching collator. */ TEST(QueryPlannerIXSelectTest, StringGTEUnequalCollators) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); CollatorInterfaceMock indexCollator(CollatorInterfaceMock::MockType::kReverseString); index.collator = &indexCollator; std::vector indices; indices.push_back(index); std::set expectedIndices; testRateIndices("{a: {$gte: 'string'}}", "", &collator, indices, "a", expectedIndices); } /** * $gte string comparison requires matching collator. */ TEST(QueryPlannerIXSelectTest, StringGTEEqualCollators) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); index.collator = &collator; std::vector indices; indices.push_back(index); std::set expectedIndices = {0}; testRateIndices("{a: {$gte: 'string'}}", "", &collator, indices, "a", expectedIndices); } /** * $lt string comparison requires matching collator. */ TEST(QueryPlannerIXSelectTest, StringLTUnequalCollators) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); CollatorInterfaceMock indexCollator(CollatorInterfaceMock::MockType::kReverseString); index.collator = &indexCollator; std::vector indices; indices.push_back(index); std::set expectedIndices; testRateIndices("{a: {$lt: 'string'}}", "", &collator, indices, "a", expectedIndices); } /** * $lt string comparison requires matching collator. */ TEST(QueryPlannerIXSelectTest, StringLTEqualCollators) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); index.collator = &collator; std::vector indices; indices.push_back(index); std::set expectedIndices = {0}; testRateIndices("{a: {$lt: 'string'}}", "", &collator, indices, "a", expectedIndices); } /** * $lte string comparison requires matching collator. */ TEST(QueryPlannerIXSelectTest, StringLTEUnequalCollators) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); CollatorInterfaceMock indexCollator(CollatorInterfaceMock::MockType::kReverseString); index.collator = &indexCollator; std::vector indices; indices.push_back(index); std::set expectedIndices; testRateIndices("{a: {$lte: 'string'}}", "", &collator, indices, "a", expectedIndices); } /** * $lte string comparison requires matching collator. */ TEST(QueryPlannerIXSelectTest, StringLTEEqualCollators) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); index.collator = &collator; std::vector indices; indices.push_back(index); std::set expectedIndices = {0}; testRateIndices("{a: {$lte: 'string'}}", "", &collator, indices, "a", expectedIndices); } /** * If no string comparison is done in an 'in' expression, unequal collators are allowed. */ TEST(QueryPlannerIXSelectTest, NoStringComparisonInExpression) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); CollatorInterfaceMock indexCollator(CollatorInterfaceMock::MockType::kReverseString); index.collator = &indexCollator; std::vector indices; indices.push_back(index); std::set expectedIndices = {0}; testRateIndices("{a: {$in: [1, 2]}}", "", &collator, indices, "a", expectedIndices); } /** * If string comparison is done in an 'in' expression, matching collators are required. */ TEST(QueryPlannerIXSelectTest, StringComparisonInExpressionUnequalCollators) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); CollatorInterfaceMock indexCollator(CollatorInterfaceMock::MockType::kReverseString); index.collator = &indexCollator; std::vector indices; indices.push_back(index); std::set expectedIndices; testRateIndices("{a: {$in: [1, 2, 'b', 3]}}", "", &collator, indices, "a", expectedIndices); } /** * If string comparison is done in an 'in' expression, matching collators are required. */ TEST(QueryPlannerIXSelectTest, StringComparisonInExpressionEqualCollators) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); index.collator = &collator; std::vector indices; indices.push_back(index); std::set expectedIndices = {0}; testRateIndices("{a: {$in: [1, 2, 'b', 3]}}", "", &collator, indices, "a", expectedIndices); } /** * If no string comparison is done in a 'not' expression, unequal collators are allowed. */ TEST(QueryPlannerIXSelectTest, NoStringComparisonNotExpression) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); CollatorInterfaceMock indexCollator(CollatorInterfaceMock::MockType::kReverseString); index.collator = &indexCollator; std::vector indices; indices.push_back(index); std::set expectedIndices = {0}; testRateIndices("{a: {$not: {$gt: 1}}}", "", &collator, indices, "a", expectedIndices); } /** * If string comparison is done in a 'not' expression, matching collators are required. */ TEST(QueryPlannerIXSelectTest, StringComparisonNotExpressionUnequalCollators) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); CollatorInterfaceMock indexCollator(CollatorInterfaceMock::MockType::kReverseString); index.collator = &indexCollator; std::vector indices; indices.push_back(index); std::set expectedIndices; testRateIndices("{a: {$not: {$gt: 'a'}}}", "", &collator, indices, "a", expectedIndices); } /** * If string comparison is done in a 'not' expression, matching collators are required. */ TEST(QueryPlannerIXSelectTest, StringComparisonNotExpressionEqualCollators) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); index.collator = &collator; std::vector indices; indices.push_back(index); std::set expectedIndices = {0}; testRateIndices("{a: {$not: {$gt: 'a'}}}", "", &collator, indices, "a", expectedIndices); } /** * If no string comparison is done in an elemMatch value, unequal collators are allowed. */ TEST(QueryPlannerIXSelectTest, NoStringComparisonElemMatchValueExpression) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); CollatorInterfaceMock indexCollator(CollatorInterfaceMock::MockType::kReverseString); index.collator = &indexCollator; std::vector indices; indices.push_back(index); std::set expectedIndices = {0}; testRateIndices("{a: {$elemMatch: {$gt: 1}}}", "", &collator, indices, "a", expectedIndices); } /** * If string comparison is done in an elemMatch value, matching collators are required. */ TEST(QueryPlannerIXSelectTest, StringComparisonElemMatchValueExpressionUnequalCollators) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); CollatorInterfaceMock indexCollator(CollatorInterfaceMock::MockType::kReverseString); index.collator = &indexCollator; std::vector indices; indices.push_back(index); std::set expectedIndices; testRateIndices( "{a: {$elemMatch: {$gt: 'string'}}}", "", &collator, indices, "a", expectedIndices); } /** * If string comparison is done in an elemMatch value, matching collators are required. */ TEST(QueryPlannerIXSelectTest, StringComparisonElemMatchValueExpressionEqualCollators) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); index.collator = &collator; std::vector indices; indices.push_back(index); std::set expectedIndices = {0}; testRateIndices( "{a: {$elemMatch: {$gt: 'string'}}}", "", &collator, indices, "a", expectedIndices); } /** * If no string comparison is done in an 'in' in a 'not', unequal collators are allowed. */ TEST(QueryPlannerIXSelectTest, NoStringComparisonNotInExpression) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); CollatorInterfaceMock indexCollator(CollatorInterfaceMock::MockType::kReverseString); index.collator = &indexCollator; std::vector indices; indices.push_back(index); std::set expectedIndices = {0}; testRateIndices("{a: {$not: {$in: [1]}}}", "", &collator, indices, "a", expectedIndices); } /** * If string comparison is done in an 'in' in a 'not', matching collators are required. */ TEST(QueryPlannerIXSelectTest, StringComparisonNotInExpressionUnequalCollators) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); CollatorInterfaceMock indexCollator(CollatorInterfaceMock::MockType::kReverseString); index.collator = &indexCollator; std::vector indices; indices.push_back(index); std::set expectedIndices; testRateIndices("{a: {$not: {$in: ['a']}}}", "", &collator, indices, "a", expectedIndices); } /** * If string comparison is done in an 'in' in a 'not', matching collators are required. */ TEST(QueryPlannerIXSelectTest, StringComparisonNotInExpressionEqualCollators) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); index.collator = &collator; std::vector indices; indices.push_back(index); std::set expectedIndices = {0}; testRateIndices("{a: {$not: {$in: ['a']}}}", "", &collator, indices, "a", expectedIndices); } /** * If no string comparison is done in a 'nin', unequal collators are allowed. */ TEST(QueryPlannerIXSelectTest, NoStringComparisonNinExpression) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); CollatorInterfaceMock indexCollator(CollatorInterfaceMock::MockType::kReverseString); index.collator = &indexCollator; std::vector indices; indices.push_back(index); std::set expectedIndices = {0}; testRateIndices("{a: {$nin: [1]}}", "", &collator, indices, "a,a", expectedIndices); } /** * If string comparison is done in a 'nin', matching collators are required. */ TEST(QueryPlannerIXSelectTest, StringComparisonNinExpressionUnequalCollators) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); CollatorInterfaceMock indexCollator(CollatorInterfaceMock::MockType::kReverseString); index.collator = &indexCollator; std::vector indices; indices.push_back(index); std::set expectedIndices; testRateIndices("{a: {$nin: ['a']}}", "", &collator, indices, "a,a", expectedIndices); } /** * If string comparison is done in a 'nin', matching collators are required. */ TEST(QueryPlannerIXSelectTest, StringComparisonNinExpressionEqualCollators) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); index.collator = &collator; std::vector indices; indices.push_back(index); std::set expectedIndices = {0}; testRateIndices("{a: {$nin: ['a']}}", "", &collator, indices, "a,a", expectedIndices); } /** * If string comparison is done in an 'or', matching collators are required. */ TEST(QueryPlannerIXSelectTest, StringComparisonOrExpressionUnequalCollators) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); CollatorInterfaceMock indexCollator(CollatorInterfaceMock::MockType::kReverseString); index.collator = &indexCollator; std::vector indices; indices.push_back(index); std::set expectedIndices; testRateIndices("{$or: [{a: 'string'}]}", "", &collator, indices, "a", expectedIndices); } /** * If string comparison is done in an 'or', matching collators are required. */ TEST(QueryPlannerIXSelectTest, StringComparisonOrExpressionEqualCollators) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); index.collator = &collator; std::vector indices; indices.push_back(index); std::set expectedIndices = {0}; testRateIndices("{$or: [{a: 'string'}]}", "", &collator, indices, "a", expectedIndices); } /** * If string comparison is done in an 'and', matching collators are required. */ TEST(QueryPlannerIXSelectTest, StringComparisonAndExpressionUnequalCollators) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); CollatorInterfaceMock indexCollator(CollatorInterfaceMock::MockType::kReverseString); index.collator = &indexCollator; std::vector indices; indices.push_back(index); std::set expectedIndices; testRateIndices("{$and: [{a: 'string'}]}", "", &collator, indices, "a", expectedIndices); } /** * If string comparison is done in an 'and', matching collators are required. */ TEST(QueryPlannerIXSelectTest, StringComparisonAndExpressionEqualCollators) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); index.collator = &collator; std::vector indices; indices.push_back(index); std::set expectedIndices = {0}; testRateIndices("{$and: [{a: 'string'}]}", "", &collator, indices, "a", expectedIndices); } /** * If string comparison is done in an 'all', matching collators are required. */ TEST(QueryPlannerIXSelectTest, StringComparisonAllExpressionUnequalCollators) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); CollatorInterfaceMock indexCollator(CollatorInterfaceMock::MockType::kReverseString); index.collator = &indexCollator; std::vector indices; indices.push_back(index); std::set expectedIndices; testRateIndices("{a: {$all: ['string']}}", "", &collator, indices, "a", expectedIndices); } /** * If string comparison is done in an 'all', matching collators are required. */ TEST(QueryPlannerIXSelectTest, StringComparisonAllExpressionEqualCollators) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); index.collator = &collator; std::vector indices; indices.push_back(index); std::set expectedIndices = {0}; testRateIndices("{a: {$all: ['string']}}", "", &collator, indices, "a", expectedIndices); } /** * If string comparison is done in an elemMatch object, matching collators are required. */ TEST(QueryPlannerIXSelectTest, StringComparisonElemMatchObjectExpressionUnequalCollators) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a.b" << 1)); CollatorInterfaceMock indexCollator(CollatorInterfaceMock::MockType::kReverseString); index.collator = &indexCollator; std::vector indices; indices.push_back(index); std::set expectedIndices; testRateIndices( "{a: {$elemMatch: {b: 'string'}}}", "", &collator, indices, "a.b", expectedIndices); } /** * If string comparison is done in an elemMatch object, matching collators are required. */ TEST(QueryPlannerIXSelectTest, StringComparisonElemMatchObjectExpressionEqualCollators) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a.b" << 1)); index.collator = &collator; std::vector indices; indices.push_back(index); std::set expectedIndices = {0}; testRateIndices( "{a: {$elemMatch: {b: 'string'}}}", "", &collator, indices, "a.b", expectedIndices); } /** * If no string comparison is done in a query containing $mod, unequal collators are allowed. */ TEST(QueryPlannerIXSelectTest, NoStringComparisonMod) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); CollatorInterfaceMock indexCollator(CollatorInterfaceMock::MockType::kReverseString); index.collator = &indexCollator; std::vector indices; indices.push_back(index); std::set expectedIndices = {0}; testRateIndices("{a: {$mod: [2, 0]}}", "", &collator, indices, "a", expectedIndices); } /** * If no string comparison is done in a query containing $exists, unequal collators are allowed. */ TEST(QueryPlannerIXSelectTest, NoStringComparisonExists) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); CollatorInterfaceMock indexCollator(CollatorInterfaceMock::MockType::kReverseString); index.collator = &indexCollator; std::vector indices; indices.push_back(index); std::set expectedIndices = {0}; testRateIndices("{a: {$exists: true}}", "", &collator, indices, "a", expectedIndices); } /** * If no string comparison is done in a query containing $type, unequal collators are allowed. */ TEST(QueryPlannerIXSelectTest, NoStringComparisonType) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); IndexEntry index(BSON("a" << 1)); CollatorInterfaceMock indexCollator(CollatorInterfaceMock::MockType::kReverseString); index.collator = &indexCollator; std::vector indices; indices.push_back(index); std::set expectedIndices = {0}; std::set testPatterns = { "{a: {$type: 'string'}}", "{a: {$type: 'object'}}", "{a: {$type: 'array'}}"}; for (const auto& pattern : testPatterns) { testRateIndices(pattern.c_str(), "", &collator, indices, "a", expectedIndices); } } } // namespace