diff options
author | Ian Boros <ian.boros@mongodb.com> | 2021-08-31 15:28:58 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2021-08-31 16:14:01 +0000 |
commit | a88433c2c78516e30b1356404df5cf977fe37c31 (patch) | |
tree | d8b3947824a2713fa6ddc7de7fc96f5237533b66 | |
parent | a9c17e84f57070ab1021013ba9ca02a74c7c771e (diff) | |
download | mongo-a88433c2c78516e30b1356404df5cf977fe37c31.tar.gz |
SERVER-59202 Create bounds for min/max queries on compound indexes correctly
-rw-r--r-- | src/mongo/db/query/query_planner.cpp | 40 | ||||
-rw-r--r-- | src/mongo/db/query/query_planner_options_test.cpp | 26 | ||||
-rw-r--r-- | src/mongo/db/query/query_planner_test_lib.cpp | 43 |
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; |