summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIan Boros <ian.boros@mongodb.com>2021-08-31 15:28:58 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2021-08-31 16:14:01 +0000
commita88433c2c78516e30b1356404df5cf977fe37c31 (patch)
treed8b3947824a2713fa6ddc7de7fc96f5237533b66
parenta9c17e84f57070ab1021013ba9ca02a74c7c771e (diff)
downloadmongo-a88433c2c78516e30b1356404df5cf977fe37c31.tar.gz
SERVER-59202 Create bounds for min/max queries on compound indexes correctly
-rw-r--r--src/mongo/db/query/query_planner.cpp40
-rw-r--r--src/mongo/db/query/query_planner_options_test.cpp26
-rw-r--r--src/mongo/db/query/query_planner_test_lib.cpp43
3 files changed, 85 insertions, 24 deletions
diff --git a/src/mongo/db/query/query_planner.cpp b/src/mongo/db/query/query_planner.cpp
index c3badfe6aca..712ddd70267 100644
--- a/src/mongo/db/query/query_planner.cpp
+++ b/src/mongo/db/query/query_planner.cpp
@@ -270,20 +270,16 @@ static BSONObj stripFieldNamesAndApplyCollation(const BSONObj& obj,
static BSONObj finishMinObj(const IndexEntry& indexEntry,
const BSONObj& minObj,
const BSONObj& maxObj) {
- BSONObjBuilder bob;
- bob.appendMinKey("");
- BSONObj minKey = bob.obj();
-
if (minObj.isEmpty()) {
- if (0 > minKey.woCompare(maxObj, indexEntry.keyPattern, false)) {
- BSONObjBuilder minKeyBuilder;
- minKeyBuilder.appendMinKey("");
- return minKeyBuilder.obj();
- } else {
- BSONObjBuilder maxKeyBuilder;
- maxKeyBuilder.appendMaxKey("");
- return maxKeyBuilder.obj();
+ BSONObjBuilder ret;
+ for (auto key : indexEntry.keyPattern) {
+ if (!key.isNumber() || key.numberInt() > 0) {
+ ret.appendMinKey("");
+ } else {
+ ret.appendMaxKey("");
+ }
}
+ return ret.obj();
} else {
return stripFieldNamesAndApplyCollation(minObj, indexEntry.collator);
}
@@ -299,20 +295,16 @@ static BSONObj finishMinObj(const IndexEntry& indexEntry,
static BSONObj finishMaxObj(const IndexEntry& indexEntry,
const BSONObj& minObj,
const BSONObj& maxObj) {
- BSONObjBuilder bob;
- bob.appendMaxKey("");
- BSONObj maxKey = bob.obj();
-
if (maxObj.isEmpty()) {
- if (0 < maxKey.woCompare(minObj, indexEntry.keyPattern, false)) {
- BSONObjBuilder maxKeyBuilder;
- maxKeyBuilder.appendMaxKey("");
- return maxKeyBuilder.obj();
- } else {
- BSONObjBuilder minKeyBuilder;
- minKeyBuilder.appendMinKey("");
- return minKeyBuilder.obj();
+ BSONObjBuilder ret;
+ for (auto key : indexEntry.keyPattern) {
+ if (!key.isNumber() || key.numberInt() > 0) {
+ ret.appendMaxKey("");
+ } else {
+ ret.appendMinKey("");
+ }
}
+ return ret.obj();
} else {
return stripFieldNamesAndApplyCollation(maxObj, indexEntry.collator);
}
diff --git a/src/mongo/db/query/query_planner_options_test.cpp b/src/mongo/db/query/query_planner_options_test.cpp
index 0f80df93301..1f662d32b88 100644
--- a/src/mongo/db/query/query_planner_options_test.cpp
+++ b/src/mongo/db/query/query_planner_options_test.cpp
@@ -91,6 +91,32 @@ TEST_F(QueryPlannerTest, MaxValid) {
"node: {ixscan: {filter: null, pattern: {a: 1}}}}}");
}
+TEST_F(QueryPlannerTest, MaxWithoutMinMultipleComponents) {
+ addIndex(BSON("a" << 1 << "b" << 1));
+ runQueryHintMinMax(
+ BSONObj(), BSONObj(fromjson("{a: 1, b: 1}")), BSONObj(), fromjson("{a: 1, b: 1}"));
+
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{fetch: {filter: null, "
+ "node: {ixscan: {filter: null, pattern: {a: 1, b: 1},"
+ "bounds: {'$startKey': {'': {$minKey: 1}, '': {$minKey: 1}}, '$endKey': {'': 1, '': 1}}"
+ "}}}}");
+}
+
+TEST_F(QueryPlannerTest, MinWithoutMaxMultipleComponents) {
+ addIndex(BSON("a" << 1 << "b" << 1));
+ runQueryHintMinMax(
+ BSONObj(), BSONObj(fromjson("{a: 1, b: 1}")), fromjson("{a: 1, b: 1}"), BSONObj());
+
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{fetch: {filter: null, "
+ "node: {ixscan: {filter: null, pattern: {a: 1, b: 1},"
+ "bounds: {'$startKey': {'': 1, '': 1}, '$endKey': {'': {$maxKey:1}, '': {$maxKey:1}}}"
+ "}}}}");
+}
+
TEST_F(QueryPlannerTest, MinMaxSameValue) {
addIndex(BSON("a" << 1));
runInvalidQueryHintMinMax(
diff --git a/src/mongo/db/query/query_planner_test_lib.cpp b/src/mongo/db/query/query_planner_test_lib.cpp
index a065b77e9f3..a9558920073 100644
--- a/src/mongo/db/query/query_planner_test_lib.cpp
+++ b/src/mongo/db/query/query_planner_test_lib.cpp
@@ -249,6 +249,49 @@ static Status childrenMatch(const BSONObj& testSoln,
Status QueryPlannerTestLib::boundsMatch(const BSONObj& testBounds,
const IndexBounds trueBounds,
bool relaxBoundsCheck) {
+ if (testBounds.firstElementFieldName() == "$startKey"_sd) {
+ if (!trueBounds.isSimpleRange) {
+ return {ErrorCodes::Error{5920202}, str::stream() << "Expected bounds to be simple"};
+ }
+
+ if (testBounds.nFields() != 2) {
+ return {ErrorCodes::Error{5920203},
+ str::stream() << "Expected object of form {'$startKey': ..., '$endKey': ...}"};
+ }
+
+ BSONObjIterator it(testBounds);
+
+ {
+ auto minElt = it.next();
+ if (minElt.type() != BSONType::Object) {
+ return {ErrorCodes::Error{5920205}, str::stream() << "Expected min obj"};
+ }
+
+ auto minObj = minElt.embeddedObject();
+ if (minObj.woCompare(trueBounds.startKey)) {
+ return {ErrorCodes::Error{5920201},
+ str::stream() << "'startKey' in bounds did not match. Expected "
+ << trueBounds.startKey << " got " << minObj};
+ }
+ }
+
+ {
+ auto maxElt = it.next();
+ if (maxElt.type() != BSONType::Object) {
+ return {ErrorCodes::Error{5920204}, str::stream() << "Expected max obj"};
+ }
+
+ auto maxObj = maxElt.embeddedObject();
+ if (maxObj.woCompare(trueBounds.endKey)) {
+ return {ErrorCodes::Error{5920206},
+ str::stream() << "'endKey' in bounds did not match. Expected "
+ << trueBounds.endKey << " got " << maxObj};
+ }
+ }
+
+ return Status::OK();
+ }
+
// Iterate over the fields on which we have index bounds.
BSONObjIterator fieldIt(testBounds);
size_t fieldItCount = 0;