diff options
author | Andrii Dobroshynski <andrii.dobroshynski@mongodb.com> | 2022-02-05 18:02:46 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-02-05 18:49:41 +0000 |
commit | a1e5f50b2d3f162b4ade24ef4cf8035855bf5658 (patch) | |
tree | 7cfd61404afc63ce55628c136eb48701c19ebacb | |
parent | 8819693dce1282c2e93aa49c488473cb12429cec (diff) | |
download | mongo-a1e5f50b2d3f162b4ade24ef4cf8035855bf5658.tar.gz |
SERVER-60298 Hex encode the index bounds if associated with collation
-rw-r--r-- | src/mongo/db/exec/distinct_scan.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/exec/index_scan.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/query/expression_index.cpp | 15 | ||||
-rw-r--r-- | src/mongo/db/query/get_executor.cpp | 3 | ||||
-rw-r--r-- | src/mongo/db/query/index_bounds.cpp | 13 | ||||
-rw-r--r-- | src/mongo/db/query/index_bounds.h | 17 | ||||
-rw-r--r-- | src/mongo/db/query/index_bounds_builder.cpp | 13 | ||||
-rw-r--r-- | src/mongo/db/query/index_bounds_builder.h | 9 | ||||
-rw-r--r-- | src/mongo/db/query/index_bounds_builder_test.cpp | 40 | ||||
-rw-r--r-- | src/mongo/db/query/index_bounds_test.cpp | 36 | ||||
-rw-r--r-- | src/mongo/db/query/interval.h | 22 | ||||
-rw-r--r-- | src/mongo/db/query/plan_explainer_sbe.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/query/planner_access.cpp | 7 | ||||
-rw-r--r-- | src/mongo/db/query/planner_wildcard_helpers.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/query/query_planner_common.cpp | 6 | ||||
-rw-r--r-- | src/mongo/db/query/query_planner_test_lib.cpp | 4 | ||||
-rw-r--r-- | src/mongo/db/query/query_solution.cpp | 6 | ||||
-rw-r--r-- | src/mongo/s/chunk_manager.cpp | 6 | ||||
-rw-r--r-- | src/mongo/s/chunk_manager_index_bounds_test.cpp | 8 |
19 files changed, 145 insertions, 68 deletions
diff --git a/src/mongo/db/exec/distinct_scan.cpp b/src/mongo/db/exec/distinct_scan.cpp index 395130f09cb..82c0805eb43 100644 --- a/src/mongo/db/exec/distinct_scan.cpp +++ b/src/mongo/db/exec/distinct_scan.cpp @@ -167,7 +167,7 @@ unique_ptr<PlanStageStats> DistinctScan::getStats() { // the constructor in order to avoid the expensive serialization operation unless the distinct // command is being explained. if (_specificStats.indexBounds.isEmpty()) { - _specificStats.indexBounds = _bounds.toBSON(); + _specificStats.indexBounds = _bounds.toBSON(!_specificStats.collation.isEmpty()); } unique_ptr<PlanStageStats> ret = diff --git a/src/mongo/db/exec/index_scan.cpp b/src/mongo/db/exec/index_scan.cpp index f746f3f8bad..83cca369086 100644 --- a/src/mongo/db/exec/index_scan.cpp +++ b/src/mongo/db/exec/index_scan.cpp @@ -289,7 +289,7 @@ std::unique_ptr<PlanStageStats> IndexScan::getStats() { if (_specificStats.indexType.empty()) { _specificStats.indexType = "BtreeCursor"; // TODO amName; - _specificStats.indexBounds = _bounds.toBSON(); + _specificStats.indexBounds = _bounds.toBSON(!_specificStats.collation.isEmpty()); _specificStats.direction = _direction; } diff --git a/src/mongo/db/query/expression_index.cpp b/src/mongo/db/query/expression_index.cpp index cf9202ea653..7820ffbb1ad 100644 --- a/src/mongo/db/query/expression_index.cpp +++ b/src/mongo/db/query/expression_index.cpp @@ -27,6 +27,8 @@ * it in the license file. */ +#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kQuery + #include "mongo/db/query/expression_index.h" #include <iostream> @@ -37,6 +39,7 @@ #include "mongo/db/hasher.h" #include "mongo/db/index/expression_params.h" #include "mongo/db/query/expression_index_knobs_gen.h" +#include "mongo/logv2/log.h" #include "third_party/s2/s2cellid.h" #include "third_party/s2/s2region.h" #include "third_party/s2/s2regioncoverer.h" @@ -175,8 +178,10 @@ void ExpressionMapping::S2CellIdsToIntervals(const std::vector<S2CellId>& interv // Make sure that our intervals don't overlap each other and are ordered correctly. // This perhaps should only be done in debug mode. if (!oilOut->isValidFor(1)) { - std::cout << "check your assumptions! OIL = " << oilOut->toString() << std::endl; - verify(0); + LOGV2(6029801, + "invalid OrderedIntervalList", + "orderedIntervalList"_attr = oilOut->toString(false)); + MONGO_UNREACHABLE; } } @@ -222,8 +227,10 @@ void ExpressionMapping::S2CellIdsToIntervalsWithParents(const std::vector<S2Cell // Make sure that our intervals don't overlap each other and are ordered correctly. // This perhaps should only be done in debug mode. if (!oilOut->isValidFor(1)) { - std::cout << "check your assumptions! OIL = " << oilOut->toString() << std::endl; - verify(0); + LOGV2(6029802, + "invalid OrderedIntervalList", + "orderedIntervalList"_attr = oilOut->toString(false)); + MONGO_UNREACHABLE; } } diff --git a/src/mongo/db/query/get_executor.cpp b/src/mongo/db/query/get_executor.cpp index a08402df756..fcb7c87a427 100644 --- a/src/mongo/db/query/get_executor.cpp +++ b/src/mongo/db/query/get_executor.cpp @@ -2413,7 +2413,8 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> getExecutorForS auto dn = std::make_unique<DistinctNode>(plannerParams.indices[distinctNodeIndex]); dn->direction = 1; - IndexBoundsBuilder::allValuesBounds(dn->index.keyPattern, &dn->bounds); + IndexBoundsBuilder::allValuesBounds( + dn->index.keyPattern, &dn->bounds, dn->index.collator != nullptr); dn->queryCollator = collator; dn->fieldNo = 0; diff --git a/src/mongo/db/query/index_bounds.cpp b/src/mongo/db/query/index_bounds.cpp index 8703368fcbd..d5f3e90733f 100644 --- a/src/mongo/db/query/index_bounds.cpp +++ b/src/mongo/db/query/index_bounds.cpp @@ -126,11 +126,11 @@ bool IndexBounds::operator!=(const IndexBounds& other) const { return !(*this == other); } -string OrderedIntervalList::toString() const { +string OrderedIntervalList::toString(bool hasNonSimpleCollation) const { str::stream ss; ss << "['" << name << "']: "; for (size_t j = 0; j < intervals.size(); ++j) { - ss << intervals[j].toString(); + ss << intervals[j].toString(hasNonSimpleCollation); if (j < intervals.size() - 1) { ss << ", "; } @@ -305,7 +305,7 @@ void OrderedIntervalList::complement() { intervals.insert(intervals.end(), newIntervals.begin(), newIntervals.end()); } -string IndexBounds::toString() const { +string IndexBounds::toString(bool hasNonSimpleCollation) const { str::stream ss; if (isSimpleRange) { if (IndexBounds::isStartIncludedInBound(boundInclusion)) { @@ -330,13 +330,13 @@ string IndexBounds::toString() const { if (i > 0) { ss << ", "; } - ss << "field #" << i << fields[i].toString(); + ss << "field #" << i << fields[i].toString(hasNonSimpleCollation); } return ss; } -BSONObj IndexBounds::toBSON() const { +BSONObj IndexBounds::toBSON(bool hasNonSimpleCollation) const { BSONObjBuilder bob; vector<OrderedIntervalList>::const_iterator itField; for (itField = fields.begin(); itField != fields.end(); ++itField) { @@ -345,8 +345,7 @@ BSONObj IndexBounds::toBSON() const { vector<Interval>::const_iterator itInterval; for (itInterval = itField->intervals.begin(); itInterval != itField->intervals.end(); ++itInterval) { - std::string intervalStr = itInterval->toString(); - + std::string intervalStr = itInterval->toString(hasNonSimpleCollation); // Insulate against hitting BSON size limit. if ((bob.len() + (int)intervalStr.size()) > BSONObjMaxUserSize) { fieldBuilder.append("warning: bounds truncated due to BSON size limit"); diff --git a/src/mongo/db/query/index_bounds.h b/src/mongo/db/query/index_bounds.h index 5bc2967bac4..f9838065106 100644 --- a/src/mongo/db/query/index_bounds.h +++ b/src/mongo/db/query/index_bounds.h @@ -58,7 +58,11 @@ struct OrderedIntervalList { std::string name; bool isValidFor(int expectedOrientation) const; - std::string toString() const; + /** + * Generates a debug string for an interval. If 'hasNonSimpleCollation' is true, then string + * bounds are hex-encoded. + */ + std::string toString(bool hasNonSimpleCollation) const; /** * Complements the OIL. Used by the index bounds builder in order @@ -117,7 +121,11 @@ struct IndexBounds { std::string getFieldName(size_t i) const; size_t getNumIntervals(size_t i) const; Interval getInterval(size_t i, size_t j) const; - std::string toString() const; + /** + * Generates a debug string displaying the index bounds. If 'hasNonSimpleCollation' is true, + * then string bounds are hex-encoded. + */ + std::string toString(bool hasNonSimpleCollation) const; bool operator==(const IndexBounds& other) const; bool operator!=(const IndexBounds& other) const; @@ -154,8 +162,11 @@ struct IndexBounds { * * Ex. * {a: ["[1, 1]", "(3, 10)"], b: ["[Infinity, 10)"] } + * + * If the index bounds are associated with a collation ('hasNonSimpleCollation'), then we will + * hex-encode the collation keys. */ - BSONObj toBSON() const; + BSONObj toBSON(bool hasNonSimpleCollation) const; /** * Return a copy of the index bounds, but with each of the OILs going in the ascending diff --git a/src/mongo/db/query/index_bounds_builder.cpp b/src/mongo/db/query/index_bounds_builder.cpp index ec16c4bb6b4..92d181b53d7 100644 --- a/src/mongo/db/query/index_bounds_builder.cpp +++ b/src/mongo/db/query/index_bounds_builder.cpp @@ -1325,7 +1325,9 @@ void IndexBoundsBuilder::translateEquality(const BSONElement& data, } // static -void IndexBoundsBuilder::allValuesBounds(const BSONObj& keyPattern, IndexBounds* bounds) { +void IndexBoundsBuilder::allValuesBounds(const BSONObj& keyPattern, + IndexBounds* bounds, + bool hasNonSimpleCollation) { bounds->fields.resize(keyPattern.nFields()); BSONObjIterator it(keyPattern); @@ -1335,11 +1337,14 @@ void IndexBoundsBuilder::allValuesBounds(const BSONObj& keyPattern, IndexBounds* ++field; } - alignBounds(bounds, keyPattern); + alignBounds(bounds, keyPattern, hasNonSimpleCollation); } // static -void IndexBoundsBuilder::alignBounds(IndexBounds* bounds, const BSONObj& kp, int scanDir) { +void IndexBoundsBuilder::alignBounds(IndexBounds* bounds, + const BSONObj& kp, + bool hasNonSimpleCollation, + int scanDir) { BSONObjIterator it(kp); size_t oilIdx = 0; while (it.more()) { @@ -1357,7 +1362,7 @@ void IndexBoundsBuilder::alignBounds(IndexBounds* bounds, const BSONObj& kp, int if (!bounds->isValidFor(kp, scanDir)) { LOGV2(20933, "Invalid bounds", - "bounds"_attr = redact(bounds->toString()), + "bounds"_attr = redact(bounds->toString(hasNonSimpleCollation)), "keyPattern"_attr = redact(kp), "scanDirection"_attr = scanDir); MONGO_UNREACHABLE; diff --git a/src/mongo/db/query/index_bounds_builder.h b/src/mongo/db/query/index_bounds_builder.h index c140a151b46..4cdba8c0c0e 100644 --- a/src/mongo/db/query/index_bounds_builder.h +++ b/src/mongo/db/query/index_bounds_builder.h @@ -192,14 +192,19 @@ public: * Fills out 'bounds' with the bounds for an index scan over all values of the * index described by 'keyPattern' in the default forward direction. */ - static void allValuesBounds(const BSONObj& keyPattern, IndexBounds* bounds); + static void allValuesBounds(const BSONObj& keyPattern, + IndexBounds* bounds, + bool hasNonSimpleCollation); /** * Assumes each OIL in 'bounds' is increasing. * * Aligns OILs (and bounds) according to the 'kp' direction * the scanDir. */ - static void alignBounds(IndexBounds* bounds, const BSONObj& kp, int scanDir = 1); + static void alignBounds(IndexBounds* bounds, + const BSONObj& kp, + bool hasNonSimpleCollation, + int scanDir = 1); /** * Returns 'true' if the bounds 'bounds' can be represented as one interval between diff --git a/src/mongo/db/query/index_bounds_builder_test.cpp b/src/mongo/db/query/index_bounds_builder_test.cpp index 8413c471214..7b8762cf9b0 100644 --- a/src/mongo/db/query/index_bounds_builder_test.cpp +++ b/src/mongo/db/query/index_bounds_builder_test.cpp @@ -153,7 +153,7 @@ TEST_F(IndexBoundsBuilderTest, TranslateLteCode) { IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); ASSERT_EQUALS(oil.name, "a"); ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(oil.intervals[0].toString(), "[, function(){ return 0; }]"); + ASSERT_EQUALS(oil.intervals[0].toString(false), "[, function(){ return 0; }]"); ASSERT_TRUE(oil.intervals[0].startInclusive); ASSERT_TRUE(oil.intervals[0].endInclusive); ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); @@ -169,7 +169,7 @@ TEST_F(IndexBoundsBuilderTest, TranslateLteCodeWScope) { IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); ASSERT_EQUALS(oil.name, "a"); ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(oil.intervals[0].toString(), + ASSERT_EQUALS(oil.intervals[0].toString(false), "[CodeWScope( , {}), CodeWScope( this.b == c, { c: 1 })]"); ASSERT_TRUE(oil.intervals[0].startInclusive); ASSERT_TRUE(oil.intervals[0].endInclusive); @@ -186,7 +186,7 @@ TEST_F(IndexBoundsBuilderTest, TranslateLteMinKey) { IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); ASSERT_EQUALS(oil.name, "a"); ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(oil.intervals[0].toString(), "[MinKey, MinKey]"); + ASSERT_EQUALS(oil.intervals[0].toString(false), "[MinKey, MinKey]"); ASSERT_TRUE(oil.intervals[0].startInclusive); ASSERT_TRUE(oil.intervals[0].endInclusive); ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); @@ -202,7 +202,7 @@ TEST_F(IndexBoundsBuilderTest, TranslateLteMaxKey) { IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); ASSERT_EQUALS(oil.name, "a"); ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(oil.intervals[0].toString(), "[MinKey, MaxKey]"); + ASSERT_EQUALS(oil.intervals[0].toString(false), "[MinKey, MaxKey]"); ASSERT_TRUE(oil.intervals[0].startInclusive); ASSERT_TRUE(oil.intervals[0].endInclusive); ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); @@ -296,7 +296,7 @@ TEST_F(IndexBoundsBuilderTest, TranslateLtCode) { IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); ASSERT_EQUALS(oil.name, "a"); ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(oil.intervals[0].toString(), "[, function(){ return 0; })"); + ASSERT_EQUALS(oil.intervals[0].toString(false), "[, function(){ return 0; })"); ASSERT_TRUE(oil.intervals[0].startInclusive); ASSERT_FALSE(oil.intervals[0].endInclusive); ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); @@ -312,7 +312,7 @@ TEST_F(IndexBoundsBuilderTest, TranslateLtCodeWScope) { IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); ASSERT_EQUALS(oil.name, "a"); ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(oil.intervals[0].toString(), + ASSERT_EQUALS(oil.intervals[0].toString(false), "[CodeWScope( , {}), CodeWScope( this.b == c, { c: 1 }))"); ASSERT_TRUE(oil.intervals[0].startInclusive); ASSERT_FALSE(oil.intervals[0].endInclusive); @@ -343,7 +343,7 @@ TEST_F(IndexBoundsBuilderTest, TranslateLtMaxKey) { IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); ASSERT_EQUALS(oil.name, "a"); ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(oil.intervals[0].toString(), "[MinKey, MaxKey)"); + ASSERT_EQUALS(oil.intervals[0].toString(false), "[MinKey, MaxKey)"); ASSERT_TRUE(oil.intervals[0].startInclusive); ASSERT_FALSE(oil.intervals[0].endInclusive); ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); @@ -455,7 +455,7 @@ TEST_F(IndexBoundsBuilderTest, TranslateGtCode) { IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); ASSERT_EQUALS(oil.name, "a"); ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(oil.intervals[0].toString(), "(function(){ return 0; }, CodeWScope( , {}))"); + ASSERT_EQUALS(oil.intervals[0].toString(false), "(function(){ return 0; }, CodeWScope( , {}))"); ASSERT_FALSE(oil.intervals[0].startInclusive); ASSERT_FALSE(oil.intervals[0].endInclusive); ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); @@ -471,7 +471,7 @@ TEST_F(IndexBoundsBuilderTest, TranslateGtCodeWScope) { IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); ASSERT_EQUALS(oil.name, "a"); ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(oil.intervals[0].toString(), "(CodeWScope( this.b == c, { c: 1 }), MaxKey)"); + ASSERT_EQUALS(oil.intervals[0].toString(false), "(CodeWScope( this.b == c, { c: 1 }), MaxKey)"); ASSERT_FALSE(oil.intervals[0].startInclusive); ASSERT_FALSE(oil.intervals[0].endInclusive); ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); @@ -487,7 +487,7 @@ TEST_F(IndexBoundsBuilderTest, TranslateGtMinKey) { IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); ASSERT_EQUALS(oil.name, "a"); ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(oil.intervals[0].toString(), "(MinKey, MaxKey]"); + ASSERT_EQUALS(oil.intervals[0].toString(false), "(MinKey, MaxKey]"); ASSERT_FALSE(oil.intervals[0].startInclusive); ASSERT_TRUE(oil.intervals[0].endInclusive); ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); @@ -581,7 +581,7 @@ TEST_F(IndexBoundsBuilderTest, TranslateGteCode) { IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); ASSERT_EQUALS(oil.name, "a"); ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(oil.intervals[0].toString(), "[function(){ return 0; }, CodeWScope( , {}))"); + ASSERT_EQUALS(oil.intervals[0].toString(false), "[function(){ return 0; }, CodeWScope( , {}))"); ASSERT_TRUE(oil.intervals[0].startInclusive); ASSERT_FALSE(oil.intervals[0].endInclusive); ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); @@ -597,7 +597,7 @@ TEST_F(IndexBoundsBuilderTest, TranslateGteCodeWScope) { IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); ASSERT_EQUALS(oil.name, "a"); ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(oil.intervals[0].toString(), "[CodeWScope( this.b == c, { c: 1 }), MaxKey)"); + ASSERT_EQUALS(oil.intervals[0].toString(false), "[CodeWScope( this.b == c, { c: 1 }), MaxKey)"); ASSERT_TRUE(oil.intervals[0].startInclusive); ASSERT_FALSE(oil.intervals[0].endInclusive); ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); @@ -613,7 +613,7 @@ TEST_F(IndexBoundsBuilderTest, TranslateGteMinKey) { IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); ASSERT_EQUALS(oil.name, "a"); ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(oil.intervals[0].toString(), "[MinKey, MaxKey]"); + ASSERT_EQUALS(oil.intervals[0].toString(false), "[MinKey, MaxKey]"); ASSERT_TRUE(oil.intervals[0].startInclusive); ASSERT_TRUE(oil.intervals[0].endInclusive); ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); @@ -629,7 +629,7 @@ TEST_F(IndexBoundsBuilderTest, TranslateGteMaxKey) { IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); ASSERT_EQUALS(oil.name, "a"); ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(oil.intervals[0].toString(), "[MaxKey, MaxKey]"); + ASSERT_EQUALS(oil.intervals[0].toString(false), "[MaxKey, MaxKey]"); ASSERT_TRUE(oil.intervals[0].startInclusive); ASSERT_TRUE(oil.intervals[0].endInclusive); ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); @@ -1386,7 +1386,7 @@ TEST_F(IndexBoundsBuilderTest, TranslateInternalExprGTNull) { IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); ASSERT_EQUALS(oil.name, "a"); ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(oil.intervals[0].toString(), "(null, MaxKey]"); + ASSERT_EQUALS(oil.intervals[0].toString(false), "(null, MaxKey]"); ASSERT_FALSE(oil.intervals[0].startInclusive); ASSERT_TRUE(oil.intervals[0].endInclusive); ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); @@ -1480,7 +1480,7 @@ TEST_F(IndexBoundsBuilderTest, TranslateInternalExprGTENull) { IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); ASSERT_EQUALS(oil.name, "a"); ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(oil.intervals[0].toString(), "[null, MaxKey]"); + ASSERT_EQUALS(oil.intervals[0].toString(false), "[null, MaxKey]"); ASSERT_TRUE(oil.intervals[0].startInclusive); ASSERT_TRUE(oil.intervals[0].endInclusive); ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); @@ -1497,7 +1497,7 @@ TEST_F(IndexBoundsBuilderTest, TranslateInternalExprGTEMaxKeyGeneratesBounds) { IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); ASSERT_EQUALS(oil.name, "a"); ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(oil.intervals[0].toString(), "[MaxKey, MaxKey]"); + ASSERT_EQUALS(oil.intervals[0].toString(false), "[MaxKey, MaxKey]"); ASSERT_TRUE(oil.intervals[0].startInclusive); ASSERT_TRUE(oil.intervals[0].endInclusive); ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); @@ -1577,7 +1577,7 @@ TEST_F(IndexBoundsBuilderTest, TranslateInternalExprLTNull) { IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); ASSERT_EQUALS(oil.name, "a"); ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(oil.intervals[0].toString(), "[MinKey, null]"); + ASSERT_EQUALS(oil.intervals[0].toString(false), "[MinKey, null]"); ASSERT_TRUE(oil.intervals[0].startInclusive); ASSERT_TRUE(oil.intervals[0].endInclusive); ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); @@ -1671,7 +1671,7 @@ TEST_F(IndexBoundsBuilderTest, TranslateInternalExprLTENull) { IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); ASSERT_EQUALS(oil.name, "a"); ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(oil.intervals[0].toString(), "[MinKey, null]"); + ASSERT_EQUALS(oil.intervals[0].toString(false), "[MinKey, null]"); ASSERT_TRUE(oil.intervals[0].startInclusive); ASSERT_TRUE(oil.intervals[0].endInclusive); ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); @@ -1688,7 +1688,7 @@ TEST_F(IndexBoundsBuilderTest, TranslateInternalExprLTEMinKeyGeneratesBounds) { IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); ASSERT_EQUALS(oil.name, "a"); ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(oil.intervals[0].toString(), "[MinKey, MinKey]"); + ASSERT_EQUALS(oil.intervals[0].toString(false), "[MinKey, MinKey]"); ASSERT_TRUE(oil.intervals[0].startInclusive); ASSERT_TRUE(oil.intervals[0].endInclusive); ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); diff --git a/src/mongo/db/query/index_bounds_test.cpp b/src/mongo/db/query/index_bounds_test.cpp index a6328bbe08a..45cabc3cc6e 100644 --- a/src/mongo/db/query/index_bounds_test.cpp +++ b/src/mongo/db/query/index_bounds_test.cpp @@ -621,6 +621,38 @@ TEST(IndexBoundsTest, ForwardizeOnNonSimpleRangeShouldOnlyReverseDescendingRange ASSERT(expectedBounds == forwardizedBounds); } +TEST(IndexBoundsTest, BoundsDebugStringFormatTest) { + // The bounds consist of a string and a non-string interval: + // {a: [["string", "string"]], b: [[1,1]]}. + OrderedIntervalList stringInterval; + stringInterval.name = "a"; + stringInterval.intervals.push_back(Interval(BSON("" + << "string" + << "" + << "string"), + true, + true)); + + OrderedIntervalList nonStringInterval; + nonStringInterval.name = "b"; + nonStringInterval.intervals.push_back(Interval(BSON("" << 1 << "" << 1), true, true)); + + IndexBounds bounds; + bounds.fields.push_back(stringInterval); + bounds.fields.push_back(nonStringInterval); + + // First test the debug format pretending there is no non-simple collation preset. + bool hasNonSimpleCollation = false; + ASSERT_EQ(stringInterval.toString(hasNonSimpleCollation), "['a']: [\"string\", \"string\"]"); + ASSERT_EQ(nonStringInterval.toString(hasNonSimpleCollation), "['b']: [1, 1]"); + + // Now test pretending there is a non-simple collation. + hasNonSimpleCollation = true; + ASSERT_EQ(stringInterval.toString(true), + "['a']: [CollationKey(0x737472696e67), CollationKey(0x737472696e67)]"); + ASSERT_EQ(nonStringInterval.toString(true), "['b']: [1, 1]"); +} + // // Iteration over // @@ -991,7 +1023,7 @@ void testFindIntervalForField(int key, if (expectedLocation != location) { str::stream ss; ss << "Unexpected location from findIntervalForField: key=" << keyElt - << "; intervals=" << oil.toString() << "; direction=" << expectedDirection + << "; intervals=" << oil.toString(false) << "; direction=" << expectedDirection << ". Expected: " << toString(expectedLocation) << ". Actual: " << toString(location); FAIL(ss); } @@ -1001,7 +1033,7 @@ void testFindIntervalForField(int key, expectedIntervalIndex != intervalIndex) { str::stream ss; ss << "Unexpected interval index from findIntervalForField: key=" << keyElt - << "; intervals=" << oil.toString() << "; direction=" << expectedDirection + << "; intervals=" << oil.toString(false) << "; direction=" << expectedDirection << "; location= " << toString(location) << ". Expected: " << expectedIntervalIndex << ". Actual: " << intervalIndex; FAIL(ss); diff --git a/src/mongo/db/query/interval.h b/src/mongo/db/query/interval.h index 7a9c565d3f0..26fadffb11b 100644 --- a/src/mongo/db/query/interval.h +++ b/src/mongo/db/query/interval.h @@ -30,6 +30,7 @@ #pragma once #include "mongo/db/jsobj.h" +#include "mongo/util/hex.h" #include "mongo/util/str.h" namespace mongo { @@ -52,17 +53,30 @@ struct Interval { /** Creates an empty interval */ Interval(); - std::string toString() const { + /** + * Generates a debug string for an interval. If interval 'hasNonSimpleCollation', then string + * bounds are hex-encoded. + */ + std::string toString(bool hasNonSimpleCollation) const { str::stream ss; if (startInclusive) { ss << "["; } else { ss << "("; } - // false means omit the field name - ss << start.toString(false); + auto boundToString = [&](BSONElement bound) { + if (bound.type() == BSONType::String && hasNonSimpleCollation) { + ss << "CollationKey("; + // False means omit the field name. + ss << "0x" << hexblob::encodeLower(bound.valueStringData()); + ss << ")"; + } else { + ss << bound.toString(false); + } + }; + boundToString(start); ss << ", "; - ss << end.toString(false); + boundToString(end); if (endInclusive) { ss << "]"; } else { diff --git a/src/mongo/db/query/plan_explainer_sbe.cpp b/src/mongo/db/query/plan_explainer_sbe.cpp index 00035f473f2..35c9a2830ac 100644 --- a/src/mongo/db/query/plan_explainer_sbe.cpp +++ b/src/mongo/db/query/plan_explainer_sbe.cpp @@ -114,7 +114,7 @@ void statsToBSON(const QuerySolutionNode* node, bob->append("indexVersion", static_cast<int>(ixn->index.version)); bob->append("direction", ixn->direction > 0 ? "forward" : "backward"); - auto bounds = ixn->bounds.toBSON(); + auto bounds = ixn->bounds.toBSON(!collation.isEmpty()); if (topLevelBob->len() + bounds.objsize() > internalQueryExplainSizeThresholdBytes.load()) { bob->append("warning", "index bounds omitted due to BSON size limit for explain"); diff --git a/src/mongo/db/query/planner_access.cpp b/src/mongo/db/query/planner_access.cpp index 58652f73279..99073842737 100644 --- a/src/mongo/db/query/planner_access.cpp +++ b/src/mongo/db/query/planner_access.cpp @@ -971,7 +971,8 @@ void QueryPlannerAccess::finishLeafNode(QuerySolutionNode* node, const IndexEntr // All fields are filled out with bounds, nothing to do. if (firstEmptyField == bounds->fields.size()) { - return IndexBoundsBuilder::alignBounds(bounds, nodeIndex->keyPattern); + return IndexBoundsBuilder::alignBounds( + bounds, nodeIndex->keyPattern, nodeIndex->collator != nullptr); } // Skip ahead to the firstEmptyField-th element, where we begin filling in bounds. @@ -998,7 +999,7 @@ void QueryPlannerAccess::finishLeafNode(QuerySolutionNode* node, const IndexEntr // We create bounds assuming a forward direction but can easily reverse bounds to align // according to our desired direction. - IndexBoundsBuilder::alignBounds(bounds, nodeIndex->keyPattern); + IndexBoundsBuilder::alignBounds(bounds, nodeIndex->keyPattern, nodeIndex->collator != nullptr); } void QueryPlannerAccess::findElemMatchChildren(const MatchExpression* node, @@ -1698,7 +1699,7 @@ std::unique_ptr<QuerySolutionNode> QueryPlannerAccess::scanWholeIndex( isn->addKeyMetadata = query.metadataDeps()[DocumentMetadataFields::kIndexKey]; isn->queryCollator = query.getCollator(); - IndexBoundsBuilder::allValuesBounds(index.keyPattern, &isn->bounds); + IndexBoundsBuilder::allValuesBounds(index.keyPattern, &isn->bounds, index.collator != nullptr); if (-1 == direction) { QueryPlannerCommon::reverseScans(isn.get()); diff --git a/src/mongo/db/query/planner_wildcard_helpers.cpp b/src/mongo/db/query/planner_wildcard_helpers.cpp index 395d0253e04..a829a460aa6 100644 --- a/src/mongo/db/query/planner_wildcard_helpers.cpp +++ b/src/mongo/db/query/planner_wildcard_helpers.cpp @@ -504,7 +504,7 @@ void finalizeWildcardIndexScanConfiguration(IndexScanNode* scan) { } } // Ensure that the bounds' intervals are correctly aligned. - IndexBoundsBuilder::alignBounds(bounds, index->keyPattern); + IndexBoundsBuilder::alignBounds(bounds, index->keyPattern, index->collator != nullptr); } bool isWildcardObjectSubpathScan(const IndexScanNode* node) { diff --git a/src/mongo/db/query/query_planner_common.cpp b/src/mongo/db/query/query_planner_common.cpp index bba5b5d6eb5..85534e6128c 100644 --- a/src/mongo/db/query/query_planner_common.cpp +++ b/src/mongo/db/query/query_planner_common.cpp @@ -50,7 +50,8 @@ void QueryPlannerCommon::reverseScans(QuerySolutionNode* node) { isn->bounds = isn->bounds.reverse(); invariant(isn->bounds.isValidFor(isn->index.keyPattern, isn->direction), - str::stream() << "Invalid bounds: " << redact(isn->bounds.toString())); + str::stream() << "Invalid bounds: " + << redact(isn->bounds.toString(isn->index.collator != nullptr))); // TODO: we can just negate every value in the already computed properties. isn->computeProperties(); @@ -61,7 +62,8 @@ void QueryPlannerCommon::reverseScans(QuerySolutionNode* node) { dn->bounds = dn->bounds.reverse(); invariant(dn->bounds.isValidFor(dn->index.keyPattern, dn->direction), - str::stream() << "Invalid bounds: " << redact(dn->bounds.toString())); + str::stream() << "Invalid bounds: " + << redact(dn->bounds.toString(dn->index.collator != nullptr))); dn->computeProperties(); } else if (STAGE_SORT_MERGE == type) { diff --git a/src/mongo/db/query/query_planner_test_lib.cpp b/src/mongo/db/query/query_planner_test_lib.cpp index de41618357d..1d78d8a086d 100644 --- a/src/mongo/db/query/query_planner_test_lib.cpp +++ b/src/mongo/db/query/query_planner_test_lib.cpp @@ -151,8 +151,8 @@ Status intervalMatches(const BSONObj& testInt, const Interval trueInt) { return Status::OK(); } return {ErrorCodes::Error{5619217}, - str::stream() << "provided interval did not match. Expected: " << toCompare.toString() - << " Found: " << trueInt.toString()}; + str::stream() << "provided interval did not match. Expected: " + << toCompare.toString(false) << " Found: " << trueInt.toString(false)}; } bool bsonObjFieldsAreInSet(BSONObj obj, const std::set<std::string>& allowedFields) { diff --git a/src/mongo/db/query/query_solution.cpp b/src/mongo/db/query/query_solution.cpp index f98c056bbaa..4062c0516ef 100644 --- a/src/mongo/db/query/query_solution.cpp +++ b/src/mongo/db/query/query_solution.cpp @@ -599,7 +599,7 @@ void IndexScanNode::appendToString(str::stream* ss, int indent) const { addIndent(ss, indent + 1); *ss << "direction = " << direction << '\n'; addIndent(ss, indent + 1); - *ss << "bounds = " << bounds.toString() << '\n'; + *ss << "bounds = " << bounds.toString(index.collator != nullptr) << '\n'; addCommon(ss, indent); } @@ -1304,7 +1304,7 @@ void GeoNear2DSphereNode::appendToString(str::stream* ss, int indent) const { addIndent(ss, indent + 1); *ss << "keyPattern = " << index.keyPattern.toString() << '\n'; addCommon(ss, indent); - *ss << "baseBounds = " << baseBounds.toString() << '\n'; + *ss << "baseBounds = " << baseBounds.toString(index.collator != nullptr) << '\n'; addIndent(ss, indent + 1); *ss << "nearQuery = " << nq->toString() << '\n'; if (nullptr != filter) { @@ -1365,7 +1365,7 @@ void DistinctNode::appendToString(str::stream* ss, int indent) const { addIndent(ss, indent + 1); *ss << "direction = " << direction << '\n'; addIndent(ss, indent + 1); - *ss << "bounds = " << bounds.toString() << '\n'; + *ss << "bounds = " << bounds.toString(index.collator != nullptr) << '\n'; } QuerySolutionNode* DistinctNode::clone() const { diff --git a/src/mongo/s/chunk_manager.cpp b/src/mongo/s/chunk_manager.cpp index 3f829e6791f..d784898b656 100644 --- a/src/mongo/s/chunk_manager.cpp +++ b/src/mongo/s/chunk_manager.cpp @@ -567,7 +567,7 @@ IndexBounds ChunkManager::getIndexBoundsForQuery(const BSONObj& key, // the query { a: 2, $text: { ... } } will only target to {a: 2}. if (QueryPlannerCommon::hasNode(canonicalQuery.root(), MatchExpression::TEXT)) { IndexBounds bounds; - IndexBoundsBuilder::allValuesBounds(key, &bounds); // [minKey, maxKey] + IndexBoundsBuilder::allValuesBounds(key, &bounds, false); // [minKey, maxKey] return bounds; } @@ -589,7 +589,7 @@ IndexBounds ChunkManager::getIndexBoundsForQuery(const BSONObj& key, if (!geoIdx) { IndexBounds bounds; - IndexBoundsBuilder::allValuesBounds(key, &bounds); + IndexBoundsBuilder::allValuesBounds(key, &bounds, false); return bounds; } @@ -640,7 +640,7 @@ IndexBounds ChunkManager::getIndexBoundsForQuery(const BSONObj& key, // We cannot plan the query without collection scan, so target to all shards. IndexBounds bounds; - IndexBoundsBuilder::allValuesBounds(key, &bounds); // [minKey, maxKey] + IndexBoundsBuilder::allValuesBounds(key, &bounds, false); // [minKey, maxKey] return bounds; } diff --git a/src/mongo/s/chunk_manager_index_bounds_test.cpp b/src/mongo/s/chunk_manager_index_bounds_test.cpp index ff1c99604e5..154c774157d 100644 --- a/src/mongo/s/chunk_manager_index_bounds_test.cpp +++ b/src/mongo/s/chunk_manager_index_bounds_test.cpp @@ -91,8 +91,8 @@ protected: oil.intervals[i].compare(expectedOil.intervals[i])) { LOGV2(22676, "Found mismatching field interval", - "queryFieldInterval"_attr = oil.intervals[i], - "expectedFieldInterval"_attr = expectedOil.intervals[i]); + "queryFieldInterval"_attr = oil.intervals[i].toString(false), + "expectedFieldInterval"_attr = expectedOil.intervals[i].toString(false)); } ASSERT_EQUALS(Interval::INTERVAL_EQUALS, oil.intervals[i].compare(expectedOil.intervals[i])); @@ -114,8 +114,8 @@ protected: if (oil.intervals.size() != expectedOil.intervals.size()) { LOGV2(22677, "Found mismatching field intervals", - "queryFieldInterval"_attr = oil, - "expectedFieldInterval"_attr = expectedOil); + "queryFieldInterval"_attr = oil.toString(false), + "expectedFieldInterval"_attr = expectedOil.toString(false)); } ASSERT_EQUALS(oil.intervals.size(), expectedOil.intervals.size()); |