diff options
author | Judah Schvimer <judah@mongodb.com> | 2016-09-15 10:20:59 -0400 |
---|---|---|
committer | Judah Schvimer <judah@mongodb.com> | 2016-09-15 10:20:59 -0400 |
commit | 4dc41d135737dbda0a9ecc70af75687dd5df9099 (patch) | |
tree | 381dcdf024db4da65d20051a430a4f7515c122d6 /src/mongo/db | |
parent | 97dd4a8eebfb2a2dc2e3a5e0907c8fc6e859c0ac (diff) | |
download | mongo-4dc41d135737dbda0a9ecc70af75687dd5df9099.tar.gz |
SERVER-26033 Allow simple range to exclude start key
Diffstat (limited to 'src/mongo/db')
30 files changed, 357 insertions, 198 deletions
diff --git a/src/mongo/db/commands/dbcommands.cpp b/src/mongo/db/commands/dbcommands.cpp index 3e8ce384741..9720775b0cf 100644 --- a/src/mongo/db/commands/dbcommands.cpp +++ b/src/mongo/db/commands/dbcommands.cpp @@ -834,7 +834,7 @@ public: idx, min, max, - false, // endKeyInclusive + BoundInclusion::kIncludeStartKeyOnly, PlanExecutor::YIELD_MANUAL); } diff --git a/src/mongo/db/commands/dbhash.cpp b/src/mongo/db/commands/dbhash.cpp index 045376922a8..acfa7435c72 100644 --- a/src/mongo/db/commands/dbhash.cpp +++ b/src/mongo/db/commands/dbhash.cpp @@ -224,7 +224,7 @@ private: desc, BSONObj(), BSONObj(), - false, // endKeyInclusive + BoundInclusion::kIncludeStartKeyOnly, PlanExecutor::YIELD_MANUAL, InternalPlanner::FORWARD, InternalPlanner::IXSCAN_FETCH); diff --git a/src/mongo/db/dbhelpers.cpp b/src/mongo/db/dbhelpers.cpp index 53e73c11119..82fb612ddf8 100644 --- a/src/mongo/db/dbhelpers.cpp +++ b/src/mongo/db/dbhelpers.cpp @@ -295,7 +295,7 @@ BSONObj Helpers::inferKeyPattern(const BSONObj& o) { long long Helpers::removeRange(OperationContext* txn, const KeyRange& range, - bool maxInclusive, + BoundInclusion boundInclusion, const WriteConcernOptions& writeConcern, RemoveSaver* callback, bool fromMigrate, @@ -337,10 +337,12 @@ long long Helpers::removeRange(OperationContext* txn, // Extend bounds to match the index we found + invariant(IndexBounds::isStartIncludedInBound(boundInclusion)); // Extend min to get (min, MinKey, MinKey, ....) min = Helpers::toKeyFormat(indexKeyPattern.extendRangeBound(range.minKey, false)); // If upper bound is included, extend max to get (max, MaxKey, MaxKey, ...) // If not included, extend max to get (max, MinKey, MinKey, ....) + const bool maxInclusive = IndexBounds::isEndIncludedInBound(boundInclusion); max = Helpers::toKeyFormat(indexKeyPattern.extendRangeBound(range.maxKey, maxInclusive)); } @@ -369,7 +371,7 @@ long long Helpers::removeRange(OperationContext* txn, desc, min, max, - maxInclusive, + boundInclusion, PlanExecutor::YIELD_MANUAL, InternalPlanner::FORWARD, InternalPlanner::IXSCAN_FETCH)); diff --git a/src/mongo/db/dbhelpers.h b/src/mongo/db/dbhelpers.h index f3acb9f190c..bb303a681c4 100644 --- a/src/mongo/db/dbhelpers.h +++ b/src/mongo/db/dbhelpers.h @@ -173,7 +173,7 @@ struct Helpers { */ static long long removeRange(OperationContext* txn, const KeyRange& range, - bool maxInclusive, + BoundInclusion boundInclusion, const WriteConcernOptions& secondaryThrottle, RemoveSaver* callback = NULL, bool fromMigrate = false, diff --git a/src/mongo/db/exec/geo_near.cpp b/src/mongo/db/exec/geo_near.cpp index 7e7d00fc8ec..56b10994c3c 100644 --- a/src/mongo/db/exec/geo_near.cpp +++ b/src/mongo/db/exec/geo_near.cpp @@ -331,7 +331,8 @@ void GeoNear2DStage::DensityEstimator::buildIndexScan(OperationContext* txn, mongo::BSONObjBuilder builder; it->appendHashMin(&builder, ""); it->appendHashMax(&builder, ""); - oil.intervals.push_back(IndexBoundsBuilder::makeRangeInterval(builder.obj(), true, true)); + oil.intervals.push_back(IndexBoundsBuilder::makeRangeInterval( + builder.obj(), BoundInclusion::kIncludeBothStartAndEndKeys)); } invariant(oil.isValidFor(1)); diff --git a/src/mongo/db/exec/index_scan.cpp b/src/mongo/db/exec/index_scan.cpp index 7e0cf847dc3..767539e65b4 100644 --- a/src/mongo/db/exec/index_scan.cpp +++ b/src/mongo/db/exec/index_scan.cpp @@ -71,7 +71,8 @@ IndexScan::IndexScan(OperationContext* txn, _shouldDedup(true), _forward(params.direction == 1), _params(params), - _endKeyInclusive(false) { + _startKeyInclusive(IndexBounds::isStartIncludedInBound(params.bounds.boundInclusion)), + _endKeyInclusive(IndexBounds::isEndIncludedInBound(params.bounds.boundInclusion)) { // We can't always access the descriptor in the call to getStats() so we pull // any info we need for stats reporting out here. _specificStats.keyPattern = _keyPattern; @@ -104,20 +105,18 @@ boost::optional<IndexKeyEntry> IndexScan::initIndexScan() { if (_params.bounds.isSimpleRange) { // Start at one key, end at another. + _startKey = _params.bounds.startKey; _endKey = _params.bounds.endKey; - _endKeyInclusive = _params.bounds.endKeyInclusive; _indexCursor->setEndPosition(_endKey, _endKeyInclusive); - return _indexCursor->seek(_params.bounds.startKey, /*inclusive*/ true); + return _indexCursor->seek(_startKey, _startKeyInclusive); } else { // For single intervals, we can use an optimized scan which checks against the position // of an end cursor. For all other index scans, we fall back on using // IndexBoundsChecker to determine when we've finished the scan. - BSONObj startKey; - bool startKeyInclusive; if (IndexBoundsBuilder::isSingleInterval( - _params.bounds, &startKey, &startKeyInclusive, &_endKey, &_endKeyInclusive)) { + _params.bounds, &_startKey, &_startKeyInclusive, &_endKey, &_endKeyInclusive)) { _indexCursor->setEndPosition(_endKey, _endKeyInclusive); - return _indexCursor->seek(startKey, startKeyInclusive); + return _indexCursor->seek(_startKey, _startKeyInclusive); } else { _checker.reset(new IndexBoundsChecker(&_params.bounds, _keyPattern, _params.direction)); @@ -154,6 +153,15 @@ PlanStage::StageState IndexScan::doWork(WorkingSetID* out) { if (kv) { // In debug mode, check that the cursor isn't lying to us. + if (kDebugBuild && !_startKey.isEmpty()) { + int cmp = kv->key.woCompare(_startKey, + Ordering::make(_params.descriptor->keyPattern()), + /*compareFieldNames*/ false); + if (cmp == 0) + dassert(_startKeyInclusive); + dassert(_forward ? cmp >= 0 : cmp <= 0); + } + if (kDebugBuild && !_endKey.isEmpty()) { int cmp = kv->key.woCompare(_endKey, Ordering::make(_params.descriptor->keyPattern()), diff --git a/src/mongo/db/exec/index_scan.h b/src/mongo/db/exec/index_scan.h index c6f43992d47..1cf2780f24b 100644 --- a/src/mongo/db/exec/index_scan.h +++ b/src/mongo/db/exec/index_scan.h @@ -165,9 +165,13 @@ private: // BSON compares against scanned keys. In this case _checker will be NULL. // + // The key that the index cursor should start on/after. + BSONObj _startKey; // The key that the index cursor should stop on/after. BSONObj _endKey; + // Is the start key included in the range? + bool _startKeyInclusive; // Is the end key included in the range? bool _endKeyInclusive; }; diff --git a/src/mongo/db/exec/stagedebug_cmd.cpp b/src/mongo/db/exec/stagedebug_cmd.cpp index 13da0300c94..926cca4c485 100644 --- a/src/mongo/db/exec/stagedebug_cmd.cpp +++ b/src/mongo/db/exec/stagedebug_cmd.cpp @@ -306,7 +306,8 @@ public: params.bounds.isSimpleRange = true; params.bounds.startKey = stripFieldNames(nodeArgs["startKey"].Obj()); params.bounds.endKey = stripFieldNames(nodeArgs["endKey"].Obj()); - params.bounds.endKeyInclusive = nodeArgs["endKeyInclusive"].Bool(); + params.bounds.boundInclusion = IndexBounds::makeBoundInclusionFromBoundBools( + nodeArgs["startKeyInclusive"].Bool(), nodeArgs["endKeyInclusive"].Bool()); params.direction = nodeArgs["direction"].numberInt(); return new IndexScan(txn, params, workingSet, matcher); diff --git a/src/mongo/db/exec/text.cpp b/src/mongo/db/exec/text.cpp index ddbcc1d9a46..a290b8f3095 100644 --- a/src/mongo/db/exec/text.cpp +++ b/src/mongo/db/exec/text.cpp @@ -105,7 +105,7 @@ unique_ptr<PlanStage> TextStage::buildTextTree(OperationContext* txn, MAX_WEIGHT, term, _params.indexPrefix, _params.spec.getTextIndexVersion()); ixparams.bounds.endKey = FTSIndexFormat::getIndexKey( 0, term, _params.indexPrefix, _params.spec.getTextIndexVersion()); - ixparams.bounds.endKeyInclusive = true; + ixparams.bounds.boundInclusion = BoundInclusion::kIncludeBothStartAndEndKeys; ixparams.bounds.isSimpleRange = true; ixparams.descriptor = _params.index; ixparams.direction = -1; diff --git a/src/mongo/db/index/haystack_access_method.cpp b/src/mongo/db/index/haystack_access_method.cpp index 95822b21dc8..3322a6f0aa9 100644 --- a/src/mongo/db/index/haystack_access_method.cpp +++ b/src/mongo/db/index/haystack_access_method.cpp @@ -110,13 +110,14 @@ void HaystackAccessMethod::searchCommand(OperationContext* txn, unordered_set<RecordId, RecordId::Hasher> thisPass; - unique_ptr<PlanExecutor> exec(InternalPlanner::indexScan(txn, - collection, - _descriptor, - key, - key, - true, // endKeyInclusive - PlanExecutor::YIELD_MANUAL)); + unique_ptr<PlanExecutor> exec( + InternalPlanner::indexScan(txn, + collection, + _descriptor, + key, + key, + BoundInclusion::kIncludeBothStartAndEndKeys, + PlanExecutor::YIELD_MANUAL)); PlanExecutor::ExecState state; BSONObj obj; RecordId loc; diff --git a/src/mongo/db/query/expression_index.cpp b/src/mongo/db/query/expression_index.cpp index 45617f35a89..f2e1439610a 100644 --- a/src/mongo/db/query/expression_index.cpp +++ b/src/mongo/db/query/expression_index.cpp @@ -95,8 +95,8 @@ void ExpressionMapping::GeoHashsToIntervalsWithParents( geoHash.appendHashMin(&builder, ""); geoHash.appendHashMax(&builder, ""); - oilOut->intervals.push_back( - IndexBoundsBuilder::makeRangeInterval(builder.obj(), true, true)); + oilOut->intervals.push_back(IndexBoundsBuilder::makeRangeInterval( + builder.obj(), BoundInclusion::kIncludeBothStartAndEndKeys)); } } @@ -152,7 +152,8 @@ void S2CellIdsToIntervalsUnsorted(const std::vector<S2CellId>& intervalSet, b.append("start", start); b.append("end", end); invariant(start <= end); - oilOut->intervals.push_back(IndexBoundsBuilder::makeRangeInterval(b.obj(), true, true)); + oilOut->intervals.push_back(IndexBoundsBuilder::makeRangeInterval( + b.obj(), BoundInclusion::kIncludeBothStartAndEndKeys)); } else { // for backwards compatibility, use strings std::string start = interval.toString(); @@ -160,8 +161,8 @@ void S2CellIdsToIntervalsUnsorted(const std::vector<S2CellId>& intervalSet, end[start.size() - 1]++; b.append("start", start); b.append("end", end); - oilOut->intervals.push_back( - IndexBoundsBuilder::makeRangeInterval(b.obj(), true, false)); + oilOut->intervals.push_back(IndexBoundsBuilder::makeRangeInterval( + b.obj(), BoundInclusion::kIncludeStartKeyOnly)); } } } diff --git a/src/mongo/db/query/index_bounds.cpp b/src/mongo/db/query/index_bounds.cpp index 065d53ba510..ef8eeb6d8b8 100644 --- a/src/mongo/db/query/index_bounds.cpp +++ b/src/mongo/db/query/index_bounds.cpp @@ -104,7 +104,7 @@ bool IndexBounds::operator==(const IndexBounds& other) const { if (this->isSimpleRange) { return SimpleBSONObjComparator::kInstance.evaluate(this->startKey == other.startKey) && SimpleBSONObjComparator::kInstance.evaluate(this->endKey == other.endKey) && - (this->endKeyInclusive == other.endKeyInclusive); + (this->boundInclusion == other.boundInclusion); } if (this->fields.size() != other.fields.size()) { @@ -136,6 +136,34 @@ string OrderedIntervalList::toString() const { return ss; } +bool IndexBounds::isStartIncludedInBound(BoundInclusion boundInclusion) { + return boundInclusion == BoundInclusion::kIncludeBothStartAndEndKeys || + boundInclusion == BoundInclusion::kIncludeStartKeyOnly; +} + +bool IndexBounds::isEndIncludedInBound(BoundInclusion boundInclusion) { + return boundInclusion == BoundInclusion::kIncludeBothStartAndEndKeys || + boundInclusion == BoundInclusion::kIncludeEndKeyOnly; +} + +BoundInclusion IndexBounds::makeBoundInclusionFromBoundBools(bool startKeyInclusive, + bool endKeyInclusive) { + if (startKeyInclusive) { + if (endKeyInclusive) { + return BoundInclusion::kIncludeBothStartAndEndKeys; + } else { + return BoundInclusion::kIncludeStartKeyOnly; + } + } else { + if (endKeyInclusive) { + return BoundInclusion::kIncludeEndKeyOnly; + } else { + return BoundInclusion::kExcludeBothStartAndEndKeys; + } + } +} + + bool OrderedIntervalList::operator==(const OrderedIntervalList& other) const { if (this->name != other.name) { return false; @@ -216,12 +244,17 @@ void OrderedIntervalList::complement() { string IndexBounds::toString() const { mongoutils::str::stream ss; if (isSimpleRange) { - ss << "[" << startKey.toString() << ", "; + if (IndexBounds::isStartIncludedInBound(boundInclusion)) { + ss << "["; + } else { + ss << "("; + } + ss << startKey.toString() << ", "; if (endKey.isEmpty()) { ss << "]"; } else { ss << endKey.toString(); - if (endKeyInclusive) { + if (IndexBounds::isEndIncludedInBound(boundInclusion)) { ss << "]"; } else { ss << ")"; diff --git a/src/mongo/db/query/index_bounds.h b/src/mongo/db/query/index_bounds.h index e3fabde17b7..2dfcc122b26 100644 --- a/src/mongo/db/query/index_bounds.h +++ b/src/mongo/db/query/index_bounds.h @@ -37,6 +37,13 @@ namespace mongo { +enum class BoundInclusion { + kExcludeBothStartAndEndKeys, + kIncludeStartKeyOnly, + kIncludeEndKeyOnly, + kIncludeBothStartAndEndKeys +}; + /** * An ordered list of intervals for one field. */ @@ -74,7 +81,7 @@ struct OrderedIntervalList { * interpret. Previously known as FieldRangeVector. */ struct IndexBounds { - IndexBounds() : isSimpleRange(false), endKeyInclusive(false) {} + IndexBounds() : isSimpleRange(false), boundInclusion(BoundInclusion::kIncludeStartKeyOnly) {} // For each indexed field, the values that the field is allowed to take on. std::vector<OrderedIntervalList> fields; @@ -101,6 +108,26 @@ struct IndexBounds { bool operator!=(const IndexBounds& other) const; /** + * Returns if the start key should be included in the bounds specified by the given + * BoundInclusion. + */ + static bool isStartIncludedInBound(BoundInclusion boundInclusion); + + /** + * Returns if the end key should be included in the bounds specified by the given + * BoundInclusion. + */ + static bool isEndIncludedInBound(BoundInclusion boundInclusion); + + /** + * Returns a BoundInclusion given two booleans of whether to included the start key and the end + * key. + */ + static BoundInclusion makeBoundInclusionFromBoundBools(bool startKeyInclusive, + bool endKeyInclusive); + + + /** * BSON format for explain. The format is an array of strings for each field. * Each string represents an interval. The strings use "[" and "]" if the interval * bounds are inclusive, and "(" / ")" if exclusive. @@ -114,7 +141,7 @@ struct IndexBounds { bool isSimpleRange; BSONObj startKey; BSONObj endKey; - bool endKeyInclusive; + BoundInclusion boundInclusion; }; /** diff --git a/src/mongo/db/query/index_bounds_builder.cpp b/src/mongo/db/query/index_bounds_builder.cpp index 21e56b4f42b..2268e3ef30c 100644 --- a/src/mongo/db/query/index_bounds_builder.cpp +++ b/src/mongo/db/query/index_bounds_builder.cpp @@ -186,14 +186,15 @@ void IndexBoundsBuilder::allValuesForField(const BSONElement& elt, OrderedInterv bob.appendMinKey(""); bob.appendMaxKey(""); out->name = elt.fieldName(); - out->intervals.push_back(makeRangeInterval(bob.obj(), true, true)); + out->intervals.push_back( + makeRangeInterval(bob.obj(), BoundInclusion::kIncludeBothStartAndEndKeys)); } Interval IndexBoundsBuilder::allValues() { BSONObjBuilder bob; bob.appendMinKey(""); bob.appendMaxKey(""); - return makeRangeInterval(bob.obj(), true, true); + return makeRangeInterval(bob.obj(), BoundInclusion::kIncludeBothStartAndEndKeys); } bool IntervalComparison(const Interval& lhs, const Interval& rhs) { @@ -310,7 +311,8 @@ void IndexBoundsBuilder::translate(const MatchExpression* expr, bob.appendNull(""); bob.appendNull(""); BSONObj dataObj = bob.obj(); - oilOut->intervals.push_back(makeRangeInterval(dataObj, true, true)); + oilOut->intervals.push_back( + makeRangeInterval(dataObj, BoundInclusion::kIncludeBothStartAndEndKeys)); *tightnessOut = IndexBoundsBuilder::INEXACT_FETCH; return; @@ -393,7 +395,8 @@ void IndexBoundsBuilder::translate(const MatchExpression* expr, CollationIndexKey::collationAwareIndexKeyAppend(dataElt, index.collator, &bob); BSONObj dataObj = bob.obj(); verify(dataObj.isOwned()); - oilOut->intervals.push_back(makeRangeInterval(dataObj, typeMatch(dataObj), true)); + oilOut->intervals.push_back(makeRangeInterval( + dataObj, IndexBounds::makeBoundInclusionFromBoundBools(typeMatch(dataObj), true))); *tightnessOut = getInequalityPredicateTightness(dataElt, index); } else if (MatchExpression::LT == expr->matchType()) { @@ -424,7 +427,8 @@ void IndexBoundsBuilder::translate(const MatchExpression* expr, CollationIndexKey::collationAwareIndexKeyAppend(dataElt, index.collator, &bob); BSONObj dataObj = bob.obj(); verify(dataObj.isOwned()); - Interval interval = makeRangeInterval(dataObj, typeMatch(dataObj), false); + Interval interval = makeRangeInterval( + dataObj, IndexBounds::makeBoundInclusionFromBoundBools(typeMatch(dataObj), false)); // If the operand to LT is equal to the lower bound X, the interval [X, X) is invalid // and should not be added to the bounds. @@ -460,7 +464,8 @@ void IndexBoundsBuilder::translate(const MatchExpression* expr, } BSONObj dataObj = bob.obj(); verify(dataObj.isOwned()); - Interval interval = makeRangeInterval(dataObj, false, typeMatch(dataObj)); + Interval interval = makeRangeInterval( + dataObj, IndexBounds::makeBoundInclusionFromBoundBools(false, typeMatch(dataObj))); // If the operand to GT is equal to the upper bound X, the interval (X, X] is invalid // and should not be added to the bounds. @@ -499,7 +504,8 @@ void IndexBoundsBuilder::translate(const MatchExpression* expr, BSONObj dataObj = bob.obj(); verify(dataObj.isOwned()); - oilOut->intervals.push_back(makeRangeInterval(dataObj, true, typeMatch(dataObj))); + oilOut->intervals.push_back(makeRangeInterval( + dataObj, IndexBounds::makeBoundInclusionFromBoundBools(true, typeMatch(dataObj)))); *tightnessOut = getInequalityPredicateTightness(dataElt, index); } else if (MatchExpression::REGEX == expr->matchType()) { @@ -511,7 +517,8 @@ void IndexBoundsBuilder::translate(const MatchExpression* expr, bob.appendMaxForType("", NumberDouble); BSONObj dataObj = bob.obj(); verify(dataObj.isOwned()); - oilOut->intervals.push_back(makeRangeInterval(dataObj, true, true)); + oilOut->intervals.push_back( + makeRangeInterval(dataObj, BoundInclusion::kIncludeBothStartAndEndKeys)); *tightnessOut = IndexBoundsBuilder::INEXACT_COVERED; } else if (MatchExpression::TYPE_OPERATOR == expr->matchType()) { const TypeMatchExpression* tme = static_cast<const TypeMatchExpression*>(expr); @@ -524,7 +531,8 @@ void IndexBoundsBuilder::translate(const MatchExpression* expr, bob.appendMaxForType("", type); BSONObj dataObj = bob.obj(); verify(dataObj.isOwned()); - oilOut->intervals.push_back(makeRangeInterval(dataObj, true, true)); + oilOut->intervals.push_back( + makeRangeInterval(dataObj, BoundInclusion::kIncludeBothStartAndEndKeys)); *tightnessOut = tme->matchesAllNumbers() ? IndexBoundsBuilder::EXACT : IndexBoundsBuilder::INEXACT_FETCH; @@ -597,13 +605,11 @@ void IndexBoundsBuilder::translate(const MatchExpression* expr, } // static -Interval IndexBoundsBuilder::makeRangeInterval(const BSONObj& obj, - bool startInclusive, - bool endInclusive) { +Interval IndexBoundsBuilder::makeRangeInterval(const BSONObj& obj, BoundInclusion boundInclusion) { Interval ret; ret._intervalData = obj; - ret.startInclusive = startInclusive; - ret.endInclusive = endInclusive; + ret.startInclusive = IndexBounds::isStartIncludedInBound(boundInclusion); + ret.endInclusive = IndexBounds::isEndIncludedInBound(boundInclusion); BSONObjIterator it(obj); verify(it.more()); ret.start = it.next(); @@ -705,7 +711,8 @@ void IndexBoundsBuilder::unionize(OrderedIntervalList* oilOut) { bool endInclusive = iv[i + 1].endInclusive; iv.erase(iv.begin() + i); // iv[i] is now the former iv[i + 1] - iv[i] = makeRangeInterval(data, startInclusive, endInclusive); + iv[i] = makeRangeInterval( + data, IndexBounds::makeBoundInclusionFromBoundBools(startInclusive, endInclusive)); // Don't increment 'i'. } } @@ -714,12 +721,11 @@ void IndexBoundsBuilder::unionize(OrderedIntervalList* oilOut) { // static Interval IndexBoundsBuilder::makeRangeInterval(const string& start, const string& end, - bool startInclusive, - bool endInclusive) { + BoundInclusion boundInclusion) { BSONObjBuilder bob; bob.append("", start); bob.append("", end); - return makeRangeInterval(bob.obj(), startInclusive, endInclusive); + return makeRangeInterval(bob.obj(), boundInclusion); } // static @@ -776,14 +782,16 @@ void IndexBoundsBuilder::translateRegex(const RegexMatchExpression* rme, if (!start.empty()) { string end = start; end[end.size() - 1]++; - oilOut->intervals.push_back(makeRangeInterval(start, end, true, false)); + oilOut->intervals.push_back( + makeRangeInterval(start, end, BoundInclusion::kIncludeStartKeyOnly)); } else { BSONObjBuilder bob; bob.appendMinForType("", String); bob.appendMaxForType("", String); BSONObj dataObj = bob.obj(); verify(dataObj.isOwned()); - oilOut->intervals.push_back(makeRangeInterval(dataObj, true, false)); + oilOut->intervals.push_back( + makeRangeInterval(dataObj, BoundInclusion::kIncludeStartKeyOnly)); } // Regexes are after strings. diff --git a/src/mongo/db/query/index_bounds_builder.h b/src/mongo/db/query/index_bounds_builder.h index 5d3c02e029f..b397f81b295 100644 --- a/src/mongo/db/query/index_bounds_builder.h +++ b/src/mongo/db/query/index_bounds_builder.h @@ -108,15 +108,14 @@ public: * Make a range interval from the provided object. * The object must have exactly two fields. The first field is the start, the second the * end. - * The two inclusive flags indicate whether or not the start/end fields are included in the + * The BoundInclusion indicates whether or not the start/end fields are included in the * interval (closed interval if included, open if not). */ - static Interval makeRangeInterval(const BSONObj& obj, bool startInclusive, bool endInclusive); + static Interval makeRangeInterval(const BSONObj& obj, BoundInclusion boundInclusion); static Interval makeRangeInterval(const std::string& start, const std::string& end, - bool startInclusive, - bool endInclusive); + BoundInclusion boundInclusion); /** * Make a point interval from the provided object. diff --git a/src/mongo/db/query/index_bounds_test.cpp b/src/mongo/db/query/index_bounds_test.cpp index 250563b54ce..1fca089e584 100644 --- a/src/mongo/db/query/index_bounds_test.cpp +++ b/src/mongo/db/query/index_bounds_test.cpp @@ -298,13 +298,13 @@ TEST(IndexBoundsTest, SimpleRangeBoundsEqual) { bounds1.isSimpleRange = true; bounds1.startKey = BSON("" << 1 << "" << 3); bounds1.endKey = BSON("" << 2 << "" << 4); - bounds1.endKeyInclusive = false; + bounds1.boundInclusion = BoundInclusion::kIncludeStartKeyOnly; IndexBounds bounds2; bounds2.isSimpleRange = true; bounds2.startKey = BSON("" << 1 << "" << 3); bounds2.endKey = BSON("" << 2 << "" << 4); - bounds2.endKeyInclusive = false; + bounds2.boundInclusion = BoundInclusion::kIncludeStartKeyOnly; ASSERT_TRUE(bounds1 == bounds2); ASSERT_FALSE(bounds1 != bounds2); @@ -457,7 +457,7 @@ TEST(IndexBoundsTest, SimpleRangeBoundsNotEqualToRegularBounds) { bounds1.isSimpleRange = true; bounds1.startKey = BSON("" << 1 << "" << 3); bounds1.endKey = BSON("" << 2 << "" << 4); - bounds1.endKeyInclusive = false; + bounds1.boundInclusion = BoundInclusion::kIncludeStartKeyOnly; IndexBounds bounds2; OrderedIntervalList oil; @@ -473,13 +473,13 @@ TEST(IndexBoundsTest, SimpleRangeBoundsNotEqualDifferentStartKey) { bounds1.isSimpleRange = true; bounds1.startKey = BSON("" << 1 << "" << 3); bounds1.endKey = BSON("" << 2 << "" << 4); - bounds1.endKeyInclusive = false; + bounds1.boundInclusion = BoundInclusion::kIncludeStartKeyOnly; IndexBounds bounds2; bounds1.isSimpleRange = true; bounds1.startKey = BSON("" << 1 << "" << 1); bounds1.endKey = BSON("" << 2 << "" << 4); - bounds1.endKeyInclusive = false; + bounds1.boundInclusion = BoundInclusion::kIncludeStartKeyOnly; ASSERT_FALSE(bounds1 == bounds2); ASSERT_TRUE(bounds1 != bounds2); @@ -490,13 +490,13 @@ TEST(IndexBoundsTest, SimpleRangeBoundsNotEqualDifferentEndKey) { bounds1.isSimpleRange = true; bounds1.startKey = BSON("" << 1 << "" << 3); bounds1.endKey = BSON("" << 2 << "" << 4); - bounds1.endKeyInclusive = false; + bounds1.boundInclusion = BoundInclusion::kIncludeStartKeyOnly; IndexBounds bounds2; bounds1.isSimpleRange = true; bounds1.startKey = BSON("" << 1 << "" << 3); bounds1.endKey = BSON("" << 2 << "" << 99); - bounds1.endKeyInclusive = false; + bounds1.boundInclusion = BoundInclusion::kIncludeStartKeyOnly; ASSERT_FALSE(bounds1 == bounds2); ASSERT_TRUE(bounds1 != bounds2); @@ -507,13 +507,13 @@ TEST(IndexBoundsTest, SimpleRangeBoundsNotEqualDifferentEndKeyInclusive) { bounds1.isSimpleRange = true; bounds1.startKey = BSON("" << 1 << "" << 3); bounds1.endKey = BSON("" << 2 << "" << 4); - bounds1.endKeyInclusive = false; + bounds1.boundInclusion = BoundInclusion::kIncludeStartKeyOnly; IndexBounds bounds2; bounds1.isSimpleRange = true; bounds1.startKey = BSON("" << 1 << "" << 3); bounds1.endKey = BSON("" << 2 << "" << 4); - bounds1.endKeyInclusive = true; + bounds1.boundInclusion = BoundInclusion::kIncludeBothStartAndEndKeys; ASSERT_FALSE(bounds1 == bounds2); ASSERT_TRUE(bounds1 != bounds2); diff --git a/src/mongo/db/query/internal_plans.cpp b/src/mongo/db/query/internal_plans.cpp index b0ebdcd1096..3889e82031b 100644 --- a/src/mongo/db/query/internal_plans.cpp +++ b/src/mongo/db/query/internal_plans.cpp @@ -94,7 +94,7 @@ std::unique_ptr<PlanExecutor> InternalPlanner::indexScan(OperationContext* txn, const IndexDescriptor* descriptor, const BSONObj& startKey, const BSONObj& endKey, - bool endKeyInclusive, + BoundInclusion boundInclusion, PlanExecutor::YieldPolicy yieldPolicy, Direction direction, int options) { @@ -106,7 +106,7 @@ std::unique_ptr<PlanExecutor> InternalPlanner::indexScan(OperationContext* txn, descriptor, startKey, endKey, - endKeyInclusive, + boundInclusion, direction, options); @@ -123,7 +123,7 @@ std::unique_ptr<PlanExecutor> InternalPlanner::deleteWithIndexScan( const IndexDescriptor* descriptor, const BSONObj& startKey, const BSONObj& endKey, - bool endKeyInclusive, + BoundInclusion boundInclusion, PlanExecutor::YieldPolicy yieldPolicy, Direction direction) { auto ws = stdx::make_unique<WorkingSet>(); @@ -134,7 +134,7 @@ std::unique_ptr<PlanExecutor> InternalPlanner::deleteWithIndexScan( descriptor, startKey, endKey, - endKeyInclusive, + boundInclusion, direction, InternalPlanner::IXSCAN_FETCH); @@ -172,7 +172,7 @@ std::unique_ptr<PlanStage> InternalPlanner::_indexScan(OperationContext* txn, const IndexDescriptor* descriptor, const BSONObj& startKey, const BSONObj& endKey, - bool endKeyInclusive, + BoundInclusion boundInclusion, Direction direction, int options) { invariant(collection); @@ -184,7 +184,7 @@ std::unique_ptr<PlanStage> InternalPlanner::_indexScan(OperationContext* txn, params.bounds.isSimpleRange = true; params.bounds.startKey = startKey; params.bounds.endKey = endKey; - params.bounds.endKeyInclusive = endKeyInclusive; + params.bounds.boundInclusion = boundInclusion; std::unique_ptr<PlanStage> root = stdx::make_unique<IndexScan>(txn, params, ws, nullptr); diff --git a/src/mongo/db/query/internal_plans.h b/src/mongo/db/query/internal_plans.h index 7c98df487aa..40b42f75df1 100644 --- a/src/mongo/db/query/internal_plans.h +++ b/src/mongo/db/query/internal_plans.h @@ -92,7 +92,7 @@ public: const IndexDescriptor* descriptor, const BSONObj& startKey, const BSONObj& endKey, - bool endKeyInclusive, + BoundInclusion boundInclusion, PlanExecutor::YieldPolicy yieldPolicy, Direction direction = FORWARD, int options = IXSCAN_DEFAULT); @@ -106,7 +106,7 @@ public: const IndexDescriptor* descriptor, const BSONObj& startKey, const BSONObj& endKey, - bool endKeyInclusive, + BoundInclusion boundInclusion, PlanExecutor::YieldPolicy yieldPolicy, Direction direction = FORWARD); @@ -133,7 +133,7 @@ private: const IndexDescriptor* descriptor, const BSONObj& startKey, const BSONObj& endKey, - bool endKeyInclusive, + BoundInclusion boundInclusion, Direction direction = FORWARD, int options = IXSCAN_DEFAULT); }; diff --git a/src/mongo/db/query/planner_access.cpp b/src/mongo/db/query/planner_access.cpp index b221210c035..7867b7c0161 100644 --- a/src/mongo/db/query/planner_access.cpp +++ b/src/mongo/db/query/planner_access.cpp @@ -1384,7 +1384,7 @@ QuerySolutionNode* QueryPlannerAccess::makeIndexScan(const IndexEntry& index, isn->bounds.isSimpleRange = true; isn->bounds.startKey = startKey; isn->bounds.endKey = endKey; - isn->bounds.endKeyInclusive = false; + isn->bounds.boundInclusion = BoundInclusion::kIncludeStartKeyOnly; isn->queryCollator = query.getCollator(); unique_ptr<MatchExpression> filter = query.root()->shallowClone(); diff --git a/src/mongo/db/query/query_planner_common.cpp b/src/mongo/db/query/query_planner_common.cpp index 8f129e54e5b..33eb72100cc 100644 --- a/src/mongo/db/query/query_planner_common.cpp +++ b/src/mongo/db/query/query_planner_common.cpp @@ -45,9 +45,19 @@ void QueryPlannerCommon::reverseScans(QuerySolutionNode* node) { if (isn->bounds.isSimpleRange) { std::swap(isn->bounds.startKey, isn->bounds.endKey); - // XXX: Not having a startKeyInclusive means that if we reverse a max/min query - // we have different results with and without the reverse... - isn->bounds.endKeyInclusive = true; + // If only one bound is included, swap which one is included. + switch (isn->bounds.boundInclusion) { + case BoundInclusion::kIncludeStartKeyOnly: + isn->bounds.boundInclusion = BoundInclusion::kIncludeEndKeyOnly; + break; + case BoundInclusion::kIncludeEndKeyOnly: + isn->bounds.boundInclusion = BoundInclusion::kIncludeStartKeyOnly; + break; + case BoundInclusion::kIncludeBothStartAndEndKeys: + case BoundInclusion::kExcludeBothStartAndEndKeys: + // These are both symmetric so no change needed. + break; + } } else { for (size_t i = 0; i < isn->bounds.fields.size(); ++i) { std::vector<Interval>& iv = isn->bounds.fields[i].intervals; diff --git a/src/mongo/db/query/query_solution.cpp b/src/mongo/db/query/query_solution.cpp index ed9ab70b4cc..aa2426954c4 100644 --- a/src/mongo/db/query/query_solution.cpp +++ b/src/mongo/db/query/query_solution.cpp @@ -52,31 +52,33 @@ OrderedIntervalList buildStringBoundsOil(const std::string& keyName) { BSONObjBuilder strBob; strBob.appendMinForType("", BSONType::String); strBob.appendMaxForType("", BSONType::String); - ret.intervals.push_back(IndexBoundsBuilder::makeRangeInterval(strBob.obj(), true, false)); + ret.intervals.push_back( + IndexBoundsBuilder::makeRangeInterval(strBob.obj(), BoundInclusion::kIncludeStartKeyOnly)); BSONObjBuilder objBob; objBob.appendMinForType("", BSONType::Object); objBob.appendMaxForType("", BSONType::Object); - ret.intervals.push_back(IndexBoundsBuilder::makeRangeInterval(objBob.obj(), true, false)); + ret.intervals.push_back( + IndexBoundsBuilder::makeRangeInterval(objBob.obj(), BoundInclusion::kIncludeStartKeyOnly)); BSONObjBuilder arrBob; arrBob.appendMinForType("", BSONType::Array); arrBob.appendMaxForType("", BSONType::Array); - ret.intervals.push_back(IndexBoundsBuilder::makeRangeInterval(arrBob.obj(), true, false)); + ret.intervals.push_back( + IndexBoundsBuilder::makeRangeInterval(arrBob.obj(), BoundInclusion::kIncludeStartKeyOnly)); return ret; } bool rangeCanContainString(const BSONElement& startKey, const BSONElement& endKey, - bool endKeyInclusive) { + BoundInclusion boundInclusion) { OrderedIntervalList stringBoundsOil = buildStringBoundsOil(""); OrderedIntervalList rangeOil; BSONObjBuilder bob; bob.appendAs(startKey, ""); bob.appendAs(endKey, ""); - rangeOil.intervals.push_back( - IndexBoundsBuilder::makeRangeInterval(bob.obj(), true, endKeyInclusive)); + rangeOil.intervals.push_back(IndexBoundsBuilder::makeRangeInterval(bob.obj(), boundInclusion)); IndexBoundsBuilder::intersectize(rangeOil, &stringBoundsOil); return !stringBoundsOil.intervals.empty(); @@ -606,8 +608,11 @@ std::set<StringData> IndexScanNode::getFieldsWithStringBounds(const IndexBounds& BSONElement endKey = endKeyIterator.next(); if (SimpleBSONElementComparator::kInstance.evaluate(startKey != endKey) || CollationIndexKey::isCollatableType(startKey.type())) { - if (!rangeCanContainString( - startKey, endKey, (startKeyIterator.more() || bounds.endKeyInclusive))) { + BoundInclusion boundInclusion = bounds.boundInclusion; + if (startKeyIterator.more()) { + boundInclusion = BoundInclusion::kIncludeBothStartAndEndKeys; + } + if (!rangeCanContainString(startKey, endKey, boundInclusion)) { // If the first non-point range cannot contain strings, we don't need to // add it to the return set. keyPatternIterator.next(); diff --git a/src/mongo/db/query/query_solution_test.cpp b/src/mongo/db/query/query_solution_test.cpp index a116d497661..b1f8bc18e4b 100644 --- a/src/mongo/db/query/query_solution_test.cpp +++ b/src/mongo/db/query/query_solution_test.cpp @@ -160,32 +160,32 @@ TEST(QuerySolutionTest, IntervalListNoPoints) { OrderedIntervalList a{}; a.name = "a"; - a.intervals.push_back( - IndexBoundsBuilder::makeRangeInterval(BSON("" << 1 << "" << 2), true, true)); + a.intervals.push_back(IndexBoundsBuilder::makeRangeInterval( + BSON("" << 1 << "" << 2), BoundInclusion::kIncludeBothStartAndEndKeys)); node.bounds.fields.push_back(a); OrderedIntervalList b{}; b.name = "b"; - b.intervals.push_back( - IndexBoundsBuilder::makeRangeInterval(BSON("" << 1 << "" << 2), true, true)); + b.intervals.push_back(IndexBoundsBuilder::makeRangeInterval( + BSON("" << 1 << "" << 2), BoundInclusion::kIncludeBothStartAndEndKeys)); node.bounds.fields.push_back(b); OrderedIntervalList c{}; c.name = "c"; - c.intervals.push_back( - IndexBoundsBuilder::makeRangeInterval(BSON("" << 1 << "" << 2), true, true)); + c.intervals.push_back(IndexBoundsBuilder::makeRangeInterval( + BSON("" << 1 << "" << 2), BoundInclusion::kIncludeBothStartAndEndKeys)); node.bounds.fields.push_back(c); OrderedIntervalList d{}; d.name = "d"; - d.intervals.push_back( - IndexBoundsBuilder::makeRangeInterval(BSON("" << 1 << "" << 2), true, true)); + d.intervals.push_back(IndexBoundsBuilder::makeRangeInterval( + BSON("" << 1 << "" << 2), BoundInclusion::kIncludeBothStartAndEndKeys)); node.bounds.fields.push_back(d); OrderedIntervalList e{}; e.name = "e"; - e.intervals.push_back( - IndexBoundsBuilder::makeRangeInterval(BSON("" << 1 << "" << 2), true, true)); + e.intervals.push_back(IndexBoundsBuilder::makeRangeInterval( + BSON("" << 1 << "" << 2), BoundInclusion::kIncludeBothStartAndEndKeys)); node.bounds.fields.push_back(e); node.computeProperties(); @@ -216,20 +216,20 @@ TEST(QuerySolutionTest, IntervalListSomePoints) { OrderedIntervalList c{}; c.name = "c"; - c.intervals.push_back( - IndexBoundsBuilder::makeRangeInterval(BSON("" << 1 << "" << 2), true, true)); + c.intervals.push_back(IndexBoundsBuilder::makeRangeInterval( + BSON("" << 1 << "" << 2), BoundInclusion::kIncludeBothStartAndEndKeys)); node.bounds.fields.push_back(c); OrderedIntervalList d{}; d.name = "d"; - d.intervals.push_back( - IndexBoundsBuilder::makeRangeInterval(BSON("" << 1 << "" << 2), true, true)); + d.intervals.push_back(IndexBoundsBuilder::makeRangeInterval( + BSON("" << 1 << "" << 2), BoundInclusion::kIncludeBothStartAndEndKeys)); node.bounds.fields.push_back(d); OrderedIntervalList e{}; e.name = "e"; - e.intervals.push_back( - IndexBoundsBuilder::makeRangeInterval(BSON("" << 1 << "" << 2), true, true)); + e.intervals.push_back(IndexBoundsBuilder::makeRangeInterval( + BSON("" << 1 << "" << 2), BoundInclusion::kIncludeBothStartAndEndKeys)); node.bounds.fields.push_back(e); node.computeProperties(); @@ -253,36 +253,38 @@ TEST(QuerySolutionTest, GetFieldsWithStringBoundsIdentifiesFieldsContainingStrin OrderedIntervalList oilA{}; oilA.name = "a"; - oilA.intervals.push_back( - IndexBoundsBuilder::makeRangeInterval(BSON("" << 1 << "" << 1), true, true)); + oilA.intervals.push_back(IndexBoundsBuilder::makeRangeInterval( + BSON("" << 1 << "" << 1), BoundInclusion::kIncludeBothStartAndEndKeys)); bounds.fields.push_back(oilA); OrderedIntervalList oilB{}; oilB.name = "b"; - oilB.intervals.push_back( - IndexBoundsBuilder::makeRangeInterval(BSON("" << false << "" << true), true, true)); + oilB.intervals.push_back(IndexBoundsBuilder::makeRangeInterval( + BSON("" << false << "" << true), BoundInclusion::kIncludeBothStartAndEndKeys)); bounds.fields.push_back(oilB); OrderedIntervalList oilC{}; oilC.name = "c"; - oilC.intervals.push_back(IndexBoundsBuilder::makeRangeInterval(BSON("" - << "a" - << "" - << "b"), - true, - true)); + oilC.intervals.push_back( + IndexBoundsBuilder::makeRangeInterval(BSON("" + << "a" + << "" + << "b"), + BoundInclusion::kIncludeBothStartAndEndKeys)); bounds.fields.push_back(oilC); OrderedIntervalList oilD{}; oilD.name = "d"; oilD.intervals.push_back(IndexBoundsBuilder::makeRangeInterval( - BSON("" << BSON("foo" << 1) << "" << BSON("foo" << 2)), true, true)); + BSON("" << BSON("foo" << 1) << "" << BSON("foo" << 2)), + BoundInclusion::kIncludeBothStartAndEndKeys)); bounds.fields.push_back(oilD); OrderedIntervalList oilE{}; oilE.name = "e"; oilE.intervals.push_back(IndexBoundsBuilder::makeRangeInterval( - BSON("" << BSON_ARRAY(1 << 2 << 3) << "" << BSON_ARRAY(2 << 3 << 4)), true, true)); + BSON("" << BSON_ARRAY(1 << 2 << 3) << "" << BSON_ARRAY(2 << 3 << 4)), + BoundInclusion::kIncludeBothStartAndEndKeys)); bounds.fields.push_back(oilE); auto fields = IndexScanNode::getFieldsWithStringBounds(bounds, keyPattern); @@ -300,7 +302,7 @@ TEST(QuerySolutionTest, GetFieldsWithStringBoundsIdentifiesStringsFromNonPointBo bounds.isSimpleRange = true; bounds.startKey = BSON("a" << 1 << "b" << 2 << "c" << 3 << "d" << 4 << "e" << 5); bounds.endKey = BSON("a" << 1 << "b" << 2 << "c" << 3 << "d" << 5 << "e" << 5); - bounds.endKeyInclusive = true; + bounds.boundInclusion = BoundInclusion::kIncludeBothStartAndEndKeys; auto fields = IndexScanNode::getFieldsWithStringBounds(bounds, keyPattern); ASSERT_EQUALS(fields.size(), 1U); @@ -317,7 +319,7 @@ TEST(QuerySolutionTest, GetFieldsWithStringBoundsIdentifiesStringsFromStringType bounds.isSimpleRange = true; bounds.startKey = fromjson("{'a': 1, 'b': 'a', 'c': 3, 'd': 4, 'e': 5}"); bounds.endKey = bounds.startKey; - bounds.endKeyInclusive = true; + bounds.boundInclusion = BoundInclusion::kIncludeBothStartAndEndKeys; auto fields = IndexScanNode::getFieldsWithStringBounds(bounds, keyPattern); ASSERT_EQUALS(fields.size(), 4U); @@ -334,7 +336,7 @@ TEST(QuerySolutionTest, GetFieldsWithStringBoundsIdentifiesStringsFromArrayTypeB bounds.isSimpleRange = true; bounds.startKey = fromjson("{'a': 1, 'b': [1,2], 'c': 3, 'd': 4, 'e': 5}"); bounds.endKey = bounds.startKey; - bounds.endKeyInclusive = true; + bounds.boundInclusion = BoundInclusion::kIncludeBothStartAndEndKeys; auto fields = IndexScanNode::getFieldsWithStringBounds(bounds, keyPattern); ASSERT_EQUALS(fields.size(), 4U); @@ -351,7 +353,7 @@ TEST(QuerySolutionTest, GetFieldsWithStringBoundsIdentifiesStringsFromObjectType bounds.isSimpleRange = true; bounds.startKey = fromjson("{'a': 1, 'b': {'foo': 2}, 'c': 3, 'd': 4, 'e': 5}"); bounds.endKey = bounds.startKey; - bounds.endKeyInclusive = true; + bounds.boundInclusion = BoundInclusion::kIncludeBothStartAndEndKeys; auto fields = IndexScanNode::getFieldsWithStringBounds(bounds, keyPattern); ASSERT_EQUALS(fields.size(), 4U); @@ -362,6 +364,59 @@ TEST(QuerySolutionTest, GetFieldsWithStringBoundsIdentifiesStringsFromObjectType ASSERT_TRUE(fields.count("e")); } +TEST(QuerySolutionTest, GetFieldsWithStringBoundsIdentifiesStringsWithExclusiveBounds) { + IndexBounds bounds; + BSONObj keyPattern = BSON("a" << 1 << "b" << 1); + bounds.isSimpleRange = true; + bounds.startKey = fromjson("{'a': 1, 'b': 1}"); + bounds.endKey = fromjson("{'a': 2, 'b': 2}"); + bounds.boundInclusion = BoundInclusion::kExcludeBothStartAndEndKeys; + + auto fields = IndexScanNode::getFieldsWithStringBounds(bounds, keyPattern); + ASSERT_EQUALS(fields.size(), 1U); + ASSERT_FALSE(fields.count("a")); + ASSERT_TRUE(fields.count("b")); +} + +TEST(QuerySolutionTest, GetFieldsWithStringBoundsIdentifiesStringsWithExclusiveBoundsOnBoundary) { + IndexBounds bounds; + BSONObj keyPattern = BSON("a" << 1 << "b" << 1); + bounds.isSimpleRange = true; + bounds.startKey = fromjson("{'a': 1, 'b': 1}"); + bounds.endKey = fromjson("{'a': '', 'b': 1}"); + bounds.boundInclusion = BoundInclusion::kExcludeBothStartAndEndKeys; + + auto fields = IndexScanNode::getFieldsWithStringBounds(bounds, keyPattern); + ASSERT_EQUALS(fields.size(), 2U); + ASSERT_TRUE(fields.count("a")); + ASSERT_TRUE(fields.count("b")); +} + +TEST(QuerySolutionTest, GetFieldsWithStringBoundsIdentifiesNoStringsWithEmptyExclusiveBounds) { + IndexBounds bounds; + BSONObj keyPattern = BSON("a" << 1); + bounds.isSimpleRange = true; + bounds.startKey = fromjson("{'a': 1}"); + bounds.endKey = fromjson("{'a': ''}"); + bounds.boundInclusion = BoundInclusion::kExcludeBothStartAndEndKeys; + + auto fields = IndexScanNode::getFieldsWithStringBounds(bounds, keyPattern); + ASSERT_EQUALS(fields.size(), 0U); +} + +TEST(QuerySolutionTest, GetFieldsWithStringBoundsIdentifiesStringsWithInclusiveBounds) { + IndexBounds bounds; + BSONObj keyPattern = BSON("a" << 1); + bounds.isSimpleRange = true; + bounds.startKey = fromjson("{'a': 1}"); + bounds.endKey = fromjson("{'a': ''}"); + bounds.boundInclusion = BoundInclusion::kIncludeBothStartAndEndKeys; + + auto fields = IndexScanNode::getFieldsWithStringBounds(bounds, keyPattern); + ASSERT_EQUALS(fields.size(), 1U); + ASSERT_TRUE(fields.count("a")); +} + TEST(QuerySolutionTest, IndexScanNodeRemovesNonMatchingCollatedFieldsFromSortsOnSimpleBounds) { IndexScanNode node{IndexEntry(BSON("a" << 1 << "b" << 1))}; CollatorInterfaceMock queryCollator(CollatorInterfaceMock::MockType::kReverseString); @@ -370,7 +425,7 @@ TEST(QuerySolutionTest, IndexScanNodeRemovesNonMatchingCollatedFieldsFromSortsOn node.bounds.isSimpleRange = true; node.bounds.startKey = BSON("a" << 1 << "b" << 1); node.bounds.endKey = BSON("a" << 2 << "b" << 1); - node.bounds.endKeyInclusive = true; + node.bounds.boundInclusion = BoundInclusion::kIncludeBothStartAndEndKeys; node.computeProperties(); @@ -388,7 +443,7 @@ TEST(QuerySolutionTest, IndexScanNodeGetFieldsWithStringBoundsCorrectlyHandlesEn node.bounds.startKey = BSON("a" << 1 << "b" << 1); node.bounds.endKey = BSON("a" << 1 << "b" << ""); - node.bounds.endKeyInclusive = false; + node.bounds.boundInclusion = BoundInclusion::kIncludeStartKeyOnly; node.computeProperties(); @@ -398,7 +453,7 @@ TEST(QuerySolutionTest, IndexScanNodeGetFieldsWithStringBoundsCorrectlyHandlesEn ASSERT_TRUE(sorts.count(BSON("a" << 1 << "b" << 1))); ASSERT_TRUE(sorts.count(BSON("b" << 1))); - node.bounds.endKeyInclusive = true; + node.bounds.boundInclusion = BoundInclusion::kIncludeBothStartAndEndKeys; node.computeProperties(); @@ -416,8 +471,8 @@ TEST(QuerySolutionTest, IndexScanNodeRemovesCollatedFieldsFromSortsIfCollationDi OrderedIntervalList oilA{}; oilA.name = "a"; - oilA.intervals.push_back( - IndexBoundsBuilder::makeRangeInterval(BSON("" << MINKEY << "" << MAXKEY), true, true)); + oilA.intervals.push_back(IndexBoundsBuilder::makeRangeInterval( + BSON("" << MINKEY << "" << MAXKEY), BoundInclusion::kIncludeBothStartAndEndKeys)); node.bounds.fields.push_back(oilA); node.computeProperties(); @@ -432,8 +487,8 @@ TEST(QuerySolutionTest, IndexScanNodeDoesNotRemoveCollatedFieldsFromSortsIfColla OrderedIntervalList oilA{}; oilA.name = "a"; - oilA.intervals.push_back( - IndexBoundsBuilder::makeRangeInterval(BSON("" << MINKEY << "" << MAXKEY), true, true)); + oilA.intervals.push_back(IndexBoundsBuilder::makeRangeInterval( + BSON("" << MINKEY << "" << MAXKEY), BoundInclusion::kIncludeBothStartAndEndKeys)); node.bounds.fields.push_back(oilA); node.computeProperties(); @@ -464,20 +519,20 @@ TEST(QuerySolutionTest, CompoundIndexWithNonMatchingCollationFiltersAllSortsWith OrderedIntervalList c{}; c.name = "c"; - c.intervals.push_back( - IndexBoundsBuilder::makeRangeInterval(BSON("" << MINKEY << "" << MAXKEY), true, true)); + c.intervals.push_back(IndexBoundsBuilder::makeRangeInterval( + BSON("" << MINKEY << "" << MAXKEY), BoundInclusion::kIncludeBothStartAndEndKeys)); node.bounds.fields.push_back(c); OrderedIntervalList d{}; d.name = "d"; - d.intervals.push_back( - IndexBoundsBuilder::makeRangeInterval(BSON("" << 1 << "" << 2), true, true)); + d.intervals.push_back(IndexBoundsBuilder::makeRangeInterval( + BSON("" << 1 << "" << 2), BoundInclusion::kIncludeBothStartAndEndKeys)); node.bounds.fields.push_back(d); OrderedIntervalList e{}; e.name = "e"; - e.intervals.push_back( - IndexBoundsBuilder::makeRangeInterval(BSON("" << 1 << "" << 2), true, true)); + e.intervals.push_back(IndexBoundsBuilder::makeRangeInterval( + BSON("" << 1 << "" << 2), BoundInclusion::kIncludeBothStartAndEndKeys)); node.bounds.fields.push_back(e); node.computeProperties(); @@ -498,7 +553,8 @@ TEST(QuerySolutionTest, IndexScanNodeWithNonMatchingCollationFiltersObjectField) OrderedIntervalList oilA{}; oilA.name = "a"; oilA.intervals.push_back(IndexBoundsBuilder::makeRangeInterval( - BSON("" << BSON("foo" << 1) << "" << BSON("foo" << 2)), true, true)); + BSON("" << BSON("foo" << 1) << "" << BSON("foo" << 2)), + BoundInclusion::kIncludeBothStartAndEndKeys)); node.bounds.fields.push_back(oilA); node.computeProperties(); @@ -516,8 +572,9 @@ TEST(QuerySolutionTest, IndexScanNodeWithNonMatchingCollationFiltersArrayField) OrderedIntervalList oilA{}; oilA.name = "a"; - oilA.intervals.push_back(IndexBoundsBuilder::makeRangeInterval( - BSON("" << BSON_ARRAY(1) << "" << BSON_ARRAY(2)), true, true)); + oilA.intervals.push_back( + IndexBoundsBuilder::makeRangeInterval(BSON("" << BSON_ARRAY(1) << "" << BSON_ARRAY(2)), + BoundInclusion::kIncludeBothStartAndEndKeys)); node.bounds.fields.push_back(oilA); node.computeProperties(); @@ -533,18 +590,18 @@ TEST(QuerySolutionTest, WithNonMatchingCollatorAndNoEqualityPrefixSortsAreNotDup OrderedIntervalList oilA{}; oilA.name = "a"; - oilA.intervals.push_back( - IndexBoundsBuilder::makeRangeInterval(BSON("" << 1 << "" << 2), true, true)); + oilA.intervals.push_back(IndexBoundsBuilder::makeRangeInterval( + BSON("" << 1 << "" << 2), BoundInclusion::kIncludeBothStartAndEndKeys)); node.bounds.fields.push_back(oilA); OrderedIntervalList oilB{}; oilB.name = "b"; - oilB.intervals.push_back(IndexBoundsBuilder::makeRangeInterval(BSON("" - << "a" - << "" - << "b"), - true, - true)); + oilB.intervals.push_back( + IndexBoundsBuilder::makeRangeInterval(BSON("" + << "a" + << "" + << "b"), + BoundInclusion::kIncludeBothStartAndEndKeys)); node.bounds.fields.push_back(oilB); node.computeProperties(); @@ -559,12 +616,12 @@ TEST(QuerySolutionTest, IndexScanNodeHasFieldIncludesStringFieldWhenNoCollator) OrderedIntervalList oilA{}; oilA.name = "a"; - oilA.intervals.push_back(IndexBoundsBuilder::makeRangeInterval(BSON("" - << "str" - << "" - << "str"), - true, - true)); + oilA.intervals.push_back( + IndexBoundsBuilder::makeRangeInterval(BSON("" + << "str" + << "" + << "str"), + BoundInclusion::kIncludeBothStartAndEndKeys)); node.bounds.fields.push_back(oilA); OrderedIntervalList oilB{}; @@ -582,7 +639,7 @@ TEST(QuerySolutionTest, IndexScanNodeHasFieldIncludesSimpleBoundsStringFieldWhen node.bounds.isSimpleRange = true; node.bounds.startKey = BSON("a" << 1 << "b" << 2); node.bounds.endKey = BSON("a" << 2 << "b" << 1); - node.bounds.endKeyInclusive = false; + node.bounds.boundInclusion = BoundInclusion::kIncludeStartKeyOnly; ASSERT_TRUE(node.hasField("a")); ASSERT_TRUE(node.hasField("b")); @@ -595,18 +652,18 @@ TEST(QuerySolutionTest, IndexScanNodeHasFieldExcludesStringFieldWhenIndexHasColl OrderedIntervalList oilA{}; oilA.name = "a"; - oilA.intervals.push_back( - IndexBoundsBuilder::makeRangeInterval(BSON("" << 1 << "" << 2), true, true)); + oilA.intervals.push_back(IndexBoundsBuilder::makeRangeInterval( + BSON("" << 1 << "" << 2), BoundInclusion::kIncludeBothStartAndEndKeys)); node.bounds.fields.push_back(oilA); OrderedIntervalList oilB{}; oilB.name = "b"; - oilB.intervals.push_back(IndexBoundsBuilder::makeRangeInterval(BSON("" - << "bar" - << "" - << "foo"), - true, - false)); + oilB.intervals.push_back( + IndexBoundsBuilder::makeRangeInterval(BSON("" + << "bar" + << "" + << "foo"), + BoundInclusion::kIncludeStartKeyOnly)); node.bounds.fields.push_back(oilB); ASSERT_TRUE(node.hasField("a")); @@ -621,7 +678,7 @@ TEST(QuerySolutionTest, IndexScanNodeHasFieldExcludesSimpleBoundsStringFieldWhen node.bounds.isSimpleRange = true; node.bounds.startKey = BSON("a" << 1 << "b" << 2); node.bounds.endKey = BSON("a" << 2 << "b" << 1); - node.bounds.endKeyInclusive = false; + node.bounds.boundInclusion = BoundInclusion::kIncludeStartKeyOnly; ASSERT_TRUE(node.hasField("a")); ASSERT_FALSE(node.hasField("b")); diff --git a/src/mongo/db/range_deleter_db_env.cpp b/src/mongo/db/range_deleter_db_env.cpp index e064de33599..2f692111c0a 100644 --- a/src/mongo/db/range_deleter_db_env.cpp +++ b/src/mongo/db/range_deleter_db_env.cpp @@ -90,7 +90,7 @@ bool RangeDeleterDBEnv::deleteRange(OperationContext* txn, *deletedDocs = Helpers::removeRange(txn, KeyRange(ns, inclusiveLower, exclusiveUpper, keyPattern), - false, /*maxInclusive*/ + BoundInclusion::kIncludeStartKeyOnly, writeConcern, removeSaverPtr, fromMigrate, diff --git a/src/mongo/db/repl/storage_interface_impl.cpp b/src/mongo/db/repl/storage_interface_impl.cpp index 3a3c280fa7a..acabfe69eb9 100644 --- a/src/mongo/db/repl/storage_interface_impl.cpp +++ b/src/mongo/db/repl/storage_interface_impl.cpp @@ -506,7 +506,6 @@ StatusWith<BSONObj> _findOrDeleteOne(OperationContext* txn, auto maxKey = Helpers::toKeyFormat(keyPattern.extendRangeBound({}, true)); auto bounds = isForward ? std::make_pair(minKey, maxKey) : std::make_pair(maxKey, minKey); - bool endKeyInclusive = false; planExecutor = isFind ? InternalPlanner::indexScan(txn, @@ -514,7 +513,7 @@ StatusWith<BSONObj> _findOrDeleteOne(OperationContext* txn, indexDescriptor, bounds.first, bounds.second, - endKeyInclusive, + BoundInclusion::kIncludeStartKeyOnly, PlanExecutor::YIELD_MANUAL, direction, InternalPlanner::IXSCAN_FETCH) @@ -524,7 +523,7 @@ StatusWith<BSONObj> _findOrDeleteOne(OperationContext* txn, indexDescriptor, bounds.first, bounds.second, - endKeyInclusive, + BoundInclusion::kIncludeStartKeyOnly, PlanExecutor::YIELD_MANUAL, direction); } diff --git a/src/mongo/db/s/check_sharding_index_command.cpp b/src/mongo/db/s/check_sharding_index_command.cpp index 311ba971233..0d7ef33de31 100644 --- a/src/mongo/db/s/check_sharding_index_command.cpp +++ b/src/mongo/db/s/check_sharding_index_command.cpp @@ -135,14 +135,15 @@ public: max = Helpers::toKeyFormat(kp.extendRangeBound(max, false)); } - unique_ptr<PlanExecutor> exec(InternalPlanner::indexScan(txn, - collection, - idx, - min, - max, - false, // endKeyInclusive - PlanExecutor::YIELD_MANUAL, - InternalPlanner::FORWARD)); + unique_ptr<PlanExecutor> exec( + InternalPlanner::indexScan(txn, + collection, + idx, + min, + max, + BoundInclusion::kIncludeStartKeyOnly, + PlanExecutor::YIELD_MANUAL, + InternalPlanner::FORWARD)); exec->setYieldPolicy(PlanExecutor::YIELD_AUTO, collection); // Find the 'missingField' value used to represent a missing document field in a key of diff --git a/src/mongo/db/s/collection_range_deleter.cpp b/src/mongo/db/s/collection_range_deleter.cpp index 37f1899b8af..4feb104f748 100644 --- a/src/mongo/db/s/collection_range_deleter.cpp +++ b/src/mongo/db/s/collection_range_deleter.cpp @@ -169,15 +169,16 @@ int CollectionRangeDeleter::_doDeletion(OperationContext* txn, return -1; } - std::unique_ptr<PlanExecutor> exec(InternalPlanner::indexScan(txn, - collection, - desc, - min, - max, - false, - PlanExecutor::YIELD_MANUAL, - InternalPlanner::FORWARD, - InternalPlanner::IXSCAN_FETCH)); + std::unique_ptr<PlanExecutor> exec( + InternalPlanner::indexScan(txn, + collection, + desc, + min, + max, + BoundInclusion::kIncludeStartKeyOnly, + PlanExecutor::YIELD_MANUAL, + InternalPlanner::FORWARD, + InternalPlanner::IXSCAN_FETCH)); int numDeleted = 0; const int maxItersBeforeYield = std::max(static_cast<int>(internalQueryExecYieldIterations), 1); diff --git a/src/mongo/db/s/migration_chunk_cloner_source_legacy.cpp b/src/mongo/db/s/migration_chunk_cloner_source_legacy.cpp index d73e58cbfe4..a6f40042f1c 100644 --- a/src/mongo/db/s/migration_chunk_cloner_source_legacy.cpp +++ b/src/mongo/db/s/migration_chunk_cloner_source_legacy.cpp @@ -569,13 +569,14 @@ Status MigrationChunkClonerSourceLegacy::_storeCurrentLocs(OperationContext* txn BSONObj min = Helpers::toKeyFormat(kp.extendRangeBound(_args.getMinKey(), false)); BSONObj max = Helpers::toKeyFormat(kp.extendRangeBound(_args.getMaxKey(), false)); - std::unique_ptr<PlanExecutor> exec(InternalPlanner::indexScan(txn, - collection, - idx, - min, - max, - false, // endKeyInclusive - PlanExecutor::YIELD_MANUAL)); + std::unique_ptr<PlanExecutor> exec( + InternalPlanner::indexScan(txn, + collection, + idx, + min, + max, + BoundInclusion::kIncludeStartKeyOnly, + PlanExecutor::YIELD_MANUAL)); // We can afford to yield here because any change to the base data that we might miss is already // being queued and will migrate in the 'transferMods' stage. diff --git a/src/mongo/db/s/split_chunk_command.cpp b/src/mongo/db/s/split_chunk_command.cpp index 3be198a377d..c5719f9c7e1 100644 --- a/src/mongo/db/s/split_chunk_command.cpp +++ b/src/mongo/db/s/split_chunk_command.cpp @@ -78,7 +78,7 @@ bool checkIfSingleDoc(OperationContext* txn, idx, newmin, newmax, - false, // endKeyInclusive + BoundInclusion::kIncludeStartKeyOnly, PlanExecutor::YIELD_MANUAL)); // check if exactly one document found PlanExecutor::ExecState state; diff --git a/src/mongo/db/s/split_vector_command.cpp b/src/mongo/db/s/split_vector_command.cpp index 77503dda5f5..9b013430f3b 100644 --- a/src/mongo/db/s/split_vector_command.cpp +++ b/src/mongo/db/s/split_vector_command.cpp @@ -253,14 +253,15 @@ public: long long currCount = 0; long long numChunks = 0; - unique_ptr<PlanExecutor> exec(InternalPlanner::indexScan(txn, - collection, - idx, - min, - max, - false, // endKeyInclusive - PlanExecutor::YIELD_MANUAL, - InternalPlanner::FORWARD)); + unique_ptr<PlanExecutor> exec( + InternalPlanner::indexScan(txn, + collection, + idx, + min, + max, + BoundInclusion::kIncludeStartKeyOnly, + PlanExecutor::YIELD_MANUAL, + InternalPlanner::FORWARD)); BSONObj currKey; PlanExecutor::ExecState state = exec->getNext(&currKey, NULL); @@ -334,7 +335,7 @@ public: idx, min, max, - false, // endKeyInclusive + BoundInclusion::kIncludeStartKeyOnly, PlanExecutor::YIELD_MANUAL, InternalPlanner::FORWARD); diff --git a/src/mongo/db/ttl.cpp b/src/mongo/db/ttl.cpp index 09aea33b069..c2ee5ba1561 100644 --- a/src/mongo/db/ttl.cpp +++ b/src/mongo/db/ttl.cpp @@ -221,7 +221,6 @@ private: const Date_t expirationTime = Date_t::now() - Seconds(secondsExpireElt.numberLong()); const BSONObj startKey = BSON("" << kDawnOfTime); const BSONObj endKey = BSON("" << expirationTime); - const bool endKeyInclusive = true; // The canonical check as to whether a key pattern element is "ascending" or // "descending" is (elt.number() >= 0). This is defined by the Ordering class. const InternalPlanner::Direction direction = (key.firstElement().number() >= 0) @@ -251,7 +250,7 @@ private: desc, startKey, endKey, - endKeyInclusive, + BoundInclusion::kIncludeBothStartAndEndKeys, PlanExecutor::YIELD_AUTO, direction); |