summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDavid Storch <david.storch@10gen.com>2016-08-12 12:10:32 -0400
committerDavid Storch <david.storch@10gen.com>2016-08-15 15:19:41 -0400
commite7b0ba5c67df867bfb31bd6028b41631f04164a6 (patch)
tree1a8ffe9b27211780445bac09115c91304d7af5fc /src
parentafef8d4e7c39fe9c7e2c06a2e8dc5fadbd52cf4d (diff)
downloadmongo-e7b0ba5c67df867bfb31bd6028b41631f04164a6.tar.gz
SERVER-23093 avoid extra predicate evaluation for indexed collation-aware queries
Diffstat (limited to 'src')
-rw-r--r--src/mongo/db/query/index_bounds_builder.cpp33
-rw-r--r--src/mongo/db/query/index_bounds_builder_test.cpp28
-rw-r--r--src/mongo/db/query/query_planner_collation_test.cpp126
-rw-r--r--src/mongo/db/query/query_planner_partialidx_test.cpp2
-rw-r--r--src/mongo/db/query/query_solution.cpp9
-rw-r--r--src/mongo/db/query/query_solution_test.cpp73
6 files changed, 217 insertions, 54 deletions
diff --git a/src/mongo/db/query/index_bounds_builder.cpp b/src/mongo/db/query/index_bounds_builder.cpp
index 86d8a005229..f0b4067fce4 100644
--- a/src/mongo/db/query/index_bounds_builder.cpp
+++ b/src/mongo/db/query/index_bounds_builder.cpp
@@ -57,13 +57,6 @@ namespace {
// Tightness rules are shared for $lt, $lte, $gt, $gte.
IndexBoundsBuilder::BoundsTightness getInequalityPredicateTightness(const BSONElement& dataElt,
const IndexEntry& index) {
- // TODO SERVER-23093: Right now we consider a string comparison in the presence of a
- // collator to be inexact fetch, since such queries cannot be covered. Although it is
- // necessary to fetch the keyed documents, it is not necessary to reapply the filter.
- if (CollationIndexKey::shouldUseCollationIndexKey(dataElt, index.collator)) {
- return IndexBoundsBuilder::INEXACT_FETCH;
- }
-
if (dataElt.isSimpleType() || dataElt.type() == BSONType::BinData) {
return IndexBoundsBuilder::EXACT;
}
@@ -330,15 +323,7 @@ void IndexBoundsBuilder::translate(const MatchExpression* expr,
// return INEXACT_FETCH. Consider a multikey index on 'a' with document {a: [1, 2, 3]} and
// query {a: {$ne: 3}}. If we treated the bounds [MinKey, 3), (3, MaxKey] as exact, then we
// would erroneously return the document!
- //
- // If the index has a collator, then complementing the bounds generally results in strings
- // being in-bounds. Such index bounds cannot be used in a covered plan, since we should
- // never return collator comparison keys to the user. As such, we must make the bounds
- // INEXACT_FETCH in this case.
- //
- // TODO SERVER-23093: Although it is necessary to fetch the keyed documents, it is not
- // necessary to reapply the filter.
- if (index.multikey || index.collator) {
+ if (index.multikey) {
*tightnessOut = INEXACT_FETCH;
}
} else if (MatchExpression::EXISTS == expr->matchType()) {
@@ -362,13 +347,7 @@ void IndexBoundsBuilder::translate(const MatchExpression* expr,
// {a:{ $exists:false }} - sparse indexes cannot be used at all.
//
// Noted in SERVER-12869, in case this ever changes some day.
- //
- // Bounds are always INEXACT_FETCH if there is a collator on the index, since the bounds
- // include collator-generated sort keys that shouldn't be returned to the user.
- //
- // TODO SERVER-23093: Although it is necessary to fetch the keyed documents when there is a
- // collator, it is not necessary to reapply the filter.
- if (index.sparse && !index.collator) {
+ if (index.sparse) {
// A sparse, compound index on { a:1, b:1 } will include entries
// for all of the following documents:
// { a:1 }, { b:1 }, { a:1, b:1 }
@@ -830,14 +809,6 @@ void IndexBoundsBuilder::translateEquality(const BSONElement& data,
verify(dataObj.isOwned());
oil->intervals.push_back(makePointInterval(dataObj));
- // TODO SERVER-23093: Right now we consider a string comparison in the presence of a
- // collator to be inexact fetch, since such queries cannot be covered. Although it is
- // necessary to fetch the keyed documents, it is not necessary to reapply the filter.
- if (CollationIndexKey::shouldUseCollationIndexKey(data, index.collator)) {
- *tightnessOut = IndexBoundsBuilder::INEXACT_FETCH;
- return;
- }
-
if (dataObj.firstElement().isNull() || isHashed) {
*tightnessOut = IndexBoundsBuilder::INEXACT_FETCH;
} else {
diff --git a/src/mongo/db/query/index_bounds_builder_test.cpp b/src/mongo/db/query/index_bounds_builder_test.cpp
index af346b5f908..c894de8360e 100644
--- a/src/mongo/db/query/index_bounds_builder_test.cpp
+++ b/src/mongo/db/query/index_bounds_builder_test.cpp
@@ -1538,7 +1538,7 @@ TEST(IndexBoundsBuilderTest, TranslateEqualityToStringWithMockCollator) {
ASSERT_EQUALS(
Interval::INTERVAL_EQUALS,
oil.intervals[0].compare(Interval(fromjson("{'': 'oof', '': 'oof'}"), true, true)));
- ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH);
+ ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT);
}
TEST(IndexBoundsBuilderTest, TranslateEqualityToNonStringWithMockCollator) {
@@ -1578,7 +1578,7 @@ TEST(IndexBoundsBuilderTest, TranslateNotEqualToStringWithMockCollator) {
// Bounds should be [MinKey, "rab"), ("rab", MaxKey].
ASSERT_EQUALS(oil.name, "a");
ASSERT_EQUALS(oil.intervals.size(), 2U);
- ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH);
+ ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT);
{
BSONObjBuilder bob;
@@ -1635,7 +1635,7 @@ TEST(IndexBoundsBuilderTest, TranslateLTEToStringWithMockCollator) {
ASSERT_EQUALS(oil.intervals.size(), 1U);
ASSERT_EQUALS(Interval::INTERVAL_EQUALS,
oil.intervals[0].compare(Interval(fromjson("{'': '', '': 'oof'}"), true, true)));
- ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH);
+ ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT);
}
TEST(IndexBoundsBuilderTest, TranslateLTEToNumberWithMockCollator) {
@@ -1676,7 +1676,7 @@ TEST(IndexBoundsBuilderTest, TranslateLTStringWithMockCollator) {
ASSERT_EQUALS(oil.intervals.size(), 1U);
ASSERT_EQUALS(Interval::INTERVAL_EQUALS,
oil.intervals[0].compare(Interval(fromjson("{'': '', '': 'oof'}"), true, false)));
- ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH);
+ ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT);
}
TEST(IndexBoundsBuilderTest, TranslateLTNumberWithMockCollator) {
@@ -1718,7 +1718,7 @@ TEST(IndexBoundsBuilderTest, TranslateGTStringWithMockCollator) {
ASSERT_EQUALS(
Interval::INTERVAL_EQUALS,
oil.intervals[0].compare(Interval(fromjson("{'': 'oof', '': {}}"), false, false)));
- ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH);
+ ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT);
}
TEST(IndexBoundsBuilderTest, TranslateGTNumberWithMockCollator) {
@@ -1759,7 +1759,7 @@ TEST(IndexBoundsBuilderTest, TranslateGTEToStringWithMockCollator) {
ASSERT_EQUALS(oil.intervals.size(), 1U);
ASSERT_EQUALS(Interval::INTERVAL_EQUALS,
oil.intervals[0].compare(Interval(fromjson("{'': 'oof', '': {}}"), true, false)));
- ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH);
+ ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT);
}
TEST(IndexBoundsBuilderTest, TranslateGTEToNumberWithMockCollator) {
@@ -1805,7 +1805,7 @@ TEST(IndexBoundsBuilderTest, SimplePrefixRegexWithMockCollator) {
ASSERT(tightness == IndexBoundsBuilder::INEXACT_FETCH);
}
-TEST(IndexBoundsBuilderTest, NotWithMockCollatorIsInexactFetch) {
+TEST(IndexBoundsBuilderTest, NotWithMockCollatorIsExact) {
CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
IndexEntry testIndex = IndexEntry(BSONObj());
testIndex.collator = &collator;
@@ -1823,10 +1823,10 @@ TEST(IndexBoundsBuilderTest, NotWithMockCollatorIsInexactFetch) {
oil.intervals[0].compare(Interval(minKeyIntObj(3), true, false)));
ASSERT_EQUALS(Interval::INTERVAL_EQUALS,
oil.intervals[1].compare(Interval(maxKeyIntObj(3), false, true)));
- ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH);
+ ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT);
}
-TEST(IndexBoundsBuilderTest, ExistsTrueWithMockCollatorAndSparseIsInexactFetch) {
+TEST(IndexBoundsBuilderTest, ExistsTrueWithMockCollatorAndSparseIsExact) {
CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
IndexEntry testIndex = IndexEntry(BSONObj());
testIndex.collator = &collator;
@@ -1844,7 +1844,7 @@ TEST(IndexBoundsBuilderTest, ExistsTrueWithMockCollatorAndSparseIsInexactFetch)
ASSERT_EQUALS(oil.intervals.size(), 1U);
ASSERT_EQUALS(Interval::INTERVAL_EQUALS,
oil.intervals[0].compare(IndexBoundsBuilder::allValues()));
- ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH);
+ ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT);
}
TEST(IndexBoundsBuilderTest, ExistsFalseWithMockCollatorIsInexactFetch) {
@@ -1887,7 +1887,7 @@ TEST(IndexBoundsBuilderTest, TypeStringIsInexactFetch) {
ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH);
}
-TEST(IndexBoundsBuilderTest, InWithStringAndCollatorIsInexactFetch) {
+TEST(IndexBoundsBuilderTest, InWithStringAndCollatorIsExact) {
CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
IndexEntry testIndex = IndexEntry(BSONObj());
testIndex.collator = &collator;
@@ -1905,10 +1905,10 @@ TEST(IndexBoundsBuilderTest, InWithStringAndCollatorIsInexactFetch) {
ASSERT_EQUALS(
Interval::INTERVAL_EQUALS,
oil.intervals[0].compare(Interval(fromjson("{'': 'oof', '': 'oof'}"), true, true)));
- ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH);
+ ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT);
}
-TEST(IndexBoundsBuilderTest, InWithNumberAndStringAndCollatorIsInexactFetch) {
+TEST(IndexBoundsBuilderTest, InWithNumberAndStringAndCollatorIsExact) {
CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
IndexEntry testIndex = IndexEntry(BSONObj());
testIndex.collator = &collator;
@@ -1928,7 +1928,7 @@ TEST(IndexBoundsBuilderTest, InWithNumberAndStringAndCollatorIsInexactFetch) {
ASSERT_EQUALS(
Interval::INTERVAL_EQUALS,
oil.intervals[1].compare(Interval(fromjson("{'': 'oof', '': 'oof'}"), true, true)));
- ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH);
+ ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT);
}
TEST(IndexBoundsBuilderTest, InWithRegexAndCollatorIsInexactFetch) {
diff --git a/src/mongo/db/query/query_planner_collation_test.cpp b/src/mongo/db/query/query_planner_collation_test.cpp
index 18aea107334..54a549723c8 100644
--- a/src/mongo/db/query/query_planner_collation_test.cpp
+++ b/src/mongo/db/query/query_planner_collation_test.cpp
@@ -76,7 +76,7 @@ TEST_F(QueryPlannerTest, StringComparisonWithMatchingCollationUsesIndexWithTrans
assertNumSolutions(2U);
assertSolutionExists("{cscan: {dir: 1}}");
assertSolutionExists(
- "{fetch: {filter: {a: {$lt: 'foo'}}, collation: {locale: 'reverse'}, node: {ixscan: "
+ "{fetch: {filter: null, collation: {locale: 'reverse'}, node: {ixscan: "
"{pattern: {a: 1}, filter: null, "
"bounds: {a: [['', 'oof', true, false]]}}}}}");
}
@@ -97,7 +97,7 @@ TEST_F(QueryPlannerTest, StringComparisonAndNonStringComparisonCanUseSeparateInd
assertNumSolutions(3U);
assertSolutionExists("{cscan: {dir: 1}}");
assertSolutionExists(
- "{fetch: {filter: {a: {$lt: 'foo'}, b: {$lte: 4}}, collation: {locale: 'reverse'}, node: "
+ "{fetch: {filter: {b: {$lte: 4}}, collation: {locale: 'reverse'}, node: "
"{ixscan: {pattern: {a: 1}, "
"filter: null, bounds: {a: [['', 'oof', true, false]]}}}}}");
assertSolutionExists(
@@ -106,7 +106,24 @@ TEST_F(QueryPlannerTest, StringComparisonAndNonStringComparisonCanUseSeparateInd
"bounds: {b: [[-Infinity, 4, true, true]]}}}}}");
}
-TEST_F(QueryPlannerTest, StringComparisonsWRTCollatorCannotBeCovered) {
+TEST_F(QueryPlannerTest, StringEqWrtCollatorCannotBeCovered) {
+ CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
+ addIndex(fromjson("{a: 1}"), &collator);
+
+ runQueryAsCommand(
+ fromjson("{find: 'testns', filter: {a: 'string'}, projection: {_id: 0, a: 1}, collation: "
+ "{locale: 'reverse'}}"));
+
+ assertNumSolutions(2U);
+ assertSolutionExists("{proj: {spec: {_id: 0, a: 1}, node: {cscan: {dir: 1}}}}");
+ assertSolutionExists(
+ "{proj: {spec: {_id: 0, a: 1}, node: {fetch: {filter: null, collation: "
+ "{locale: 'reverse'}, node: "
+ "{ixscan: {pattern: {a: 1}, filter: null, bounds: {a: [['gnirts', 'gnirts', true, "
+ "true]]}}}}}}}");
+}
+
+TEST_F(QueryPlannerTest, StringGteWrtCollatorCannotBeCovered) {
CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
addIndex(fromjson("{a: 1}"), &collator);
@@ -117,12 +134,105 @@ TEST_F(QueryPlannerTest, StringComparisonsWRTCollatorCannotBeCovered) {
assertNumSolutions(2U);
assertSolutionExists("{proj: {spec: {_id: 0, a: 1}, node: {cscan: {dir: 1}}}}");
assertSolutionExists(
- "{proj: {spec: {_id: 0, a: 1}, node: {fetch: {filter: {a: {$gte: 'string'}}, collation: "
+ "{proj: {spec: {_id: 0, a: 1}, node: {fetch: {filter: null, collation: "
"{locale: 'reverse'}, node: "
"{ixscan: {pattern: {a: 1}, filter: null, bounds: {a: [['gnirts', {}, true, "
"false]]}}}}}}}");
}
+TEST_F(QueryPlannerTest, InContainingStringCannotBeCoveredWithCollator) {
+ CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
+ addIndex(fromjson("{a: 1}"), &collator);
+
+ runQueryAsCommand(fromjson(
+ "{find: 'testns', filter: {a: {$in: [2, 'foo']}}, projection: {_id: 0, a: 1}, collation: "
+ "{locale: 'reverse'}}"));
+
+ assertNumSolutions(2U);
+ assertSolutionExists("{proj: {spec: {_id: 0, a: 1}, node: {cscan: {dir: 1}}}}");
+ assertSolutionExists(
+ "{proj: {spec: {_id: 0, a: 1}, node: {fetch: {filter: null, collation: "
+ "{locale: 'reverse'}, node: "
+ "{ixscan: {pattern: {a: 1}, filter: null, bounds: {a: [[2,2,true,true],"
+ "['oof','oof',true,true]]}}}}}}}");
+}
+
+TEST_F(QueryPlannerTest, TypeStringCannotBeCoveredWithCollator) {
+ CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
+ addIndex(fromjson("{a: 1}"), &collator);
+
+ runQueryAsCommand(fromjson(
+ "{find: 'testns', filter: {a: {$type: 'string'}}, projection: {_id: 0, a: 1}, collation: "
+ "{locale: 'reverse'}}"));
+
+ assertNumSolutions(2U);
+ assertSolutionExists("{proj: {spec: {_id: 0, a: 1}, node: {cscan: {dir: 1}}}}");
+ assertSolutionExists(
+ "{proj: {spec: {_id: 0, a: 1}, node: {fetch: {filter: {a:{$type:'string'}}, collation: "
+ "{locale: 'reverse'}, node: {ixscan: {pattern: {a: 1}, filter: null, "
+ "bounds: {a: [['',{},true,true]]}}}}}}}");
+}
+
+TEST_F(QueryPlannerTest, NotWithStringBoundsCannotBeCoveredWithCollator) {
+ CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
+ addIndex(fromjson("{a: 1}"), &collator);
+
+ runQueryAsCommand(
+ fromjson("{find: 'testns', filter: {a: {$ne: 2}}, projection: {_id: 0, a: 1}, collation: "
+ "{locale: 'reverse'}}"));
+
+ assertNumSolutions(2U);
+ assertSolutionExists("{proj: {spec: {_id: 0, a: 1}, node: {cscan: {dir: 1}}}}");
+ assertSolutionExists(
+ "{proj: {spec: {_id: 0, a: 1}, node: {fetch: {filter: null, collation: "
+ "{locale: 'reverse'}, node: {ixscan: {pattern: {a: 1}, filter: null, "
+ "bounds: {a: [['MinKey',2,true,false], [2,'MaxKey',false,true]]}}}}}}}");
+}
+
+TEST_F(QueryPlannerTest, ExistsTrueCannotBeCoveredWithSparseIndexAndCollator) {
+ CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
+ addIndex(fromjson("{a: 1}"), &collator);
+ params.indices.back().sparse = true;
+
+ runQueryAsCommand(fromjson(
+ "{find: 'testns', filter: {a: {$exists: true}}, projection: {_id: 0, a: 1}, collation: "
+ "{locale: 'reverse'}}"));
+
+ assertNumSolutions(2U);
+ assertSolutionExists("{proj: {spec: {_id: 0, a: 1}, node: {cscan: {dir: 1}}}}");
+ assertSolutionExists(
+ "{proj: {spec: {_id: 0, a: 1}, node: {fetch: {filter: null, collation: "
+ "{locale: 'reverse'}, node: {ixscan: {pattern: {a: 1}, filter: null, "
+ "bounds: {a: [['MinKey','MaxKey',true,true]]}}}}}}}");
+}
+
+TEST_F(QueryPlannerTest, MinMaxWithStringBoundsCannotBeCoveredWithCollator) {
+ CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
+ addIndex(fromjson("{a: 1, b: 1}"), &collator);
+
+ runQueryAsCommand(
+ fromjson("{find: 'testns', min: {a: 1, b: 2}, max: {a: 2, b: 1}, "
+ "projection: {_id: 0, a: 1, b: 1}, collation: {locale: 'reverse'}}"));
+
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{proj: {spec: {_id: 0, a: 1, b: 1}, node: {fetch: {filter: null, collation: "
+ "{locale: 'reverse'}, node: {ixscan: {pattern: {a: 1, b: 1}}}}}}}");
+}
+
+TEST_F(QueryPlannerTest, MinMaxWithoutStringBoundsBoundsCanBeCoveredWithCollator) {
+ CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
+ addIndex(fromjson("{a: 1, b: 1}"), &collator);
+
+ runQueryAsCommand(
+ fromjson("{find: 'testns', min: {a: 1, b: 2}, max: {a: 1, b: 2}, "
+ "projection: {_id: 0, a: 1, b: 1}, collation: {locale: 'reverse'}}"));
+
+ assertNumSolutions(1U);
+ assertSolutionExists(
+ "{proj: {spec: {_id: 0, a: 1, b: 1}, node: {ixscan: {pattern: {a: 1, b: 1}}}}}");
+}
+
TEST_F(QueryPlannerTest, SimpleRegexCanUseAnIndexWithACollatorWithLooseBounds) {
CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
addIndex(fromjson("{a: 1}"), &collator);
@@ -176,7 +286,7 @@ TEST_F(QueryPlannerTest, AccessPlannerCorrectlyCombinesComparisonKeyBounds) {
assertNumSolutions(2U);
assertSolutionExists("{cscan: {dir: 1}}");
assertSolutionExists(
- "{fetch: {filter: {a:{$gte:'foo',$lte:'zfoo'},b:'bar'}, collation: {locale: 'reverse'}, "
+ "{fetch: {filter: null, collation: {locale: 'reverse'}, "
"node: {ixscan: {pattern: {a: 1, b: "
"1}, filter: null, bounds: {a: [['oof','oofz',true,true]], b: "
"[['rab','rab',true,true]]}}}}}");
@@ -209,9 +319,9 @@ TEST_F(QueryPlannerTest, OrQueryCanBeIndexedWhenBothBranchesHaveIndexWithMatchin
assertNumSolutions(2U);
assertSolutionExists("{cscan: {dir: 1}}");
assertSolutionExists(
- "{or: {nodes: ["
- "{fetch: {node: {ixscan: {pattern: {a: 1}, bounds: {a: [['oof','oof',true,true]]}}}}},"
- "{fetch: {node: {ixscan: {pattern: {b: 1}, bounds: {b: [['rab','rab',true,true]]}}}}}]}}");
+ "{fetch: {node: {or: {nodes: ["
+ "{ixscan: {pattern: {a: 1}, bounds: {a: [['oof','oof',true,true]]}}},"
+ "{ixscan: {pattern: {b: 1}, bounds: {b: [['rab','rab',true,true]]}}}]}}}}");
}
TEST_F(QueryPlannerTest, ElemMatchObjectResultsInCorrectComparisonKeyBounds) {
diff --git a/src/mongo/db/query/query_planner_partialidx_test.cpp b/src/mongo/db/query/query_planner_partialidx_test.cpp
index 39a025079d0..9626c19d8b3 100644
--- a/src/mongo/db/query/query_planner_partialidx_test.cpp
+++ b/src/mongo/db/query/query_planner_partialidx_test.cpp
@@ -455,7 +455,7 @@ TEST_F(QueryPlannerTest, PartialIndexStringComparisonMatchingCollators) {
fromjson("{find: 'testns', filter: {a: 'abc'}, collation: {locale: 'reverse'}}"));
assertNumSolutions(1U);
assertSolutionExists(
- "{fetch: {filter: {a: 'abc'}, collation: {locale: 'reverse'}, node: {ixscan: "
+ "{fetch: {filter: null, collation: {locale: 'reverse'}, node: {ixscan: "
"{filter: null, pattern: {a: 1}, "
"bounds: {a: [['cba', 'cba', true, true]]}}}}}");
diff --git a/src/mongo/db/query/query_solution.cpp b/src/mongo/db/query/query_solution.cpp
index 60fd5cc84fc..2cf4dc81c36 100644
--- a/src/mongo/db/query/query_solution.cpp
+++ b/src/mongo/db/query/query_solution.cpp
@@ -541,6 +541,15 @@ bool IndexScanNode::hasField(const string& field) const {
return false;
}
+ // If the index has a non-simple collation and we have collation keys inside 'field', then this
+ // index scan does not provide that field (and the query cannot be covered).
+ if (index.collator) {
+ std::set<StringData> collatedFields = getFieldsWithStringBounds(bounds, index.keyPattern);
+ if (collatedFields.find(field) != collatedFields.end()) {
+ return false;
+ }
+ }
+
BSONObjIterator it(index.keyPattern);
while (it.more()) {
if (field == it.next().fieldName()) {
diff --git a/src/mongo/db/query/query_solution_test.cpp b/src/mongo/db/query/query_solution_test.cpp
index 7654ca57867..a116d497661 100644
--- a/src/mongo/db/query/query_solution_test.cpp
+++ b/src/mongo/db/query/query_solution_test.cpp
@@ -554,6 +554,79 @@ TEST(QuerySolutionTest, WithNonMatchingCollatorAndNoEqualityPrefixSortsAreNotDup
ASSERT(node.getSort().count(BSON("a" << 1)));
}
+TEST(QuerySolutionTest, IndexScanNodeHasFieldIncludesStringFieldWhenNoCollator) {
+ IndexScanNode node{IndexEntry(BSON("a" << 1 << "b" << 1))};
+
+ OrderedIntervalList oilA{};
+ oilA.name = "a";
+ oilA.intervals.push_back(IndexBoundsBuilder::makeRangeInterval(BSON(""
+ << "str"
+ << ""
+ << "str"),
+ true,
+ true));
+ node.bounds.fields.push_back(oilA);
+
+ OrderedIntervalList oilB{};
+ oilB.name = "b";
+ oilB.intervals.push_back(IndexBoundsBuilder::allValues());
+ node.bounds.fields.push_back(oilB);
+
+ ASSERT_TRUE(node.hasField("a"));
+ ASSERT_TRUE(node.hasField("b"));
+}
+
+TEST(QuerySolutionTest, IndexScanNodeHasFieldIncludesSimpleBoundsStringFieldWhenNoCollator) {
+ IndexScanNode node{IndexEntry(BSON("a" << 1 << "b" << 1))};
+
+ node.bounds.isSimpleRange = true;
+ node.bounds.startKey = BSON("a" << 1 << "b" << 2);
+ node.bounds.endKey = BSON("a" << 2 << "b" << 1);
+ node.bounds.endKeyInclusive = false;
+
+ ASSERT_TRUE(node.hasField("a"));
+ ASSERT_TRUE(node.hasField("b"));
+}
+
+TEST(QuerySolutionTest, IndexScanNodeHasFieldExcludesStringFieldWhenIndexHasCollator) {
+ IndexScanNode node{IndexEntry(BSON("a" << 1 << "b" << 1))};
+ CollatorInterfaceMock indexCollator(CollatorInterfaceMock::MockType::kReverseString);
+ node.index.collator = &indexCollator;
+
+ OrderedIntervalList oilA{};
+ oilA.name = "a";
+ oilA.intervals.push_back(
+ IndexBoundsBuilder::makeRangeInterval(BSON("" << 1 << "" << 2), true, true));
+ node.bounds.fields.push_back(oilA);
+
+ OrderedIntervalList oilB{};
+ oilB.name = "b";
+ oilB.intervals.push_back(IndexBoundsBuilder::makeRangeInterval(BSON(""
+ << "bar"
+ << ""
+ << "foo"),
+ true,
+ false));
+ node.bounds.fields.push_back(oilB);
+
+ ASSERT_TRUE(node.hasField("a"));
+ ASSERT_FALSE(node.hasField("b"));
+}
+
+TEST(QuerySolutionTest, IndexScanNodeHasFieldExcludesSimpleBoundsStringFieldWhenIndexHasCollator) {
+ IndexScanNode node{IndexEntry(BSON("a" << 1 << "b" << 1))};
+ CollatorInterfaceMock indexCollator(CollatorInterfaceMock::MockType::kReverseString);
+ node.index.collator = &indexCollator;
+
+ node.bounds.isSimpleRange = true;
+ node.bounds.startKey = BSON("a" << 1 << "b" << 2);
+ node.bounds.endKey = BSON("a" << 2 << "b" << 1);
+ node.bounds.endKeyInclusive = false;
+
+ ASSERT_TRUE(node.hasField("a"));
+ ASSERT_FALSE(node.hasField("b"));
+}
+
std::unique_ptr<ParsedProjection> createParsedProjection(const BSONObj& query,
const BSONObj& projObj) {
const CollatorInterface* collator = nullptr;