summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDavid Storch <david.storch@10gen.com>2017-06-08 17:47:22 -0400
committerDavid Storch <david.storch@10gen.com>2017-06-20 13:41:53 -0400
commit50623596fb62da49a2b1495d5e0cd852faf91f9f (patch)
tree6d44397d8ade87fa1a29bb888a9251cdaf44a53e /src
parent9022c80626cc1316f83e657591f1f19f3db7237b (diff)
downloadmongo-50623596fb62da49a2b1495d5e0cd852faf91f9f.tar.gz
SERVER-19402 Change find command semantics for sorting on an array field.
This eliminates the behavior in which the lowest-valued in-bounds index key was chosen as the sort key. After this change, we instead choose the lowest key overall, which may or may not be in-bounds. This change prevents the sort order from depending on either the query predicate or the implementation details of the query planner. Note that it is no longer correct for a multikey index to provide a sort over an array field. However, a non-array field of a multikey index can provide a sort when that index has path-level multikeyness metadata.
Diffstat (limited to 'src')
-rw-r--r--src/mongo/db/exec/sort_key_generator.cpp129
-rw-r--r--src/mongo/db/exec/sort_key_generator.h32
-rw-r--r--src/mongo/db/exec/sort_test.cpp45
-rw-r--r--src/mongo/db/query/get_executor.cpp13
-rw-r--r--src/mongo/db/query/planner_analysis.cpp2
-rw-r--r--src/mongo/db/query/query_planner_geo_test.cpp3
-rw-r--r--src/mongo/db/query/query_solution.cpp45
-rw-r--r--src/mongo/db/query/query_solution.h4
-rw-r--r--src/mongo/db/query/query_solution_test.cpp66
-rw-r--r--src/mongo/db/query/stage_builder.cpp8
-rw-r--r--src/mongo/dbtests/query_stage_ensure_sorted.cpp4
-rw-r--r--src/mongo/dbtests/query_stage_sort.cpp6
-rw-r--r--src/mongo/dbtests/sort_key_generator_test.cpp49
13 files changed, 171 insertions, 235 deletions
diff --git a/src/mongo/db/exec/sort_key_generator.cpp b/src/mongo/db/exec/sort_key_generator.cpp
index c562be7ed26..8f145dffaf0 100644
--- a/src/mongo/db/exec/sort_key_generator.cpp
+++ b/src/mongo/db/exec/sort_key_generator.cpp
@@ -42,7 +42,6 @@
#include "mongo/db/exec/working_set_computed_data.h"
#include "mongo/db/matcher/extensions_callback_noop.h"
#include "mongo/db/query/collation/collator_interface.h"
-#include "mongo/db/query/query_planner.h"
#include "mongo/stdx/memory.h"
#include "mongo/util/log.h"
@@ -54,13 +53,8 @@ namespace mongo {
SortKeyGenerator::SortKeyGenerator(OperationContext* opCtx,
const BSONObj& sortSpec,
- const BSONObj& queryObj,
const CollatorInterface* collator)
- : _collator(collator) {
- _hasBounds = false;
- _sortHasMeta = false;
- _rawSortSpec = sortSpec;
-
+ : _collator(collator), _rawSortSpec(sortSpec) {
// 'sortSpec' can be a mix of $meta and index key expressions. We pick it apart so that
// we only generate Btree keys for the index key expressions.
@@ -68,17 +62,14 @@ SortKeyGenerator::SortKeyGenerator(OperationContext* opCtx,
// key generator below as part of generating sort keys for the docs.
BSONObjBuilder btreeBob;
- BSONObjIterator it(sortSpec);
- while (it.more()) {
- BSONElement elt = it.next();
+ for (auto&& elt : sortSpec) {
if (elt.isNumber()) {
- // Btree key. elt (should be) foo: 1 or foo: -1.
btreeBob.append(elt);
- } else if (QueryRequest::isTextScoreMeta(elt)) {
- _sortHasMeta = true;
} else {
- // Sort spec. should have been validated before here.
- verify(false);
+ // If this field of the sort pattern is non-numeric, we expect it to be a text-score
+ // meta sort. This is validated upstream.
+ invariant(QueryRequest::isTextScoreMeta(elt));
+ _sortHasMeta = true;
}
}
@@ -95,21 +86,13 @@ SortKeyGenerator::SortKeyGenerator(OperationContext* opCtx,
// the sort order.
std::vector<const char*> fieldNames;
std::vector<BSONElement> fixed;
- BSONObjIterator btreeIt(_btreeObj);
- while (btreeIt.more()) {
- BSONElement patternElt = btreeIt.next();
+ for (auto&& patternElt : _btreeObj) {
fieldNames.push_back(patternElt.fieldName());
fixed.push_back(BSONElement());
}
- _keyGen.reset(new BtreeKeyGeneratorV1(fieldNames, fixed, false /* not sparse */, _collator));
-
- // The bounds checker only works on the Btree part of the sort key.
- getBoundsForSort(opCtx, queryObj, _btreeObj);
-
- if (_hasBounds) {
- _boundsChecker.reset(new IndexBoundsChecker(&_bounds, _btreeObj, 1 /* == order */));
- }
+ constexpr bool isSparse = false;
+ _keyGen = stdx::make_unique<BtreeKeyGeneratorV1>(fieldNames, fixed, isSparse, _collator);
}
Status SortKeyGenerator::getSortKey(const WorkingSetMember& member, BSONObj* objOut) const {
@@ -203,93 +186,10 @@ StatusWith<BSONObj> SortKeyGenerator::getSortKeyFromObject(const WorkingSetMembe
// Key generator isn't sparse so we should at least get an all-null key.
invariant(!keys.empty());
- // No bounds? No problem! Use the first key.
- if (!_hasBounds) {
- // Note that we sort 'keys' according to the pattern '_btreeObj'.
- return *keys.begin();
- }
-
- // To decide which key to use in sorting, we must consider not only the sort pattern but
- // the query. Assume we have the query {a: {$gte: 5}} and a document {a:1}. That
- // document wouldn't match the query. As such, the key '1' in an array {a: [1, 10]}
- // should not be considered as being part of the result set and thus that array cannot
- // sort using the key '1'. To ensure that the keys we sort by are valid w.r.t. the
- // query we use a bounds checker.
- verify(NULL != _boundsChecker.get());
- for (BSONObjSet::const_iterator it = keys.begin(); it != keys.end(); ++it) {
- if (_boundsChecker->isValidKey(*it)) {
- return *it;
- }
- }
-
- // No key is in our bounds.
- // TODO: will this ever happen? don't think it should.
+ // The sort key is the first index key, ordered according to the pattern '_btreeObj'.
return *keys.begin();
}
-void SortKeyGenerator::getBoundsForSort(OperationContext* opCtx,
- const BSONObj& queryObj,
- const BSONObj& sortObj) {
- QueryPlannerParams params;
- params.options = QueryPlannerParams::NO_TABLE_SCAN;
-
- // We're creating a "virtual index" with key pattern equal to the sort order. The "virtual
- // index" has the collation which the query is using.
- IndexEntry sortOrder(sortObj,
- IndexNames::BTREE,
- true,
- MultikeyPaths{},
- false,
- false,
- "doesnt_matter",
- NULL,
- BSONObj(),
- _collator);
- params.indices.push_back(sortOrder);
-
- auto qr = stdx::make_unique<QueryRequest>(NamespaceString("fake.ns"));
- qr->setFilter(queryObj);
- if (_collator) {
- qr->setCollation(_collator->getSpec().toBSON());
- }
-
- auto statusWithQueryForSort =
- CanonicalQuery::canonicalize(opCtx, std::move(qr), ExtensionsCallbackNoop());
- verify(statusWithQueryForSort.isOK());
- std::unique_ptr<CanonicalQuery> queryForSort = std::move(statusWithQueryForSort.getValue());
-
- std::vector<QuerySolution*> solns;
- LOG(5) << "Sort key generation: Planning to obtain bounds for sort.";
- QueryPlanner::plan(*queryForSort, params, &solns).transitional_ignore();
-
- // TODO: are there ever > 1 solns? If so, do we look for a specific soln?
- if (1 == solns.size()) {
- IndexScanNode* ixScan = NULL;
- QuerySolutionNode* rootNode = solns[0]->root.get();
-
- if (rootNode->getType() == STAGE_FETCH) {
- FetchNode* fetchNode = static_cast<FetchNode*>(rootNode);
- if (fetchNode->children[0]->getType() != STAGE_IXSCAN) {
- delete solns[0];
- // No bounds.
- return;
- }
- ixScan = static_cast<IndexScanNode*>(fetchNode->children[0]);
- } else if (rootNode->getType() == STAGE_IXSCAN) {
- ixScan = static_cast<IndexScanNode*>(rootNode);
- }
-
- if (ixScan) {
- _bounds.fields.swap(ixScan->bounds.fields);
- _hasBounds = true;
- }
- }
-
- for (size_t i = 0; i < solns.size(); ++i) {
- delete solns[i];
- }
-}
-
//
// SortKeyGeneratorStage
//
@@ -300,13 +200,8 @@ SortKeyGeneratorStage::SortKeyGeneratorStage(OperationContext* opCtx,
PlanStage* child,
WorkingSet* ws,
const BSONObj& sortSpecObj,
- const BSONObj& queryObj,
const CollatorInterface* collator)
- : PlanStage(kStageType, opCtx),
- _ws(ws),
- _sortSpec(sortSpecObj),
- _query(queryObj),
- _collator(collator) {
+ : PlanStage(kStageType, opCtx), _ws(ws), _sortSpec(sortSpecObj), _collator(collator) {
_children.emplace_back(child);
}
@@ -316,7 +211,7 @@ bool SortKeyGeneratorStage::isEOF() {
PlanStage::StageState SortKeyGeneratorStage::doWork(WorkingSetID* out) {
if (!_sortKeyGen) {
- _sortKeyGen = stdx::make_unique<SortKeyGenerator>(getOpCtx(), _sortSpec, _query, _collator);
+ _sortKeyGen = stdx::make_unique<SortKeyGenerator>(getOpCtx(), _sortSpec, _collator);
return PlanStage::NEED_TIME;
}
diff --git a/src/mongo/db/exec/sort_key_generator.h b/src/mongo/db/exec/sort_key_generator.h
index d284bad443d..986ad1b7633 100644
--- a/src/mongo/db/exec/sort_key_generator.h
+++ b/src/mongo/db/exec/sort_key_generator.h
@@ -50,16 +50,11 @@ public:
/**
* 'sortSpec' is the BSONObj in the .sort(...) clause.
*
- * 'queryObj' is the BSONObj in the .find(...) clause. For multikey arrays we have to
- * ensure that the value we select to sort by is within bounds generated by
- * executing 'queryObj' using the virtual index with key pattern 'sortSpec'.
- *
* 'opCtx' must point to a valid OperationContext, but 'opCtx' does not need to outlive the
* constructed SortKeyGenerator.
*/
SortKeyGenerator(OperationContext* opCtx,
const BSONObj& sortSpec,
- const BSONObj& queryObj,
const CollatorInterface* collator);
/**
@@ -75,17 +70,7 @@ private:
StatusWith<BSONObj> getSortKeyFromIndexKey(const WorkingSetMember& member) const;
StatusWith<BSONObj> getSortKeyFromObject(const WorkingSetMember& member) const;
- /**
- * In order to emulate the existing sort behavior we must make unindexed sort behavior as
- * consistent as possible with indexed sort behavior. As such, we must only consider index
- * keys that we would encounter if we were answering the query using the sort-providing
- * index.
- *
- * Populates _hasBounds and _bounds.
- */
- void getBoundsForSort(OperationContext* opCtx, const BSONObj& queryObj, const BSONObj& sortObj);
-
- const CollatorInterface* _collator;
+ const CollatorInterface* _collator = nullptr;
// The raw object in .sort()
BSONObj _rawSortSpec;
@@ -94,19 +79,10 @@ private:
BSONObj _btreeObj;
// If we're not sorting with a $meta value we can short-cut some work.
- bool _sortHasMeta;
-
- // True if the bounds are valid.
- bool _hasBounds;
-
- // The bounds generated from the query we're sorting.
- IndexBounds _bounds;
+ bool _sortHasMeta = false;
// Helper to extract sorting keys from documents.
std::unique_ptr<BtreeKeyGenerator> _keyGen;
-
- // Helper to filter keys, ensuring keys generated with _keyGen are within _bounds.
- std::unique_ptr<IndexBoundsChecker> _boundsChecker;
};
/**
@@ -119,7 +95,6 @@ public:
PlanStage* child,
WorkingSet* ws,
const BSONObj& sortSpecObj,
- const BSONObj& queryObj,
const CollatorInterface* collator);
bool isEOF() final;
@@ -142,9 +117,6 @@ private:
// The raw sort pattern as expressed by the user.
const BSONObj _sortSpec;
- // The raw query as expressed by the user.
- const BSONObj _query;
-
const CollatorInterface* _collator;
std::unique_ptr<SortKeyGenerator> _sortKeyGen;
diff --git a/src/mongo/db/exec/sort_test.cpp b/src/mongo/db/exec/sort_test.cpp
index 8f200ffb0a3..68a2240db7b 100644
--- a/src/mongo/db/exec/sort_test.cpp
+++ b/src/mongo/db/exec/sort_test.cpp
@@ -66,7 +66,7 @@ public:
/**
* Test function to verify sort stage.
- * SortStageParams will be initialized using patternStr, collator, queryStr and limit.
+ * SortStageParams will be initialized using patternStr, collator, and limit.
* inputStr represents the input data set in a BSONObj.
* {input: [doc1, doc2, doc3, ...]}
* expectedStr represents the expected sorted data set.
@@ -74,7 +74,6 @@ public:
*/
void testWork(const char* patternStr,
CollatorInterface* collator,
- const char* queryStr,
int limit,
const char* inputStr,
const char* expectedStr) {
@@ -107,12 +106,8 @@ public:
params.pattern = fromjson(patternStr);
params.limit = limit;
- auto sortKeyGen = stdx::make_unique<SortKeyGeneratorStage>(getOpCtx(),
- queuedDataStage.release(),
- &ws,
- params.pattern,
- fromjson(queryStr),
- collator);
+ auto sortKeyGen = stdx::make_unique<SortKeyGeneratorStage>(
+ getOpCtx(), queuedDataStage.release(), &ws, params.pattern, collator);
SortStage sort(getOpCtx(), params, &ws, sortKeyGen.release());
@@ -151,8 +146,8 @@ public:
mongoutils::str::stream ss;
// Even though we have the original string representation of the expected output,
// we invoke BSONObj::toString() to get a format consistent with outputObj.
- ss << "Unexpected sort result with query=" << queryStr << "; pattern=" << patternStr
- << "; limit=" << limit << ":\n"
+ ss << "Unexpected sort result with pattern=" << patternStr << "; limit=" << limit
+ << ":\n"
<< "Expected: " << expectedObj.toString() << "\n"
<< "Actual: " << outputObj.toString() << "\n";
FAIL(ss);
@@ -177,7 +172,7 @@ TEST_F(SortStageTest, SortEmptyWorkingSet) {
// QueuedDataStage will be owned by SortStage.
auto queuedDataStage = stdx::make_unique<QueuedDataStage>(getOpCtx(), &ws);
auto sortKeyGen = stdx::make_unique<SortKeyGeneratorStage>(
- getOpCtx(), queuedDataStage.release(), &ws, BSONObj(), BSONObj(), nullptr);
+ getOpCtx(), queuedDataStage.release(), &ws, BSONObj(), nullptr);
SortStageParams params;
SortStage sort(getOpCtx(), params, &ws, sortKeyGen.release());
@@ -216,7 +211,6 @@ TEST_F(SortStageTest, SortEmptyWorkingSet) {
TEST_F(SortStageTest, SortAscending) {
testWork("{a: 1}",
nullptr,
- "{}",
0,
"{input: [{a: 2}, {a: 1}, {a: 3}]}",
"{output: [{a: 1}, {a: 2}, {a: 3}]}");
@@ -225,7 +219,6 @@ TEST_F(SortStageTest, SortAscending) {
TEST_F(SortStageTest, SortDescending) {
testWork("{a: -1}",
nullptr,
- "{}",
0,
"{input: [{a: 2}, {a: 1}, {a: 3}]}",
"{output: [{a: 3}, {a: 2}, {a: 1}]}");
@@ -234,7 +227,6 @@ TEST_F(SortStageTest, SortDescending) {
TEST_F(SortStageTest, SortIrrelevantSortKey) {
testWork("{b: 1}",
nullptr,
- "{}",
0,
"{input: [{a: 2}, {a: 1}, {a: 3}]}",
"{output: [{a: 2}, {a: 1}, {a: 3}]}");
@@ -247,21 +239,13 @@ TEST_F(SortStageTest, SortIrrelevantSortKey) {
//
TEST_F(SortStageTest, SortAscendingWithLimit) {
- testWork("{a: 1}",
- nullptr,
- "{}",
- 2,
- "{input: [{a: 2}, {a: 1}, {a: 3}]}",
- "{output: [{a: 1}, {a: 2}]}");
+ testWork(
+ "{a: 1}", nullptr, 2, "{input: [{a: 2}, {a: 1}, {a: 3}]}", "{output: [{a: 1}, {a: 2}]}");
}
TEST_F(SortStageTest, SortDescendingWithLimit) {
- testWork("{a: -1}",
- nullptr,
- "{}",
- 2,
- "{input: [{a: 2}, {a: 1}, {a: 3}]}",
- "{output: [{a: 3}, {a: 2}]}");
+ testWork(
+ "{a: -1}", nullptr, 2, "{input: [{a: 2}, {a: 1}, {a: 3}]}", "{output: [{a: 3}, {a: 2}]}");
}
//
@@ -273,7 +257,6 @@ TEST_F(SortStageTest, SortDescendingWithLimit) {
TEST_F(SortStageTest, SortAscendingWithLimitGreaterThanInputSize) {
testWork("{a: 1}",
nullptr,
- "{}",
10,
"{input: [{a: 2}, {a: 1}, {a: 3}]}",
"{output: [{a: 1}, {a: 2}, {a: 3}]}");
@@ -282,7 +265,6 @@ TEST_F(SortStageTest, SortAscendingWithLimitGreaterThanInputSize) {
TEST_F(SortStageTest, SortDescendingWithLimitGreaterThanInputSize) {
testWork("{a: -1}",
nullptr,
- "{}",
10,
"{input: [{a: 2}, {a: 1}, {a: 3}]}",
"{output: [{a: 3}, {a: 2}, {a: 1}]}");
@@ -294,19 +276,17 @@ TEST_F(SortStageTest, SortDescendingWithLimitGreaterThanInputSize) {
//
TEST_F(SortStageTest, SortAscendingWithLimitOfOne) {
- testWork("{a: 1}", nullptr, "{}", 1, "{input: [{a: 2}, {a: 1}, {a: 3}]}", "{output: [{a: 1}]}");
+ testWork("{a: 1}", nullptr, 1, "{input: [{a: 2}, {a: 1}, {a: 3}]}", "{output: [{a: 1}]}");
}
TEST_F(SortStageTest, SortDescendingWithLimitOfOne) {
- testWork(
- "{a: -1}", nullptr, "{}", 1, "{input: [{a: 2}, {a: 1}, {a: 3}]}", "{output: [{a: 3}]}");
+ testWork("{a: -1}", nullptr, 1, "{input: [{a: 2}, {a: 1}, {a: 3}]}", "{output: [{a: 3}]}");
}
TEST_F(SortStageTest, SortAscendingWithCollation) {
CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
testWork("{a: 1}",
&collator,
- "{}",
0,
"{input: [{a: 'ba'}, {a: 'aa'}, {a: 'ab'}]}",
"{output: [{a: 'aa'}, {a: 'ba'}, {a: 'ab'}]}");
@@ -316,7 +296,6 @@ TEST_F(SortStageTest, SortDescendingWithCollation) {
CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
testWork("{a: -1}",
&collator,
- "{}",
0,
"{input: [{a: 'ba'}, {a: 'aa'}, {a: 'ab'}]}",
"{output: [{a: 'ab'}, {a: 'ba'}, {a: 'aa'}]}");
diff --git a/src/mongo/db/query/get_executor.cpp b/src/mongo/db/query/get_executor.cpp
index 40c52f5b74c..5367ce9c82b 100644
--- a/src/mongo/db/query/get_executor.cpp
+++ b/src/mongo/db/query/get_executor.cpp
@@ -300,13 +300,12 @@ StatusWith<PrepareExecutionResult> prepareExecution(OperationContext* opCtx,
// Add a SortKeyGeneratorStage if there is a $meta sortKey projection.
if (canonicalQuery->getProj()->wantSortKey()) {
- root = make_unique<SortKeyGeneratorStage>(
- opCtx,
- root.release(),
- ws,
- canonicalQuery->getQueryRequest().getSort(),
- canonicalQuery->getQueryRequest().getFilter(),
- canonicalQuery->getCollator());
+ root =
+ make_unique<SortKeyGeneratorStage>(opCtx,
+ root.release(),
+ ws,
+ canonicalQuery->getQueryRequest().getSort(),
+ canonicalQuery->getCollator());
}
// Stuff the right data into the params depending on what proj impl we use.
diff --git a/src/mongo/db/query/planner_analysis.cpp b/src/mongo/db/query/planner_analysis.cpp
index 19bda7e3cd1..5b72f1c98fa 100644
--- a/src/mongo/db/query/planner_analysis.cpp
+++ b/src/mongo/db/query/planner_analysis.cpp
@@ -525,7 +525,6 @@ QuerySolutionNode* QueryPlannerAnalysis::analyzeSort(const CanonicalQuery& query
// And build the full sort stage. The sort stage has to have a sort key generating stage
// as its child, supplying it with the appropriate sort keys.
SortKeyGeneratorNode* keyGenNode = new SortKeyGeneratorNode();
- keyGenNode->queryObj = qr.getFilter();
keyGenNode->sortSpec = sortObj;
keyGenNode->children.push_back(solnRoot);
solnRoot = keyGenNode;
@@ -796,7 +795,6 @@ QuerySolution* QueryPlannerAnalysis::analyzeDataAccess(
// generate the sort key computed data.
if (!hasSortStage && query.getProj()->wantSortKey()) {
SortKeyGeneratorNode* keyGenNode = new SortKeyGeneratorNode();
- keyGenNode->queryObj = qr.getFilter();
keyGenNode->sortSpec = qr.getSort();
keyGenNode->children.push_back(solnRoot.release());
solnRoot.reset(keyGenNode);
diff --git a/src/mongo/db/query/query_planner_geo_test.cpp b/src/mongo/db/query/query_planner_geo_test.cpp
index 96b71acdb3d..a4efc42195b 100644
--- a/src/mongo/db/query/query_planner_geo_test.cpp
+++ b/src/mongo/db/query/query_planner_geo_test.cpp
@@ -708,7 +708,8 @@ TEST_F(QueryPlannerTest, CompoundGeoNoGeoPredicateMultikey) {
"{sort: {pattern: {creationDate: 1}, limit: 0, node: {sortKeyGen: "
"{node: {cscan: {dir: 1}}}}}}");
assertSolutionExists(
- "{fetch: {node: {ixscan: {pattern: {creationDate: 1, 'foo.bar': '2dsphere'}}}}}");
+ "{sort: {pattern: {creationDate: 1}, limit: 0, node: {sortKeyGen: {node: {fetch: {node: "
+ "{ixscan: {pattern: {creationDate: 1, 'foo.bar': '2dsphere'}}}}}}}}}");
}
// Test that a 2dsphere index can satisfy a whole index scan solution if the query has a GEO
diff --git a/src/mongo/db/query/query_solution.cpp b/src/mongo/db/query/query_solution.cpp
index 191924eda68..5eec4719842 100644
--- a/src/mongo/db/query/query_solution.cpp
+++ b/src/mongo/db/query/query_solution.cpp
@@ -653,9 +653,30 @@ std::set<StringData> IndexScanNode::getFieldsWithStringBounds(const IndexBounds&
return ret;
}
+namespace {
+std::set<StringData> getMultikeyFields(const BSONObj& keyPattern,
+ const MultikeyPaths& multikeyPaths) {
+ std::set<StringData> multikeyFields;
+ size_t i = 0;
+ for (auto&& elem : keyPattern) {
+ if (!multikeyPaths[i].empty()) {
+ multikeyFields.insert(elem.fieldNameStringData());
+ }
+ ++i;
+ }
+ return multikeyFields;
+}
+} // namespace
+
void IndexScanNode::computeProperties() {
_sorts.clear();
+ // If the index is multikey but does not have path-level multikey metadata, then this index
+ // cannot provide any sorts.
+ if (index.multikey && index.multikeyPaths.empty()) {
+ return;
+ }
+
BSONObj sortPattern = QueryPlannerAnalysis::getSortPattern(index.keyPattern);
if (direction == -1) {
sortPattern = QueryPlannerCommon::reverseSortObj(sortPattern);
@@ -732,6 +753,27 @@ void IndexScanNode::computeProperties() {
}
}
}
+
+ // We cannot provide a sort which involves a multikey field. Prune such sort orders, if the
+ // index is multikey.
+ if (index.multikey) {
+ auto multikeyFields = getMultikeyFields(index.keyPattern, index.multikeyPaths);
+ for (auto sortsIt = _sorts.begin(); sortsIt != _sorts.end();) {
+ bool foundMultikeyField = false;
+ for (auto&& elt : *sortsIt) {
+ if (multikeyFields.find(elt.fieldNameStringData()) != multikeyFields.end()) {
+ foundMultikeyField = true;
+ break;
+ }
+ }
+
+ if (foundMultikeyField) {
+ sortsIt = _sorts.erase(sortsIt);
+ } else {
+ ++sortsIt;
+ }
+ }
+ }
}
QuerySolutionNode* IndexScanNode::clone() const {
@@ -840,8 +882,6 @@ void SortKeyGeneratorNode::appendToString(mongoutils::str::stream* ss, int inden
*ss << "SORT_KEY_GENERATOR\n";
addIndent(ss, indent + 1);
*ss << "sortSpec = " << sortSpec.toString() << '\n';
- addIndent(ss, indent + 1);
- *ss << "queryObj = " << queryObj.toString() << '\n';
addCommon(ss, indent);
addIndent(ss, indent + 1);
*ss << "Child:" << '\n';
@@ -851,7 +891,6 @@ void SortKeyGeneratorNode::appendToString(mongoutils::str::stream* ss, int inden
QuerySolutionNode* SortKeyGeneratorNode::clone() const {
SortKeyGeneratorNode* copy = new SortKeyGeneratorNode();
cloneBaseData(copy);
- copy->queryObj = this->queryObj;
copy->sortSpec = this->sortSpec;
return copy;
}
diff --git a/src/mongo/db/query/query_solution.h b/src/mongo/db/query/query_solution.h
index 7e68378042d..c2187385adb 100644
--- a/src/mongo/db/query/query_solution.h
+++ b/src/mongo/db/query/query_solution.h
@@ -597,10 +597,6 @@ struct SortKeyGeneratorNode : public QuerySolutionNode {
void appendToString(mongoutils::str::stream* ss, int indent) const final;
- // The query predicate provided by the user. For sorted by an array field, the sort key depends
- // on the predicate.
- BSONObj queryObj;
-
// The user-supplied sort pattern.
BSONObj sortSpec;
};
diff --git a/src/mongo/db/query/query_solution_test.cpp b/src/mongo/db/query/query_solution_test.cpp
index fe9749d49c3..a3a76deaa43 100644
--- a/src/mongo/db/query/query_solution_test.cpp
+++ b/src/mongo/db/query/query_solution_test.cpp
@@ -830,4 +830,70 @@ TEST(QuerySolutionTest, MultikeyIndexCannotCoverFieldWithAnyMultikeyPathComponen
ASSERT_TRUE(node->hasField("e"));
}
+TEST(QuerySolutionTest, MultikeyIndexWithoutPathLevelInfoCannotProvideAnySorts) {
+ IndexScanNode node{IndexEntry(BSON("a" << 1 << "b" << 1 << "c" << 1))};
+ node.index.multikey = true;
+
+ {
+ OrderedIntervalList oil{};
+ oil.name = "a";
+ oil.intervals.push_back(IndexBoundsBuilder::makePointInterval(BSON("" << 1)));
+ node.bounds.fields.push_back(oil);
+ }
+
+ for (auto&& name : {"b"_sd, "c"_sd}) {
+ OrderedIntervalList oil{};
+ oil.name = name.toString();
+ oil.intervals.push_back(IndexBoundsBuilder::makeRangeInterval(
+ BSON("" << 1 << "" << 2), BoundInclusion::kIncludeBothStartAndEndKeys));
+ node.bounds.fields.push_back(oil);
+ }
+
+ node.computeProperties();
+ ASSERT(node.getSort().empty());
+}
+
+TEST(QuerySolutionTest, SimpleRangeAllEqualExcludesFieldWithMultikeyComponent) {
+ IndexScanNode node{
+ IndexEntry(BSON("a" << 1 << "b" << 1 << "c.z" << 1 << "d" << 1 << "e" << 1))};
+ node.bounds.isSimpleRange = true;
+ node.bounds.startKey = BSON("a" << 1 << "b" << 1 << "c.z" << 1 << "d" << 1 << "e" << 1);
+ node.bounds.endKey = BSON("a" << 1 << "b" << 1 << "c.z" << 1 << "d" << 1 << "e" << 1);
+
+ // Add metadata indicating that "c.z" is multikey.
+ node.index.multikey = true;
+ node.index.multikeyPaths = MultikeyPaths{{}, {}, {1U}, {}, {}};
+
+ node.computeProperties();
+
+ ASSERT_EQUALS(node.getSort().size(), 4U);
+ ASSERT(node.getSort().count(BSON("a" << 1 << "b" << 1)));
+ ASSERT(node.getSort().count(BSON("a" << 1)));
+ ASSERT(node.getSort().count(BSON("d" << 1 << "e" << 1)));
+ ASSERT(node.getSort().count(BSON("e" << 1)));
+}
+
+TEST(QuerySolutionTest, NonSimpleRangeAllEqualExcludesFieldWithMultikeyComponent) {
+ IndexScanNode node{
+ IndexEntry(BSON("a" << 1 << "b" << 1 << "c.z" << 1 << "d" << 1 << "e" << 1))};
+ // Add metadata indicating that "c.z" is multikey.
+ node.index.multikey = true;
+ node.index.multikeyPaths = MultikeyPaths{{}, {}, {1U}, {}, {}};
+
+ for (auto&& name : {"a"_sd, "b"_sd, "c.z"_sd, "d"_sd, "e"_sd}) {
+ OrderedIntervalList oil{};
+ oil.name = name.toString();
+ oil.intervals.push_back(IndexBoundsBuilder::makePointInterval(BSON("" << 1)));
+ node.bounds.fields.push_back(oil);
+ }
+
+ node.computeProperties();
+
+ ASSERT_EQUALS(node.getSort().size(), 4U);
+ ASSERT(node.getSort().count(BSON("a" << 1 << "b" << 1)));
+ ASSERT(node.getSort().count(BSON("a" << 1)));
+ ASSERT(node.getSort().count(BSON("d" << 1 << "e" << 1)));
+ ASSERT(node.getSort().count(BSON("e" << 1)));
+}
+
} // namespace
diff --git a/src/mongo/db/query/stage_builder.cpp b/src/mongo/db/query/stage_builder.cpp
index 3636b2e8424..88200659335 100644
--- a/src/mongo/db/query/stage_builder.cpp
+++ b/src/mongo/db/query/stage_builder.cpp
@@ -129,12 +129,8 @@ PlanStage* buildStages(OperationContext* opCtx,
if (nullptr == childStage) {
return nullptr;
}
- return new SortKeyGeneratorStage(opCtx,
- childStage,
- ws,
- keyGenNode->sortSpec,
- keyGenNode->queryObj,
- cq.getCollator());
+ return new SortKeyGeneratorStage(
+ opCtx, childStage, ws, keyGenNode->sortSpec, cq.getCollator());
}
case STAGE_PROJECTION: {
const ProjectionNode* pn = static_cast<const ProjectionNode*>(root);
diff --git a/src/mongo/dbtests/query_stage_ensure_sorted.cpp b/src/mongo/dbtests/query_stage_ensure_sorted.cpp
index 982da6ad70a..29fe3fe5708 100644
--- a/src/mongo/dbtests/query_stage_ensure_sorted.cpp
+++ b/src/mongo/dbtests/query_stage_ensure_sorted.cpp
@@ -79,7 +79,7 @@ public:
// Initialization.
BSONObj pattern = fromjson(patternStr);
auto sortKeyGen = stdx::make_unique<SortKeyGeneratorStage>(
- opCtx.get(), queuedDataStage.release(), &ws, pattern, BSONObj(), collator);
+ opCtx.get(), queuedDataStage.release(), &ws, pattern, collator);
EnsureSortedStage ess(opCtx.get(), pattern, &ws, sortKeyGen.release());
WorkingSetID id = WorkingSet::INVALID_ID;
PlanStage::StageState state = PlanStage::NEED_TIME;
@@ -117,7 +117,7 @@ TEST_F(QueryStageEnsureSortedTest, EnsureSortedEmptyWorkingSet) {
WorkingSet ws;
auto queuedDataStage = stdx::make_unique<QueuedDataStage>(opCtx.get(), &ws);
auto sortKeyGen = stdx::make_unique<SortKeyGeneratorStage>(
- opCtx.get(), queuedDataStage.release(), &ws, BSONObj(), BSONObj(), nullptr);
+ opCtx.get(), queuedDataStage.release(), &ws, BSONObj(), nullptr);
EnsureSortedStage ess(opCtx.get(), BSONObj(), &ws, sortKeyGen.release());
WorkingSetID id = WorkingSet::INVALID_ID;
diff --git a/src/mongo/dbtests/query_stage_sort.cpp b/src/mongo/dbtests/query_stage_sort.cpp
index efe7c4c5ece..9ab3fb4fd32 100644
--- a/src/mongo/dbtests/query_stage_sort.cpp
+++ b/src/mongo/dbtests/query_stage_sort.cpp
@@ -120,7 +120,7 @@ public:
params.limit = limit();
auto keyGenStage = make_unique<SortKeyGeneratorStage>(
- &_opCtx, queuedDataStage.release(), ws.get(), params.pattern, BSONObj(), nullptr);
+ &_opCtx, queuedDataStage.release(), ws.get(), params.pattern, nullptr);
auto ss = make_unique<SortStage>(&_opCtx, params, ws.get(), keyGenStage.release());
@@ -159,7 +159,7 @@ public:
params.limit = limit();
auto keyGenStage = make_unique<SortKeyGeneratorStage>(
- &_opCtx, queuedDataStage.release(), ws.get(), params.pattern, BSONObj(), nullptr);
+ &_opCtx, queuedDataStage.release(), ws.get(), params.pattern, nullptr);
auto sortStage = make_unique<SortStage>(&_opCtx, params, ws.get(), keyGenStage.release());
@@ -560,7 +560,7 @@ public:
params.limit = 0;
auto keyGenStage = make_unique<SortKeyGeneratorStage>(
- &_opCtx, queuedDataStage.release(), ws.get(), params.pattern, BSONObj(), nullptr);
+ &_opCtx, queuedDataStage.release(), ws.get(), params.pattern, nullptr);
auto sortStage = make_unique<SortStage>(&_opCtx, params, ws.get(), keyGenStage.release());
diff --git a/src/mongo/dbtests/sort_key_generator_test.cpp b/src/mongo/dbtests/sort_key_generator_test.cpp
index 0f25f214c07..c7baa9c664a 100644
--- a/src/mongo/dbtests/sort_key_generator_test.cpp
+++ b/src/mongo/dbtests/sort_key_generator_test.cpp
@@ -50,10 +50,7 @@ namespace {
*
* Returns the BSON representation of the sort key, to be checked against the expected sort key.
*/
-BSONObj extractSortKey(const char* sortSpec,
- const char* doc,
- const char* query,
- const CollatorInterface* collator) {
+BSONObj extractSortKey(const char* sortSpec, const char* doc, const CollatorInterface* collator) {
QueryTestServiceContext serviceContext;
auto opCtx = serviceContext.makeOperationContext();
@@ -62,8 +59,8 @@ BSONObj extractSortKey(const char* sortSpec,
wsm.transitionToOwnedObj();
BSONObj sortKey;
- auto sortKeyGen = stdx::make_unique<SortKeyGenerator>(
- opCtx.get(), fromjson(sortSpec), fromjson(query), collator);
+ auto sortKeyGen =
+ stdx::make_unique<SortKeyGenerator>(opCtx.get(), fromjson(sortSpec), collator);
ASSERT_OK(sortKeyGen->getSortKey(wsm, &sortKey));
return sortKey;
@@ -93,49 +90,49 @@ BSONObj extractSortKeyCovered(const char* sortSpec,
BSONObj sortKey;
auto sortKeyGen =
- stdx::make_unique<SortKeyGenerator>(opCtx.get(), fromjson(sortSpec), BSONObj(), collator);
+ stdx::make_unique<SortKeyGenerator>(opCtx.get(), fromjson(sortSpec), collator);
ASSERT_OK(sortKeyGen->getSortKey(*wsm, &sortKey));
return sortKey;
}
TEST(SortKeyGeneratorTest, SortKeyNormal) {
- BSONObj actualOut = extractSortKey("{a: 1}", "{_id: 0, a: 5}", "", nullptr);
+ BSONObj actualOut = extractSortKey("{a: 1}", "{_id: 0, a: 5}", nullptr);
BSONObj expectedOut = BSON("" << 5);
ASSERT_BSONOBJ_EQ(actualOut, expectedOut);
}
TEST(SortKeyGeneratorTest, SortKeyNormal2) {
- BSONObj actualOut = extractSortKey("{a: 1}", "{_id: 0, z: 10, a: 6, b: 16}", "", nullptr);
+ BSONObj actualOut = extractSortKey("{a: 1}", "{_id: 0, z: 10, a: 6, b: 16}", nullptr);
BSONObj expectedOut = BSON("" << 6);
ASSERT_BSONOBJ_EQ(actualOut, expectedOut);
}
TEST(SortKeyGeneratorTest, SortKeyString) {
BSONObj actualOut =
- extractSortKey("{a: 1}", "{_id: 0, z: 'thing1', a: 'thing2', b: 16}", "", nullptr);
+ extractSortKey("{a: 1}", "{_id: 0, z: 'thing1', a: 'thing2', b: 16}", nullptr);
BSONObj expectedOut = BSON(""
<< "thing2");
ASSERT_BSONOBJ_EQ(actualOut, expectedOut);
}
TEST(SortKeyGeneratorTest, SortKeyCompound) {
- BSONObj actualOut = extractSortKey(
- "{a: 1, b: 1}", "{_id: 0, z: 'thing1', a: 99, c: {a: 4}, b: 16}", "", nullptr);
+ BSONObj actualOut =
+ extractSortKey("{a: 1, b: 1}", "{_id: 0, z: 'thing1', a: 99, c: {a: 4}, b: 16}", nullptr);
BSONObj expectedOut = BSON("" << 99 << "" << 16);
ASSERT_BSONOBJ_EQ(actualOut, expectedOut);
}
TEST(SortKeyGeneratorTest, SortKeyEmbedded) {
BSONObj actualOut = extractSortKey(
- "{'c.a': 1, b: 1}", "{_id: 0, z: 'thing1', a: 99, c: {a: 4}, b: 16}", "", nullptr);
+ "{'c.a': 1, b: 1}", "{_id: 0, z: 'thing1', a: 99, c: {a: 4}, b: 16}", nullptr);
BSONObj expectedOut = BSON("" << 4 << "" << 16);
ASSERT_BSONOBJ_EQ(actualOut, expectedOut);
}
TEST(SortKeyGeneratorTest, SortKeyArray) {
BSONObj actualOut = extractSortKey(
- "{'c': 1, b: 1}", "{_id: 0, z: 'thing1', a: 99, c: [2, 4, 1], b: 16}", "", nullptr);
+ "{'c': 1, b: 1}", "{_id: 0, z: 'thing1', a: 99, c: [2, 4, 1], b: 16}", nullptr);
BSONObj expectedOut = BSON("" << 1 << "" << 16);
ASSERT_BSONOBJ_EQ(actualOut, expectedOut);
}
@@ -194,7 +191,7 @@ TEST(SortKeyGeneratorTest, SortKeyCoveredCompound3) {
TEST(SortKeyGeneratorTest, ExtractStringSortKeyWithCollatorUsesComparisonKey) {
CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
BSONObj actualOut =
- extractSortKey("{a: 1}", "{_id: 0, z: 'thing1', a: 'thing2', b: 16}", "", &collator);
+ extractSortKey("{a: 1}", "{_id: 0, z: 'thing1', a: 'thing2', b: 16}", &collator);
BSONObj expectedOut = BSON(""
<< "2gniht");
ASSERT_BSONOBJ_EQ(actualOut, expectedOut);
@@ -202,7 +199,7 @@ TEST(SortKeyGeneratorTest, ExtractStringSortKeyWithCollatorUsesComparisonKey) {
TEST(SortKeyGeneratorTest, CollatorHasNoEffectWhenExtractingNonStringSortKey) {
CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
- BSONObj actualOut = extractSortKey("{a: 1}", "{_id: 0, z: 10, a: 6, b: 16}", "", &collator);
+ BSONObj actualOut = extractSortKey("{a: 1}", "{_id: 0, z: 10, a: 6, b: 16}", &collator);
BSONObj expectedOut = BSON("" << 6);
ASSERT_BSONOBJ_EQ(actualOut, expectedOut);
}
@@ -220,28 +217,26 @@ TEST(SortKeyGeneratorTest, CollatorHasNoAffectWhenExtractingCoveredSortKey) {
ASSERT_BSONOBJ_EQ(actualOut, expectedOut);
}
-TEST(SortKeyGeneratorTest, SortKeyGenerationForArraysUsesTheQueryPredicate) {
- BSONObj actualOut =
- extractSortKey("{a: -1}", "{_id: 0, a: [1, 2, 3, 4]}", "{a: {$lt: 3}}", nullptr);
- BSONObj expectedOut = BSON("" << 2);
+TEST(SortKeyGeneratorTest, SortKeyGenerationForArraysChoosesCorrectKey) {
+ BSONObj actualOut = extractSortKey("{a: -1}", "{_id: 0, a: [1, 2, 3, 4]}", nullptr);
+ BSONObj expectedOut = BSON("" << 4);
ASSERT_BSONOBJ_EQ(actualOut, expectedOut);
}
TEST(SortKeyGeneratorTest, EnsureSortKeyGenerationForArraysRespectsCollation) {
CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
BSONObj actualOut =
- extractSortKey("{a: 1}", "{_id: 0, a: ['aaz', 'zza', 'yya', 'zzb']}", "", &collator);
+ extractSortKey("{a: 1}", "{_id: 0, a: ['aaz', 'zza', 'yya', 'zzb']}", &collator);
BSONObj expectedOut = BSON(""
<< "ayy");
ASSERT_BSONOBJ_EQ(actualOut, expectedOut);
}
-TEST(SortKeyGeneratorTest, EnsureSortKeyGenerationForArraysWithPredicateRespectsCollation) {
- CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
- BSONObj actualOut = extractSortKey(
- "{a: 1}", "{_id: 0, a: ['aaz', 'zza', 'yya', 'zzb']}", "{a: {$gt: 'yya'}}", &collator);
- BSONObj expectedOut = BSON(""
- << "azz");
+TEST(SortKeyGeneratorTest, SortKeyGenerationForArraysRespectsCompoundOrdering) {
+ BSONObj actualOut = extractSortKey("{'a.b': 1, 'a.c': -1}",
+ "{_id: 0, a: [{b: 1, c: 0}, {b: 0, c: 3}, {b: 0, c: 1}]}",
+ nullptr);
+ BSONObj expectedOut = BSON("" << 0 << "" << 3);
ASSERT_BSONOBJ_EQ(actualOut, expectedOut);
}