From e7b0ba5c67df867bfb31bd6028b41631f04164a6 Mon Sep 17 00:00:00 2001 From: David Storch Date: Fri, 12 Aug 2016 12:10:32 -0400 Subject: SERVER-23093 avoid extra predicate evaluation for indexed collation-aware queries --- src/mongo/db/query/index_bounds_builder.cpp | 33 +----- src/mongo/db/query/index_bounds_builder_test.cpp | 28 ++--- .../db/query/query_planner_collation_test.cpp | 126 +++++++++++++++++++-- .../db/query/query_planner_partialidx_test.cpp | 2 +- src/mongo/db/query/query_solution.cpp | 9 ++ src/mongo/db/query/query_solution_test.cpp | 73 ++++++++++++ 6 files changed, 217 insertions(+), 54 deletions(-) (limited to 'src') 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 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 createParsedProjection(const BSONObj& query, const BSONObj& projObj) { const CollatorInterface* collator = nullptr; -- cgit v1.2.1