summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
authorJason Rassi <rassi@10gen.com>2014-09-19 16:12:05 -0400
committerJason Rassi <rassi@10gen.com>2014-09-22 13:38:51 -0400
commitad1dafb013342faae7e998586a0c35b70ba75636 (patch)
treee73b3d1d5ce3941d2d2484c026a6f6dcf7990911 /src/mongo
parent5376c8ad356acc9e43e0b5e1c6cda995ea209ddf (diff)
downloadmongo-ad1dafb013342faae7e998586a0c35b70ba75636.tar.gz
SERVER-15287 Can't use index key pattern plugin fields to provide sort
Diffstat (limited to 'src/mongo')
-rw-r--r--src/mongo/db/query/SConscript10
-rw-r--r--src/mongo/db/query/lite_parsed_query.cpp31
-rw-r--r--src/mongo/db/query/lite_parsed_query.h9
-rw-r--r--src/mongo/db/query/lite_parsed_query_test.cpp70
-rw-r--r--src/mongo/db/query/planner_analysis.cpp17
-rw-r--r--src/mongo/db/query/planner_analysis.h13
-rw-r--r--src/mongo/db/query/planner_analysis_test.cpp112
-rw-r--r--src/mongo/db/query/query_planner.cpp16
-rw-r--r--src/mongo/db/query/query_planner_test.cpp44
-rw-r--r--src/mongo/db/query/query_solution.cpp26
10 files changed, 236 insertions, 112 deletions
diff --git a/src/mongo/db/query/SConscript b/src/mongo/db/query/SConscript
index bf0a38ee291..a202ab02557 100644
--- a/src/mongo/db/query/SConscript
+++ b/src/mongo/db/query/SConscript
@@ -192,6 +192,16 @@ env.CppUnitTest(
)
env.CppUnitTest(
+ target="planner_analysis_test",
+ source=[
+ "planner_analysis_test.cpp"
+ ],
+ LIBDEPS=[
+ "query_planner",
+ ],
+)
+
+env.CppUnitTest(
target="planner_ixselect_test",
source=[
"planner_ixselect_test.cpp"
diff --git a/src/mongo/db/query/lite_parsed_query.cpp b/src/mongo/db/query/lite_parsed_query.cpp
index 316a43fc8f8..a81d7a280db 100644
--- a/src/mongo/db/query/lite_parsed_query.cpp
+++ b/src/mongo/db/query/lite_parsed_query.cpp
@@ -94,11 +94,8 @@ namespace mongo {
// Sort document normalization.
BSONObj sort = el.Obj().getOwned();
- if (!sort.isEmpty()) {
- if (!isValidSortOrder(sort)) {
- return Status(ErrorCodes::BadValue, "bad sort specification");
- }
- sort = normalizeSortOrder(sort);
+ if (!isValidSortOrder(sort)) {
+ return Status(ErrorCodes::BadValue, "bad sort specification");
}
pq->_sort = sort;
@@ -410,23 +407,6 @@ namespace mongo {
return false;
}
- // static
- BSONObj LiteParsedQuery::normalizeSortOrder(const BSONObj& sortObj) {
- BSONObjBuilder b;
- BSONObjIterator i(sortObj);
- while (i.more()) {
- BSONElement e = i.next();
- if (isTextScoreMeta(e)) {
- b.append(e);
- continue;
- }
- long long n = e.safeNumberLong();
- int sortOrder = n >= 0 ? 1 : -1;
- b.append(e.fieldName(), sortOrder);
- }
- return b.obj();
- }
-
LiteParsedQuery::LiteParsedQuery() :
_skip(0),
_limit(0),
@@ -710,11 +690,8 @@ namespace mongo {
_options.hasReadPref = queryObj.hasField("$readPreference");
- if (!_sort.isEmpty()) {
- if (!isValidSortOrder(_sort)) {
- return Status(ErrorCodes::BadValue, "bad sort specification");
- }
- _sort = normalizeSortOrder(_sort);
+ if (!isValidSortOrder(_sort)) {
+ return Status(ErrorCodes::BadValue, "bad sort specification");
}
return validate();
diff --git a/src/mongo/db/query/lite_parsed_query.h b/src/mongo/db/query/lite_parsed_query.h
index 86b0157243b..296a6555983 100644
--- a/src/mongo/db/query/lite_parsed_query.h
+++ b/src/mongo/db/query/lite_parsed_query.h
@@ -159,15 +159,6 @@ namespace mongo {
*/
static bool isQueryIsolated(const BSONObj& query);
- /**
- * Helper function to create a normalized sort object.
- * Each element of the object returned satisfies one of:
- * 1. a number with value 1
- * 2. a number with value -1
- * 3. isTextScoreMeta
- */
- static BSONObj normalizeSortOrder(const BSONObj& sortObj);
-
// Names of the maxTimeMS command and query option.
static const std::string cmdOptionMaxTimeMS;
static const std::string queryOptionMaxTimeMS;
diff --git a/src/mongo/db/query/lite_parsed_query_test.cpp b/src/mongo/db/query/lite_parsed_query_test.cpp
index b0fd0c5591f..c6120ee247f 100644
--- a/src/mongo/db/query/lite_parsed_query_test.cpp
+++ b/src/mongo/db/query/lite_parsed_query_test.cpp
@@ -216,59 +216,43 @@ namespace {
ASSERT_FALSE(isFirstElementTextScoreMeta("{a: {$meta: \"textScore\", b: 1}}"));
}
- void testSortOrder(bool expectedValid, const char* expectedStr, const char* sortStr) {
- BSONObj sortOrder = fromjson(sortStr);
- bool valid = LiteParsedQuery::isValidSortOrder(sortOrder);
- if (expectedValid != valid) {
- mongoutils::str::stream ss;
- ss << sortStr << ": unexpected validation result. Expected: " << expectedValid;
- FAIL(ss);
- }
- BSONObj normalizedSortOrder = LiteParsedQuery::normalizeSortOrder(sortOrder);
- if (fromjson(expectedStr) != normalizedSortOrder) {
- mongoutils::str::stream ss;
- ss << sortStr << ": unexpected normalization result. Expected: " << expectedStr
- << ". Actual: " << normalizedSortOrder.toString();
- FAIL(ss);
- }
- }
-
//
- // Sort order validation and normalization
+ // Sort order validation
// In a valid sort order, each element satisfies one of:
// 1. a number with value 1
// 2. a number with value -1
// 3. isTextScoreMeta
//
- TEST(LiteParsedQueryTest, NormalizeAndValidateSortOrder) {
+ TEST(LiteParsedQueryTest, ValidateSortOrder) {
// Valid sorts
- testSortOrder(true, "{}", "{}");
- testSortOrder(true, "{a: 1}", "{a: 1}");
- testSortOrder(true, "{a: -1}", "{a: -1}");
- testSortOrder(true, "{a: {$meta: \"textScore\"}}", "{a: {$meta: \"textScore\"}}");
+ ASSERT(LiteParsedQuery::isValidSortOrder(fromjson("{}")));
+ ASSERT(LiteParsedQuery::isValidSortOrder(fromjson("{a: 1}")));
+ ASSERT(LiteParsedQuery::isValidSortOrder(fromjson("{a: -1}")));
+ ASSERT(LiteParsedQuery::isValidSortOrder(fromjson("{a: {$meta: \"textScore\"}}")));
// Invalid sorts
- testSortOrder(false, "{a: 1}", "{a: 100}");
- testSortOrder(false, "{a: 1}", "{a: 0}");
- testSortOrder(false, "{a: -1}", "{a: -100}");
- testSortOrder(false, "{a: 1}", "{a: Infinity}");
- testSortOrder(false, "{a: -1}", "{a: -Infinity}");
- testSortOrder(false, "{a: 1}", "{a: true}");
- testSortOrder(false, "{a: 1}", "{a: false}");
- testSortOrder(false, "{a: 1}", "{a: null}");
- testSortOrder(false, "{a: 1}", "{a: {}}");
- testSortOrder(false, "{a: 1}", "{a: {b: 1}}");
- testSortOrder(false, "{a: 1}", "{a: []}");
- testSortOrder(false, "{a: 1}", "{a: [1, 2, 3]}");
- testSortOrder(false, "{a: 1}", "{a: \"\"}");
- testSortOrder(false, "{a: 1}", "{a: \"bb\"}");
- testSortOrder(false, "{a: 1}", "{a: {$meta: 1}}");
- testSortOrder(false, "{a: 1}", "{a: {$meta: \"image\"}}");
- testSortOrder(false, "{a: 1}", "{a: {$world: \"textScore\"}}");
- testSortOrder(false, "{a: 1}", "{a: {$meta: \"textScore\", b: 1}}");
- testSortOrder(false, "{'': 1}", "{'': 1}");
- testSortOrder(false, "{'': -1}", "{'': -1}");
+ ASSERT_FALSE(LiteParsedQuery::isValidSortOrder(fromjson("{a: 100}")));
+ ASSERT_FALSE(LiteParsedQuery::isValidSortOrder(fromjson("{a: 0}")));
+ ASSERT_FALSE(LiteParsedQuery::isValidSortOrder(fromjson("{a: -100}")));
+ ASSERT_FALSE(LiteParsedQuery::isValidSortOrder(fromjson("{a: Infinity}")));
+ ASSERT_FALSE(LiteParsedQuery::isValidSortOrder(fromjson("{a: -Infinity}")));
+ ASSERT_FALSE(LiteParsedQuery::isValidSortOrder(fromjson("{a: true}")));
+ ASSERT_FALSE(LiteParsedQuery::isValidSortOrder(fromjson("{a: false}")));
+ ASSERT_FALSE(LiteParsedQuery::isValidSortOrder(fromjson("{a: null}")));
+ ASSERT_FALSE(LiteParsedQuery::isValidSortOrder(fromjson("{a: {}}")));
+ ASSERT_FALSE(LiteParsedQuery::isValidSortOrder(fromjson("{a: {b: 1}}")));
+ ASSERT_FALSE(LiteParsedQuery::isValidSortOrder(fromjson("{a: []}")));
+ ASSERT_FALSE(LiteParsedQuery::isValidSortOrder(fromjson("{a: [1, 2, 3]}")));
+ ASSERT_FALSE(LiteParsedQuery::isValidSortOrder(fromjson("{a: \"\"}")));
+ ASSERT_FALSE(LiteParsedQuery::isValidSortOrder(fromjson("{a: \"bb\"}")));
+ ASSERT_FALSE(LiteParsedQuery::isValidSortOrder(fromjson("{a: {$meta: 1}}")));
+ ASSERT_FALSE(LiteParsedQuery::isValidSortOrder(fromjson("{a: {$meta: \"image\"}}")));
+ ASSERT_FALSE(LiteParsedQuery::isValidSortOrder(fromjson("{a: {$world: \"textScore\"}}")));
+ ASSERT_FALSE(LiteParsedQuery::isValidSortOrder(fromjson("{a: {$meta: \"textScore\","
+ " b: 1}}")));
+ ASSERT_FALSE(LiteParsedQuery::isValidSortOrder(fromjson("{'': 1}")));
+ ASSERT_FALSE(LiteParsedQuery::isValidSortOrder(fromjson("{'': -1}")));
}
//
diff --git a/src/mongo/db/query/planner_analysis.cpp b/src/mongo/db/query/planner_analysis.cpp
index 048615e9e5e..52b081f9ad5 100644
--- a/src/mongo/db/query/planner_analysis.cpp
+++ b/src/mongo/db/query/planner_analysis.cpp
@@ -248,6 +248,23 @@ namespace mongo {
} // namespace
+ // static
+ BSONObj QueryPlannerAnalysis::getSortPattern(const BSONObj& indexKeyPattern) {
+ BSONObjBuilder sortBob;
+ BSONObjIterator kpIt(indexKeyPattern);
+ while (kpIt.more()) {
+ BSONElement elt = kpIt.next();
+ if (elt.type() == mongo::String) {
+ break;
+ }
+ long long val = elt.safeNumberLong();
+ int sortOrder = val >= 0 ? 1 : -1;
+ sortBob.append(elt.fieldName(), sortOrder);
+ }
+ return sortBob.obj();
+ }
+
+ // static
bool QueryPlannerAnalysis::explodeForSort(const CanonicalQuery& query,
const QueryPlannerParams& params,
QuerySolutionNode** solnRoot) {
diff --git a/src/mongo/db/query/planner_analysis.h b/src/mongo/db/query/planner_analysis.h
index 8547a0f15d8..b7591bb31b5 100644
--- a/src/mongo/db/query/planner_analysis.h
+++ b/src/mongo/db/query/planner_analysis.h
@@ -39,6 +39,19 @@ namespace mongo {
class QueryPlannerAnalysis {
public:
/**
+ * Takes an index key pattern and returns an object describing the "maximal sort" that this
+ * index can provide. Returned object is in normalized sort form (all elements have value 1
+ * or -1).
+ *
+ * Examples:
+ * - {a: 1, b: -1} => {a: 1, b: -1}
+ * - {a: true} => {a: 1}
+ * - {a: "hashed"} => {}
+ * - {a: 1, b: "text", c: 1} => {a: 1}
+ */
+ static BSONObj getSortPattern(const BSONObj& indexKeyPattern);
+
+ /**
* In brief: performs sort and covering analysis.
*
* The solution rooted at 'solnRoot' provides data for the query, whether through some
diff --git a/src/mongo/db/query/planner_analysis_test.cpp b/src/mongo/db/query/planner_analysis_test.cpp
new file mode 100644
index 00000000000..e638228db50
--- /dev/null
+++ b/src/mongo/db/query/planner_analysis_test.cpp
@@ -0,0 +1,112 @@
+/**
+ * Copyright (C) 2014 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/db/query/planner_analysis.h"
+
+#include "mongo/db/json.h"
+#include "mongo/unittest/unittest.h"
+
+using namespace mongo;
+
+namespace {
+
+ TEST(QueryPlannerAnalysis, GetSortPatternBasic) {
+ ASSERT_EQUALS(fromjson("{a: 1}"),
+ QueryPlannerAnalysis::getSortPattern(fromjson("{a: 1}")));
+ ASSERT_EQUALS(fromjson("{a: -1}"),
+ QueryPlannerAnalysis::getSortPattern(fromjson("{a: -1}")));
+ ASSERT_EQUALS(fromjson("{a: 1, b: 1}"),
+ QueryPlannerAnalysis::getSortPattern(fromjson("{a: 1, b: 1}")));
+ ASSERT_EQUALS(fromjson("{a: 1, b: -1}"),
+ QueryPlannerAnalysis::getSortPattern(fromjson("{a: 1, b: -1}")));
+ ASSERT_EQUALS(fromjson("{a: -1, b: 1}"),
+ QueryPlannerAnalysis::getSortPattern(fromjson("{a: -1, b: 1}")));
+ ASSERT_EQUALS(fromjson("{a: -1, b: -1}"),
+ QueryPlannerAnalysis::getSortPattern(fromjson("{a: -1, b: -1}")));
+ }
+
+ TEST(QueryPlannerAnalysis, GetSortPatternOtherElements) {
+ ASSERT_EQUALS(fromjson("{a: 1}"),
+ QueryPlannerAnalysis::getSortPattern(fromjson("{a: 0}")));
+ ASSERT_EQUALS(fromjson("{a: 1}"),
+ QueryPlannerAnalysis::getSortPattern(fromjson("{a: 100}")));
+ ASSERT_EQUALS(fromjson("{a: 1}"),
+ QueryPlannerAnalysis::getSortPattern(fromjson("{a: Infinity}")));
+ ASSERT_EQUALS(fromjson("{a: 1}"),
+ QueryPlannerAnalysis::getSortPattern(fromjson("{a: true}")));
+ ASSERT_EQUALS(fromjson("{a: 1}"),
+ QueryPlannerAnalysis::getSortPattern(fromjson("{a: false}")));
+ ASSERT_EQUALS(fromjson("{a: 1}"),
+ QueryPlannerAnalysis::getSortPattern(fromjson("{a: []}")));
+ ASSERT_EQUALS(fromjson("{a: 1}"),
+ QueryPlannerAnalysis::getSortPattern(fromjson("{a: {}}")));
+
+ ASSERT_EQUALS(fromjson("{a: -1}"),
+ QueryPlannerAnalysis::getSortPattern(fromjson("{a: -100}")));
+ ASSERT_EQUALS(fromjson("{a: -1}"),
+ QueryPlannerAnalysis::getSortPattern(fromjson("{a: -Infinity}")));
+
+ ASSERT_EQUALS(fromjson("{}"),
+ QueryPlannerAnalysis::getSortPattern(fromjson("{}")));
+ }
+
+ TEST(QueryPlannerAnalysis, GetSortPatternSpecialIndexTypes) {
+ ASSERT_EQUALS(fromjson("{}"),
+ QueryPlannerAnalysis::getSortPattern(fromjson("{a: 'hashed'}")));
+ ASSERT_EQUALS(fromjson("{}"),
+ QueryPlannerAnalysis::getSortPattern(fromjson("{a: 'text'}")));
+ ASSERT_EQUALS(fromjson("{}"),
+ QueryPlannerAnalysis::getSortPattern(fromjson("{a: '2dsphere'}")));
+ ASSERT_EQUALS(fromjson("{}"),
+ QueryPlannerAnalysis::getSortPattern(fromjson("{a: ''}")));
+ ASSERT_EQUALS(fromjson("{}"),
+ QueryPlannerAnalysis::getSortPattern(fromjson("{a: 'foo'}")));
+
+ ASSERT_EQUALS(fromjson("{a: -1}"),
+ QueryPlannerAnalysis::getSortPattern(fromjson("{a: -1, b: 'text'}")));
+ ASSERT_EQUALS(fromjson("{a: -1}"),
+ QueryPlannerAnalysis::getSortPattern(fromjson("{a: -1, b: '2dsphere'}")));
+ ASSERT_EQUALS(fromjson("{a: 1}"),
+ QueryPlannerAnalysis::getSortPattern(fromjson("{a: 1, b: 'text'}")));
+ ASSERT_EQUALS(fromjson("{a: 1}"),
+ QueryPlannerAnalysis::getSortPattern(fromjson("{a: 1, b: '2dsphere'}")));
+
+ ASSERT_EQUALS(fromjson("{a: 1}"),
+ QueryPlannerAnalysis::getSortPattern(fromjson("{a: 1, b: 'text', c: 1}")));
+ ASSERT_EQUALS(fromjson("{a: 1}"),
+ QueryPlannerAnalysis::getSortPattern(fromjson("{a: 1, b: '2dsphere',"
+ " c: 1}")));
+
+ ASSERT_EQUALS(fromjson("{a: 1, b: 1}"),
+ QueryPlannerAnalysis::getSortPattern(fromjson("{a: 1, b: 1, c: 'text'}")));
+ ASSERT_EQUALS(fromjson("{a: 1, b: 1}"),
+ QueryPlannerAnalysis::getSortPattern(fromjson("{a: 1, b: 1, c: 'text',"
+ " d: 1}")));
+ }
+
+} // namespace
diff --git a/src/mongo/db/query/query_planner.cpp b/src/mongo/db/query/query_planner.cpp
index 4256c5dcb07..e6b06e32460 100644
--- a/src/mongo/db/query/query_planner.cpp
+++ b/src/mongo/db/query/query_planner.cpp
@@ -232,19 +232,7 @@ namespace mongo {
}
bool providesSort(const CanonicalQuery& query, const BSONObj& kp) {
- BSONObjIterator sortIt(query.getParsed().getSort());
- BSONObjIterator kpIt(kp);
-
- while (sortIt.more() && kpIt.more()) {
- // We want the field name to be the same as well (so we pass true).
- // TODO: see if we can pull a reverse sort out...
- if (0 != sortIt.next().woCompare(kpIt.next(), true)) {
- return false;
- }
- }
-
- // every elt in sort matched kp
- return !sortIt.more();
+ return query.getParsed().getSort().isPrefixOf(kp);
}
// static
@@ -877,7 +865,7 @@ namespace mongo {
// 2dsphereIndexVersion=2) should be able to provide a sort for
// find({b: GEO}).sort({a:1}). SERVER-10801.
- const BSONObj kp = LiteParsedQuery::normalizeSortOrder(index.keyPattern);
+ const BSONObj kp = QueryPlannerAnalysis::getSortPattern(index.keyPattern);
if (providesSort(query, kp)) {
QLOG() << "Planner: outputting soln that uses index to provide sort."
<< endl;
diff --git a/src/mongo/db/query/query_planner_test.cpp b/src/mongo/db/query/query_planner_test.cpp
index c14ec5bac40..1c6a3d934f4 100644
--- a/src/mongo/db/query/query_planner_test.cpp
+++ b/src/mongo/db/query/query_planner_test.cpp
@@ -1345,6 +1345,19 @@ namespace {
"node: {cscan: {dir: 1, filter: {}}}}}");
}
+ TEST_F(QueryPlannerTest, CantUseHashedIndexToProvideSortWithIndexablePred) {
+ addIndex(BSON("x" << "hashed"));
+ runQuerySortProj(BSON("x" << BSON("$in" << BSON_ARRAY(0 << 1))), BSON("x" << 1), BSONObj());
+
+ ASSERT_EQUALS(getNumSolutions(), 2U);
+ assertSolutionExists("{sort: {pattern: {x: 1}, limit: 0, node: "
+ "{fetch: {node: "
+ "{ixscan: {pattern: {x: 'hashed'}}}}}}}");
+ assertSolutionExists("{sort: {pattern: {x: 1}, limit: 0, node: "
+ "{cscan: {dir: 1, filter: {x: {$in: [0, 1]}}}}}}");
+ }
+
+
TEST_F(QueryPlannerTest, CantUseTextIndexToProvideSort) {
addIndex(BSON("x" << 1 << "_fts" << "text" << "_ftsx" << 1));
runQuerySortProj(BSONObj(), BSON("x" << 1), BSONObj());
@@ -1363,7 +1376,22 @@ namespace {
"node: {cscan: {dir: 1, filter: {}}}}}");
}
- TEST_F(QueryPlannerTest, CantUseCompoundGeoIndexToProvideSort) {
+ TEST_F(QueryPlannerTest, CantUseNonCompoundGeoIndexToProvideSortWithIndexablePred) {
+ addIndex(BSON("x" << "2dsphere"));
+ runQuerySortProj(fromjson("{x: {$geoIntersects: {$geometry: {type: 'Point',"
+ " coordinates: [0, 0]}}}}"),
+ BSON("x" << 1),
+ BSONObj());
+
+ ASSERT_EQUALS(getNumSolutions(), 2U);
+ assertSolutionExists("{sort: {pattern: {x: 1}, limit: 0, node: "
+ "{fetch: {node: "
+ "{ixscan: {pattern: {x: '2dsphere'}}}}}}}");
+ assertSolutionExists("{sort: {pattern: {x: 1}, limit: 0, node: "
+ "{cscan: {dir: 1}}}}");
+ }
+
+ TEST_F(QueryPlannerTest, CantUseCompoundGeoIndexToProvideSortIfNoGeoPred) {
addIndex(BSON("x" << 1 << "y" << "2dsphere"));
runQuerySortProj(BSONObj(), BSON("x" << 1), BSONObj());
@@ -1372,6 +1400,20 @@ namespace {
"node: {cscan: {dir: 1, filter: {}}}}}");
}
+ TEST_F(QueryPlannerTest, CanUseCompoundGeoIndexToProvideSortWithGeoPred) {
+ addIndex(BSON("x" << 1 << "y" << "2dsphere"));
+ runQuerySortProj(fromjson("{x: 1, y: {$geoIntersects: {$geometry: {type: 'Point',"
+ " coordinates: [0, 0]}}}}"),
+ BSON("x" << 1),
+ BSONObj());
+
+ ASSERT_EQUALS(getNumSolutions(), 2U);
+ assertSolutionExists("{fetch: {node: "
+ "{ixscan: {pattern: {x: 1, y: '2dsphere'}}}}}");
+ assertSolutionExists("{sort: {pattern: {x: 1}, limit: 0, node: "
+ "{cscan: {dir: 1}}}}");
+ }
+
TEST_F(QueryPlannerTest, BasicSortWithIndexablePred) {
addIndex(BSON("a" << 1));
addIndex(BSON("b" << 1));
diff --git a/src/mongo/db/query/query_solution.cpp b/src/mongo/db/query/query_solution.cpp
index 3e042cb2b7c..34a98a46d9c 100644
--- a/src/mongo/db/query/query_solution.cpp
+++ b/src/mongo/db/query/query_solution.cpp
@@ -26,10 +26,12 @@
* it in the license file.
*/
-#include "mongo/db/index_names.h"
#include "mongo/db/query/query_solution.h"
-#include "mongo/db/query/lite_parsed_query.h"
+
+#include "mongo/db/index_names.h"
#include "mongo/db/matcher/expression_geo.h"
+#include "mongo/db/query/planner_analysis.h"
+#include "mongo/db/query/query_planner_common.h"
namespace mongo {
@@ -454,23 +456,11 @@ namespace mongo {
void IndexScanNode::computeProperties() {
_sorts.clear();
- BSONObj sortPattern;
- {
- BSONObjBuilder sortBob;
- BSONObj normalizedIndexKeyPattern(LiteParsedQuery::normalizeSortOrder(indexKeyPattern));
- BSONObjIterator it(normalizedIndexKeyPattern);
- while (it.more()) {
- BSONElement elt = it.next();
- // Zero is returned if elt is not a number. This happens when elt is hashed or
- // 2dsphere, our two projection indices. We want to drop those from the sort
- // pattern.
- int val = elt.numberInt() * direction;
- if (0 != val) {
- sortBob.append(elt.fieldName(), val);
- }
- }
- sortPattern = sortBob.obj();
+ BSONObj sortPattern = QueryPlannerAnalysis::getSortPattern(indexKeyPattern);
+ if (direction == -1) {
+ sortPattern = QueryPlannerCommon::reverseSortObj(sortPattern);
}
+
_sorts.insert(sortPattern);
const int nFields = sortPattern.nFields();