summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrii Dobroshynski <andrii.dobroshynski@mongodb.com>2022-02-05 18:02:46 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2022-02-05 18:49:41 +0000
commita1e5f50b2d3f162b4ade24ef4cf8035855bf5658 (patch)
tree7cfd61404afc63ce55628c136eb48701c19ebacb
parent8819693dce1282c2e93aa49c488473cb12429cec (diff)
downloadmongo-a1e5f50b2d3f162b4ade24ef4cf8035855bf5658.tar.gz
SERVER-60298 Hex encode the index bounds if associated with collation
-rw-r--r--src/mongo/db/exec/distinct_scan.cpp2
-rw-r--r--src/mongo/db/exec/index_scan.cpp2
-rw-r--r--src/mongo/db/query/expression_index.cpp15
-rw-r--r--src/mongo/db/query/get_executor.cpp3
-rw-r--r--src/mongo/db/query/index_bounds.cpp13
-rw-r--r--src/mongo/db/query/index_bounds.h17
-rw-r--r--src/mongo/db/query/index_bounds_builder.cpp13
-rw-r--r--src/mongo/db/query/index_bounds_builder.h9
-rw-r--r--src/mongo/db/query/index_bounds_builder_test.cpp40
-rw-r--r--src/mongo/db/query/index_bounds_test.cpp36
-rw-r--r--src/mongo/db/query/interval.h22
-rw-r--r--src/mongo/db/query/plan_explainer_sbe.cpp2
-rw-r--r--src/mongo/db/query/planner_access.cpp7
-rw-r--r--src/mongo/db/query/planner_wildcard_helpers.cpp2
-rw-r--r--src/mongo/db/query/query_planner_common.cpp6
-rw-r--r--src/mongo/db/query/query_planner_test_lib.cpp4
-rw-r--r--src/mongo/db/query/query_solution.cpp6
-rw-r--r--src/mongo/s/chunk_manager.cpp6
-rw-r--r--src/mongo/s/chunk_manager_index_bounds_test.cpp8
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());