diff options
author | David Hatch <david.hatch@mongodb.com> | 2016-07-08 17:16:15 -0400 |
---|---|---|
committer | David Hatch <david.hatch@mongodb.com> | 2016-07-28 22:33:08 -0400 |
commit | 13448cde4947adb5c935bb034187480365cf692f (patch) | |
tree | 2cf01918467d33755976588fac7dedb530530c7a /src/mongo/db | |
parent | 3a9e531cd088b6c10bec4b1d9d6ea49b3db8ce7a (diff) | |
download | mongo-13448cde4947adb5c935bb034187480365cf692f.tar.gz |
SERVER-24239 Allow creation of indexes with the same key pattern.
Diffstat (limited to 'src/mongo/db')
34 files changed, 509 insertions, 367 deletions
diff --git a/src/mongo/db/auth/auth_index_d.cpp b/src/mongo/db/auth/auth_index_d.cpp index 8f45cfda0e3..427900c8377 100644 --- a/src/mongo/db/auth/auth_index_d.cpp +++ b/src/mongo/db/auth/auth_index_d.cpp @@ -97,10 +97,11 @@ Status verifySystemIndexes(OperationContext* txn) { } IndexCatalog* indexCatalog = collection->getIndexCatalog(); - IndexDescriptor* oldIndex = NULL; + std::vector<IndexDescriptor*> indexes; + indexCatalog->findIndexesByKeyPattern(txn, v1SystemUsersKeyPattern, false, &indexes); - if (indexCatalog && - (oldIndex = indexCatalog->findIndexByKeyPattern(txn, v1SystemUsersKeyPattern))) { + if (indexCatalog && !indexes.empty()) { + fassert(ErrorCodes::AmbiguousIndexKeyPattern, indexes.size() == 1); return Status(ErrorCodes::AuthSchemaIncompatible, "Old 2.4 style user index identified. " "The authentication schema needs to be updated by " diff --git a/src/mongo/db/catalog/coll_mod.cpp b/src/mongo/db/catalog/coll_mod.cpp index 75769b5c752..8d142b25a12 100644 --- a/src/mongo/db/catalog/coll_mod.cpp +++ b/src/mongo/db/catalog/coll_mod.cpp @@ -118,14 +118,29 @@ Status collMod(OperationContext* txn, continue; } - const IndexDescriptor* idx = - coll->getIndexCatalog()->findIndexByKeyPattern(txn, keyPattern); - if (idx == NULL) { + std::vector<IndexDescriptor*> indexes; + coll->getIndexCatalog()->findIndexesByKeyPattern(txn, keyPattern, false, &indexes); + + if (indexes.size() > 1) { + errorStatus = + Status(ErrorCodes::AmbiguousIndexKeyPattern, + str::stream() << "index keyPattern " << keyPattern << " matches " + << indexes.size() + << " indexes," + << " must use index name. " + << "Conflicting indexes:" + << indexes[0]->infoObj() + << ", " + << indexes[1]->infoObj()); + continue; + } else if (indexes.empty()) { errorStatus = Status( - ErrorCodes::InvalidOptions, + ErrorCodes::IndexNotFound, str::stream() << "cannot find index " << keyPattern << " for ns " << nss.ns()); continue; } + + const IndexDescriptor* idx = indexes[0]; BSONElement oldExpireSecs = idx->infoObj().getField("expireAfterSeconds"); if (oldExpireSecs.eoo()) { errorStatus = diff --git a/src/mongo/db/catalog/drop_indexes.cpp b/src/mongo/db/catalog/drop_indexes.cpp index cab438315a9..3def51b36ed 100644 --- a/src/mongo/db/catalog/drop_indexes.cpp +++ b/src/mongo/db/catalog/drop_indexes.cpp @@ -103,14 +103,24 @@ Status wrappedRun(OperationContext* txn, } if (f.type() == Object) { - IndexDescriptor* desc = - collection->getIndexCatalog()->findIndexByKeyPattern(txn, f.embeddedObject()); - if (desc == NULL) { + std::vector<IndexDescriptor*> indexes; + collection->getIndexCatalog()->findIndexesByKeyPattern( + txn, f.embeddedObject(), false, &indexes); + if (indexes.empty()) { return Status(ErrorCodes::IndexNotFound, - str::stream() << "can't find index with key: " - << f.embeddedObject().toString()); + str::stream() << "can't find index with key: " << f.embeddedObject()); + } else if (indexes.size() > 1) { + return Status(ErrorCodes::AmbiguousIndexKeyPattern, + str::stream() << indexes.size() << " indexes found for key: " + << f.embeddedObject() + << ", identify by name instead." + << " Conflicting indexes: " + << indexes[0]->infoObj() + << ", " + << indexes[1]->infoObj()); } + IndexDescriptor* desc = indexes[0]; if (desc->isIdIndex()) { return Status(ErrorCodes::InvalidOptions, "cannot drop _id index"); } diff --git a/src/mongo/db/catalog/index_catalog.cpp b/src/mongo/db/catalog/index_catalog.cpp index 7d51b2b7d01..c6c6a9fa034 100644 --- a/src/mongo/db/catalog/index_catalog.cpp +++ b/src/mongo/db/catalog/index_catalog.cpp @@ -656,6 +656,7 @@ Status IndexCatalog::_doesSpecConflictWithExisting(OperationContext* txn, invariant(name[0]); const BSONObj key = spec.getObjectField("key"); + const BSONObj collation = spec.getObjectField("collation"); { // Check both existing and in-progress indexes (2nd param = true) @@ -663,15 +664,29 @@ Status IndexCatalog::_doesSpecConflictWithExisting(OperationContext* txn, if (desc) { // index already exists with same name - if (!desc->keyPattern().equal(key)) + if (desc->keyPattern().equal(key) && + desc->infoObj().getObjectField("collation") != collation) { + // key patterns are equal but collations differ. + return Status(ErrorCodes::IndexOptionsConflict, + str::stream() + << "An index with the same key pattern, but a different " + << "collation already exists with the same name. Try again with " + << "a unique name. " + << "Existing index: " + << desc->infoObj() + << " Requested index: " + << spec); + } + + if (!desc->keyPattern().equal(key) || + desc->infoObj().getObjectField("collation") != collation) { return Status(ErrorCodes::IndexKeySpecsConflict, - str::stream() << "Trying to create an index " - << "with same name " - << name - << " with different key spec " - << key - << " vs existing spec " - << desc->keyPattern()); + str::stream() << "Index must have unique name." + << "The existing index: " + << desc->infoObj() + << " has the same name as the requested index: " + << spec); + } IndexDescriptor temp(_collection, _getAccessMethodName(txn, key), spec); if (!desc->areIndexOptionsEquivalent(&temp)) @@ -682,30 +697,34 @@ Status IndexCatalog::_doesSpecConflictWithExisting(OperationContext* txn, // Index already exists with the same options, so no need to build a new // one (not an error). Most likely requested by a client using ensureIndex. return Status(ErrorCodes::IndexAlreadyExists, - str::stream() << "index already exists: " << name); + str::stream() << "Identical index already exists: " << name); } } { - // Check both existing and in-progress indexes (2nd param = true) - const IndexDescriptor* desc = findIndexByKeyPattern(txn, key, true); + // Check both existing and in-progress indexes. + const bool findInProgressIndexes = true; + const IndexDescriptor* desc = + findIndexByKeyPatternAndCollationSpec(txn, key, collation, findInProgressIndexes); if (desc) { - LOG(2) << "index already exists with diff name " << name << ' ' << key << endl; + LOG(2) << "index already exists with diff name " << name << " pattern: " << key + << " collation: " << collation << endl; IndexDescriptor temp(_collection, _getAccessMethodName(txn, key), spec); if (!desc->areIndexOptionsEquivalent(&temp)) return Status(ErrorCodes::IndexOptionsConflict, - str::stream() << "Index with pattern: " << key - << " already exists with different options"); + str::stream() << "Index: " << spec + << " already exists with different options: " + << desc->infoObj()); return Status(ErrorCodes::IndexAlreadyExists, - str::stream() << "index already exists: " << name); + str::stream() << "index already exists with different name: " << name); } } if (numIndexesTotal(txn) >= _maxNumIndexesAllowed) { string s = str::stream() << "add index fails, too many indexes for " - << _collection->ns().ns() << " key:" << key.toString(); + << _collection->ns().ns() << " key:" << key; log() << s; return Status(ErrorCodes::CannotCreateIndex, s); } @@ -1051,18 +1070,35 @@ IndexDescriptor* IndexCatalog::findIndexByName(OperationContext* txn, return NULL; } -IndexDescriptor* IndexCatalog::findIndexByKeyPattern(OperationContext* txn, - const BSONObj& key, - bool includeUnfinishedIndexes) const { +IndexDescriptor* IndexCatalog::findIndexByKeyPatternAndCollationSpec( + OperationContext* txn, + const BSONObj& key, + const BSONObj& collationSpec, + bool includeUnfinishedIndexes) const { IndexIterator ii = getIndexIterator(txn, includeUnfinishedIndexes); while (ii.more()) { IndexDescriptor* desc = ii.next(); - if (desc->keyPattern() == key) + if (desc->keyPattern() == key && + desc->infoObj().getObjectField("collation") == collationSpec) return desc; } return NULL; } +void IndexCatalog::findIndexesByKeyPattern(OperationContext* txn, + const BSONObj& key, + bool includeUnfinishedIndexes, + std::vector<IndexDescriptor*>* matches) const { + invariant(matches); + IndexIterator ii = getIndexIterator(txn, includeUnfinishedIndexes); + while (ii.more()) { + IndexDescriptor* desc = ii.next(); + if (desc->keyPattern() == key) { + matches->push_back(desc); + } + } +} + IndexDescriptor* IndexCatalog::findShardKeyPrefixedIndex(OperationContext* txn, const BSONObj& shardKey, bool requireSingleKey) const { @@ -1071,6 +1107,7 @@ IndexDescriptor* IndexCatalog::findShardKeyPrefixedIndex(OperationContext* txn, IndexIterator ii = getIndexIterator(txn, false); while (ii.more()) { IndexDescriptor* desc = ii.next(); + bool hasSimpleCollation = desc->infoObj().getObjectField("collation").isEmpty(); if (desc->isPartial()) continue; @@ -1078,10 +1115,10 @@ IndexDescriptor* IndexCatalog::findShardKeyPrefixedIndex(OperationContext* txn, if (!shardKey.isPrefixOf(desc->keyPattern())) continue; - if (!desc->isMultikey(txn)) + if (!desc->isMultikey(txn) && hasSimpleCollation) return desc; - if (!requireSingleKey) + if (!requireSingleKey && hasSimpleCollation) best = desc; } diff --git a/src/mongo/db/catalog/index_catalog.h b/src/mongo/db/catalog/index_catalog.h index a982a6e976b..37ec3305615 100644 --- a/src/mongo/db/catalog/index_catalog.h +++ b/src/mongo/db/catalog/index_catalog.h @@ -86,6 +86,8 @@ public: IndexDescriptor* findIdIndex(OperationContext* txn) const; /** + * Find index by name. The index name uniquely identifies an index. + * * @return null if cannot find */ IndexDescriptor* findIndexByName(OperationContext* txn, @@ -93,11 +95,31 @@ public: bool includeUnfinishedIndexes = false) const; /** - * @return null if cannot find + * Find index by matching key pattern and collation spec. The key pattern and collation spec + * uniquely identify an index. + * + * Collation is specified as a normalized collation spec as returned by + * CollationInterface::getSpec. An empty object indicates the simple collation. + * + * @return null if cannot find index, otherwise the index with a matching key pattern and + * collation. + */ + IndexDescriptor* findIndexByKeyPatternAndCollationSpec( + OperationContext* txn, + const BSONObj& key, + const BSONObj& collationSpec, + bool includeUnfinishedIndexes = false) const; + + /** + * Find indexes with a matching key pattern, putting them into the vector 'matches'. The key + * pattern alone does not uniquely identify an index. + * + * Consider using 'findIndexByName' if expecting to match one index. */ - IndexDescriptor* findIndexByKeyPattern(OperationContext* txn, - const BSONObj& key, - bool includeUnfinishedIndexes = false) const; + void findIndexesByKeyPattern(OperationContext* txn, + const BSONObj& key, + bool includeUnfinishedIndexes, + std::vector<IndexDescriptor*>* matches) const; /** * Returns an index suitable for shard key range scans. @@ -105,6 +127,7 @@ public: * This index: * - must be prefixed by 'shardKey', and * - must not be a partial index. + * - must have the simple collation. * * If the parameter 'requireSingleKey' is true, then this index additionally must not be * multi-key. diff --git a/src/mongo/db/commands/dbcommands.cpp b/src/mongo/db/commands/dbcommands.cpp index 6ddae87fa3b..d41678d5518 100644 --- a/src/mongo/db/commands/dbcommands.cpp +++ b/src/mongo/db/commands/dbcommands.cpp @@ -1009,7 +1009,8 @@ public: virtual void help(stringstream& help) const { help << "Sets collection options.\n" "Example: { collMod: 'foo', usePowerOf2Sizes:true }\n" - "Example: { collMod: 'foo', index: {keyPattern: {a: 1}, expireAfterSeconds: 600} }"; + "Example: { collMod: 'foo', index: {keyPattern: {a: 1}, expireAfterSeconds: 600} " + "}\n"; } virtual void addRequiredPrivileges(const std::string& dbname, diff --git a/src/mongo/db/dbhelpers.cpp b/src/mongo/db/dbhelpers.cpp index 1ae6e1f60da..65f8c801447 100644 --- a/src/mongo/db/dbhelpers.cpp +++ b/src/mongo/db/dbhelpers.cpp @@ -291,30 +291,6 @@ BSONObj Helpers::inferKeyPattern(const BSONObj& o) { return kpBuilder.obj(); } -static bool findShardKeyIndexPattern(OperationContext* txn, - const string& ns, - const BSONObj& shardKeyPattern, - BSONObj* indexPattern) { - AutoGetCollectionForRead ctx(txn, ns); - Collection* collection = ctx.getCollection(); - if (!collection) { - return false; - } - - // Allow multiKey based on the invariant that shard keys must be single-valued. - // Therefore, any multi-key index prefixed by shard key cannot be multikey over - // the shard key fields. - const IndexDescriptor* idx = - collection->getIndexCatalog()->findShardKeyPrefixedIndex(txn, - shardKeyPattern, - false); // requireSingleKey - - if (idx == NULL) - return false; - *indexPattern = idx->keyPattern().getOwned(); - return true; -} - long long Helpers::removeRange(OperationContext* txn, const KeyRange& range, bool maxInclusive, @@ -327,24 +303,45 @@ long long Helpers::removeRange(OperationContext* txn, // The IndexChunk has a keyPattern that may apply to more than one index - we need to // select the index and get the full index keyPattern here. - BSONObj indexKeyPatternDoc; - if (!findShardKeyIndexPattern(txn, ns, range.keyPattern, &indexKeyPatternDoc)) { - warning(LogComponent::kSharding) << "no index found to clean data over range of type " - << range.keyPattern << " in " << ns << endl; - return -1; - } + std::string indexName; + BSONObj min; + BSONObj max; + + { + AutoGetCollectionForRead ctx(txn, ns); + Collection* collection = ctx.getCollection(); + if (!collection) { + warning(LogComponent::kSharding) + << "collection deleted before cleaning data over range of type " << range.keyPattern + << " in " << ns << endl; + return -1; + } - KeyPattern indexKeyPattern(indexKeyPatternDoc); + // Allow multiKey based on the invariant that shard keys must be single-valued. + // Therefore, any multi-key index prefixed by shard key cannot be multikey over + // the shard key fields. + const IndexDescriptor* idx = + collection->getIndexCatalog()->findShardKeyPrefixedIndex(txn, + range.keyPattern, + false); // requireSingleKey + if (!idx) { + warning(LogComponent::kSharding) << "no index found to clean data over range of type " + << range.keyPattern << " in " << ns << endl; + return -1; + } + + indexName = idx->indexName(); + KeyPattern indexKeyPattern(idx->keyPattern()); - // Extend bounds to match the index we found + // Extend bounds to match the index we found + + // 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, ....) + max = Helpers::toKeyFormat(indexKeyPattern.extendRangeBound(range.maxKey, maxInclusive)); + } - // Extend min to get (min, MinKey, MinKey, ....) - const BSONObj& 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 BSONObj& max = - Helpers::toKeyFormat(indexKeyPattern.extendRangeBound(range.maxKey, maxInclusive)); MONGO_LOG_COMPONENT(1, LogComponent::kSharding) << "begin removal of " << min << " to " << max << " in " << ns @@ -362,8 +359,7 @@ long long Helpers::removeRange(OperationContext* txn, if (!collection) break; - IndexDescriptor* desc = - collection->getIndexCatalog()->findIndexByKeyPattern(txn, indexKeyPattern.toBSON()); + IndexDescriptor* desc = collection->getIndexCatalog()->findIndexByName(txn, indexName); unique_ptr<PlanExecutor> exec( InternalPlanner::indexScan(txn, diff --git a/src/mongo/db/exec/stagedebug_cmd.cpp b/src/mongo/db/exec/stagedebug_cmd.cpp index 837300e3ace..d626595b87d 100644 --- a/src/mongo/db/exec/stagedebug_cmd.cpp +++ b/src/mongo/db/exec/stagedebug_cmd.cpp @@ -273,12 +273,30 @@ public: string nodeName = firstElt.fieldName(); if ("ixscan" == nodeName) { - // This'll throw if it's not an obj but that's OK. - BSONObj keyPatternObj = nodeArgs["keyPattern"].Obj(); - - IndexDescriptor* desc = - collection->getIndexCatalog()->findIndexByKeyPattern(txn, keyPatternObj); - uassert(16890, "Can't find index: " + keyPatternObj.toString(), desc); + IndexDescriptor* desc; + if (BSONElement keyPatternElement = nodeArgs["keyPattern"]) { + // This'll throw if it's not an obj but that's OK. + BSONObj keyPatternObj = keyPatternElement.Obj(); + std::vector<IndexDescriptor*> indexes; + collection->getIndexCatalog()->findIndexesByKeyPattern( + txn, keyPatternObj, false, &indexes); + uassert(16890, + str::stream() << "Can't find index: " << keyPatternObj, + !indexes.empty()); + uassert(ErrorCodes::AmbiguousIndexKeyPattern, + str::stream() << indexes.size() << " matching indexes for key pattern: " + << keyPatternObj + << ". Conflicting indexes: " + << indexes[0]->infoObj() + << ", " + << indexes[1]->infoObj(), + indexes.size() == 1); + desc = indexes[0]; + } else { + StringData name = nodeArgs["name"].valueStringData(); + desc = collection->getIndexCatalog()->findIndexByName(txn, name); + uassert(40223, str::stream() << "Can't find index: " << name.toString(), desc); + } IndexScanParams params; params.descriptor = desc; diff --git a/src/mongo/db/exec/subplan.cpp b/src/mongo/db/exec/subplan.cpp index b909047b9ed..f98f088ca9c 100644 --- a/src/mongo/db/exec/subplan.cpp +++ b/src/mongo/db/exec/subplan.cpp @@ -172,7 +172,7 @@ Status SubplanStage::planSubqueries() { for (size_t i = 0; i < _plannerParams.indices.size(); ++i) { const IndexEntry& ie = _plannerParams.indices[i]; - _indexMap[ie.keyPattern] = i; + _indexMap[ie.name] = i; LOG(5) << "Subplanner: index " << i << " is " << ie.toString(); } @@ -249,7 +249,7 @@ namespace { Status tagOrChildAccordingToCache(PlanCacheIndexTree* compositeCacheData, SolutionCacheData* branchCacheData, MatchExpression* orChild, - const std::map<BSONObj, size_t>& indexMap) { + const std::map<StringData, size_t>& indexMap) { invariant(compositeCacheData); // We want a well-formed *indexed* solution. diff --git a/src/mongo/db/exec/subplan.h b/src/mongo/db/exec/subplan.h index 198be1d7d06..a9b356ff4a6 100644 --- a/src/mongo/db/exec/subplan.h +++ b/src/mongo/db/exec/subplan.h @@ -32,6 +32,7 @@ #include "mongo/base/owned_pointer_vector.h" #include "mongo/base/status.h" +#include "mongo/base/string_data.h" #include "mongo/db/exec/plan_stage.h" #include "mongo/db/query/canonical_query.h" #include "mongo/db/query/plan_cache.h" @@ -205,7 +206,7 @@ private: OwnedPointerVector<BranchPlanningResult> _branchResults; // We need this to extract cache-friendly index data from the index assignments. - std::map<BSONObj, size_t> _indexMap; + std::map<StringData, size_t> _indexMap; }; } // namespace mongo diff --git a/src/mongo/db/query/get_executor.cpp b/src/mongo/db/query/get_executor.cpp index 8130ac57e4d..d45c6145abb 100644 --- a/src/mongo/db/query/get_executor.cpp +++ b/src/mongo/db/query/get_executor.cpp @@ -1095,8 +1095,7 @@ bool turnIxscanIntoCount(QuerySolution* soln) { } // Make the count node that we replace the fetch + ixscan with. - CountScanNode* csn = new CountScanNode(); - csn->indexKeyPattern = isn->indexKeyPattern; + CountScanNode* csn = new CountScanNode(isn->index); csn->startKey = startKey; csn->startKeyInclusive = startKeyInclusive; csn->endKey = endKey; @@ -1330,8 +1329,7 @@ bool turnIxscanIntoDistinctIxscan(QuerySolution* soln, const string& field) { } // Make a new DistinctNode. We swap this for the ixscan in the provided solution. - DistinctNode* dn = new DistinctNode(); - dn->indexKeyPattern = isn->indexKeyPattern; + DistinctNode* dn = new DistinctNode(isn->index); dn->direction = isn->direction; dn->bounds = isn->bounds; @@ -1339,7 +1337,7 @@ bool turnIxscanIntoDistinctIxscan(QuerySolution* soln, const string& field) { // try to distinct-hack when there is an index prefixed by the field we're distinct-ing // over. Consider removing this code if we stick with that policy. dn->fieldNo = 0; - BSONObjIterator it(isn->indexKeyPattern); + BSONObjIterator it(isn->index.keyPattern); while (it.more()) { if (field == it.next().fieldName()) { break; @@ -1447,10 +1445,9 @@ StatusWith<unique_ptr<PlanExecutor>> getExecutorDistinct(OperationContext* txn, parsedDistinct->getKey(), cq->getCollator(), &distinctNodeIndex)) { - auto dn = stdx::make_unique<DistinctNode>(); - dn->indexKeyPattern = plannerParams.indices[distinctNodeIndex].keyPattern; + auto dn = stdx::make_unique<DistinctNode>(plannerParams.indices[distinctNodeIndex]); dn->direction = 1; - IndexBoundsBuilder::allValuesBounds(dn->indexKeyPattern, &dn->bounds); + IndexBoundsBuilder::allValuesBounds(dn->index.keyPattern, &dn->bounds); dn->fieldNo = 0; // An index with a non-simple collation requires a FETCH stage. diff --git a/src/mongo/db/query/index_entry.h b/src/mongo/db/query/index_entry.h index 92507461c4d..42e4d2281d2 100644 --- a/src/mongo/db/query/index_entry.h +++ b/src/mongo/db/query/index_entry.h @@ -92,17 +92,22 @@ struct IndexEntry { /** * For testing purposes only. */ - IndexEntry(const BSONObj& kp) + IndexEntry(const BSONObj& kp, const std::string& indexName = "test_foo") : keyPattern(kp), multikey(false), sparse(false), unique(false), - name("test_foo"), + name(indexName), filterExpr(nullptr), infoObj(BSONObj()) { type = IndexNames::nameToType(IndexNames::findPluginName(keyPattern)); } + bool operator==(const IndexEntry& rhs) const { + // Indexes are logically equal when names are equal. + return this->name == rhs.name; + } + std::string toString() const; BSONObj keyPattern; diff --git a/src/mongo/db/query/plan_cache_test.cpp b/src/mongo/db/query/plan_cache_test.cpp index 0c13cb3300c..bac9704e0cf 100644 --- a/src/mongo/db/query/plan_cache_test.cpp +++ b/src/mongo/db/query/plan_cache_test.cpp @@ -449,7 +449,7 @@ class CachePlanSelectionTest : public mongo::unittest::Test { protected: void setUp() { params.options = QueryPlannerParams::INCLUDE_COLLSCAN; - addIndex(BSON("_id" << 1)); + addIndex(BSON("_id" << 1), "_id_"); } void tearDown() { @@ -458,22 +458,21 @@ protected: } } - void addIndex(BSONObj keyPattern, bool multikey = false) { + void addIndex(BSONObj keyPattern, const std::string& indexName, bool multikey = false) { // The first false means not multikey. // The second false means not sparse. - // The third arg is the index name and I am egotistical. // The NULL means no filter expression. - params.indices.push_back(IndexEntry( - keyPattern, multikey, false, false, "hari_king_of_the_stove", NULL, BSONObj())); + params.indices.push_back( + IndexEntry(keyPattern, multikey, false, false, indexName, NULL, BSONObj())); } - void addIndex(BSONObj keyPattern, bool multikey, bool sparse) { - params.indices.push_back(IndexEntry( - keyPattern, multikey, sparse, false, "note_to_self_dont_break_build", NULL, BSONObj())); + void addIndex(BSONObj keyPattern, const std::string& indexName, bool multikey, bool sparse) { + params.indices.push_back( + IndexEntry(keyPattern, multikey, sparse, false, indexName, NULL, BSONObj())); } - void addIndex(BSONObj keyPattern, CollatorInterface* collator) { - IndexEntry entry(keyPattern, false, false, false, "index_with_collation", NULL, BSONObj()); + void addIndex(BSONObj keyPattern, const std::string& indexName, CollatorInterface* collator) { + IndexEntry entry(keyPattern, false, false, false, indexName, NULL, BSONObj()); entry.collator = collator; params.indices.push_back(entry); } @@ -782,7 +781,7 @@ const PlanCacheKey CachePlanSelectionTest::ck = "mock_cache_key"; // TEST_F(CachePlanSelectionTest, EqualityIndexScan) { - addIndex(BSON("x" << 1)); + addIndex(BSON("x" << 1), "x_1"); runQuery(BSON("x" << 5)); assertPlanCacheRecoversSolution(BSON("x" << 5), @@ -790,7 +789,7 @@ TEST_F(CachePlanSelectionTest, EqualityIndexScan) { } TEST_F(CachePlanSelectionTest, EqualityIndexScanWithTrailingFields) { - addIndex(BSON("x" << 1 << "y" << 1)); + addIndex(BSON("x" << 1 << "y" << 1), "x_1_y_1"); runQuery(BSON("x" << 5)); assertPlanCacheRecoversSolution( @@ -803,7 +802,8 @@ TEST_F(CachePlanSelectionTest, EqualityIndexScanWithTrailingFields) { TEST_F(CachePlanSelectionTest, Basic2DSphereNonNear) { addIndex(BSON("a" - << "2dsphere")); + << "2dsphere"), + "a_2dsphere"); BSONObj query; query = fromjson( @@ -819,7 +819,8 @@ TEST_F(CachePlanSelectionTest, Basic2DSphereNonNear) { TEST_F(CachePlanSelectionTest, Basic2DSphereGeoNear) { addIndex(BSON("a" - << "2dsphere")); + << "2dsphere"), + "a_2dsphere"); BSONObj query; query = fromjson("{a: {$nearSphere: [0,0], $maxDistance: 0.31 }}"); @@ -838,9 +839,10 @@ TEST_F(CachePlanSelectionTest, Basic2DSphereGeoNear) { } TEST_F(CachePlanSelectionTest, Basic2DSphereGeoNearReverseCompound) { - addIndex(BSON("x" << 1)); + addIndex(BSON("x" << 1), "x_1"); addIndex(BSON("x" << 1 << "a" - << "2dsphere")); + << "2dsphere"), + "x_1_a_2dsphere"); BSONObj query = fromjson("{x:1, a: {$nearSphere: [0,0], $maxDistance: 0.31 }}"); runQuery(query); assertPlanCacheRecoversSolution( @@ -851,7 +853,8 @@ TEST_F(CachePlanSelectionTest, Basic2DSphereGeoNearReverseCompound) { TEST_F(CachePlanSelectionTest, TwoDSphereNoGeoPred) { addIndex(BSON("x" << 1 << "a" - << "2dsphere")); + << "2dsphere"), + "x_1_a_2dsphere"); runQuery(BSON("x" << 1)); assertPlanCacheRecoversSolution(BSON("x" << 1), "{fetch: {node: {ixscan: {pattern: {x: 1, a: '2dsphere'}}}}}"); @@ -859,9 +862,11 @@ TEST_F(CachePlanSelectionTest, TwoDSphereNoGeoPred) { TEST_F(CachePlanSelectionTest, Or2DSphereNonNear) { addIndex(BSON("a" - << "2dsphere")); + << "2dsphere"), + "a_2dsphere"); addIndex(BSON("b" - << "2dsphere")); + << "2dsphere"), + "b_2dsphere"); BSONObj query = fromjson( "{$or: [ {a: {$geoIntersects: {$geometry: {type: 'Point', coordinates: [10.0, 10.0]}}}}," " {b: {$geoWithin: { $centerSphere: [[ 10, 20 ], 0.01 ] } }} ]}"); @@ -879,7 +884,8 @@ TEST_F(CachePlanSelectionTest, AndWithinPolygonWithinCenterSphere) { addIndex(BSON("a" << "2dsphere" << "b" - << 1)); + << 1), + "a_2dsphere_b_2dsphere"); BSONObj query = fromjson( "{$and: [{b: 1}, {a: {$within: {$polygon: [[0, 0], [0, 0], [0, 0], [0, 0]]}}}, {a: " @@ -895,7 +901,7 @@ TEST_F(CachePlanSelectionTest, AndWithinPolygonWithinCenterSphere) { // TEST_F(CachePlanSelectionTest, TwoPredicatesAnding) { - addIndex(BSON("x" << 1)); + addIndex(BSON("x" << 1), "x_1"); BSONObj query = fromjson("{$and: [ {x: {$gt: 1}}, {x: {$lt: 3}} ] }"); runQuery(query); assertPlanCacheRecoversSolution( @@ -903,7 +909,7 @@ TEST_F(CachePlanSelectionTest, TwoPredicatesAnding) { } TEST_F(CachePlanSelectionTest, SimpleOr) { - addIndex(BSON("a" << 1)); + addIndex(BSON("a" << 1), "a_1"); BSONObj query = fromjson("{$or: [{a: 20}, {a: 21}]}"); runQuery(query); assertPlanCacheRecoversSolution( @@ -911,7 +917,7 @@ TEST_F(CachePlanSelectionTest, SimpleOr) { } TEST_F(CachePlanSelectionTest, OrWithAndChild) { - addIndex(BSON("a" << 1)); + addIndex(BSON("a" << 1), "a_1"); BSONObj query = fromjson("{$or: [{a: 20}, {$and: [{a:1}, {b:7}]}]}"); runQuery(query); assertPlanCacheRecoversSolution(query, @@ -922,7 +928,7 @@ TEST_F(CachePlanSelectionTest, OrWithAndChild) { } TEST_F(CachePlanSelectionTest, AndWithUnindexedOrChild) { - addIndex(BSON("a" << 1)); + addIndex(BSON("a" << 1), "a_1"); BSONObj query = fromjson("{a:20, $or: [{b:1}, {c:7}]}"); runQuery(query); assertPlanCacheRecoversSolution(query, @@ -932,8 +938,8 @@ TEST_F(CachePlanSelectionTest, AndWithUnindexedOrChild) { TEST_F(CachePlanSelectionTest, AndWithOrWithOneIndex) { - addIndex(BSON("b" << 1)); - addIndex(BSON("a" << 1)); + addIndex(BSON("b" << 1), "b_1"); + addIndex(BSON("a" << 1), "a_1"); BSONObj query = fromjson("{$or: [{b:1}, {c:7}], a:20}"); runQuery(query); assertPlanCacheRecoversSolution(query, @@ -947,8 +953,8 @@ TEST_F(CachePlanSelectionTest, AndWithOrWithOneIndex) { // SERVER-1205. TEST_F(CachePlanSelectionTest, MergeSort) { - addIndex(BSON("a" << 1 << "c" << 1)); - addIndex(BSON("b" << 1 << "c" << 1)); + addIndex(BSON("a" << 1 << "c" << 1), "a_1_c_1"); + addIndex(BSON("b" << 1 << "c" << 1), "b_1_c_1"); BSONObj query = fromjson("{$or: [{a:1}, {b:1}]}"); BSONObj sort = BSON("c" << 1); @@ -965,8 +971,8 @@ TEST_F(CachePlanSelectionTest, MergeSort) { // SERVER-1205 as well. TEST_F(CachePlanSelectionTest, NoMergeSortIfNoSortWanted) { - addIndex(BSON("a" << 1 << "c" << 1)); - addIndex(BSON("b" << 1 << "c" << 1)); + addIndex(BSON("a" << 1 << "c" << 1), "a_1_c_1"); + addIndex(BSON("b" << 1 << "c" << 1), "b_1_c_1"); BSONObj query = fromjson("{$or: [{a:1}, {b:1}]}"); runQuerySortProj(query, BSONObj(), BSONObj()); @@ -983,7 +989,7 @@ TEST_F(CachePlanSelectionTest, NoMergeSortIfNoSortWanted) { // Disabled: SERVER-10801. /* TEST_F(CachePlanSelectionTest, SortOnGeoQuery) { - addIndex(BSON("timestamp" << -1 << "position" << "2dsphere")); + addIndex(BSON("timestamp" << -1 << "position" << "2dsphere"), "timestamp_-1_position_2dsphere"); BSONObj query = fromjson("{position: {$geoWithin: {$geometry: {type: \"Polygon\", " "coordinates: [[[1, 1], [1, 90], [180, 90], " "[180, 1], [1, 1]]]}}}}"); @@ -998,7 +1004,8 @@ TEST_F(CachePlanSelectionTest, SortOnGeoQuery) { // SERVER-9257 TEST_F(CachePlanSelectionTest, CompoundGeoNoGeoPredicate) { addIndex(BSON("creationDate" << 1 << "foo.bar" - << "2dsphere")); + << "2dsphere"), + "creationDate_1_foo.bar_2dsphere"); BSONObj query = fromjson("{creationDate: {$gt: 7}}"); BSONObj sort = fromjson("{creationDate: 1}"); runQuerySortProj(query, sort, BSONObj()); @@ -1012,7 +1019,7 @@ TEST_F(CachePlanSelectionTest, CompoundGeoNoGeoPredicate) { } TEST_F(CachePlanSelectionTest, ReverseScanForSort) { - addIndex(BSON("_id" << 1)); + addIndex(BSON("_id" << 1), "_id_1"); runQuerySortProj(BSONObj(), fromjson("{_id: -1}"), BSONObj()); assertPlanCacheRecoversSolution( BSONObj(), @@ -1027,22 +1034,22 @@ TEST_F(CachePlanSelectionTest, ReverseScanForSort) { // TEST_F(CachePlanSelectionTest, CollscanNoUsefulIndices) { - addIndex(BSON("a" << 1 << "b" << 1)); - addIndex(BSON("c" << 1)); + addIndex(BSON("a" << 1 << "b" << 1), "a_1_b_1"); + addIndex(BSON("c" << 1), "c_1"); runQuery(BSON("b" << 4)); assertPlanCacheRecoversSolution(BSON("b" << 4), "{cscan: {filter: {b: 4}, dir: 1}}"); } TEST_F(CachePlanSelectionTest, CollscanOrWithoutEnoughIndices) { - addIndex(BSON("a" << 1)); + addIndex(BSON("a" << 1), "a_1"); BSONObj query = fromjson("{$or: [{a: 20}, {b: 21}]}"); runQuery(query); assertPlanCacheRecoversSolution(query, "{cscan: {filter: {$or:[{a:20},{b:21}]}, dir: 1}}"); } TEST_F(CachePlanSelectionTest, CollscanMergeSort) { - addIndex(BSON("a" << 1 << "c" << 1)); - addIndex(BSON("b" << 1 << "c" << 1)); + addIndex(BSON("a" << 1 << "c" << 1), "a_1_c_1"); + addIndex(BSON("b" << 1 << "c" << 1), "b_1_c_1"); BSONObj query = fromjson("{$or: [{a:1}, {b:1}]}"); BSONObj sort = BSON("c" << 1); @@ -1064,7 +1071,7 @@ TEST_F(CachePlanSelectionTest, CachedPlanForCompoundMultikeyIndexCanCompoundBoun params.options = QueryPlannerParams::NO_TABLE_SCAN | QueryPlannerParams::INDEX_INTERSECTION; const bool multikey = true; - addIndex(BSON("a" << 1 << "b" << 1), multikey); + addIndex(BSON("a" << 1 << "b" << 1), "a_1_b_1", multikey); BSONObj query = fromjson("{a: 2, b: 3}"); runQuery(query); @@ -1080,7 +1087,7 @@ TEST_F(CachePlanSelectionTest, params.options = QueryPlannerParams::NO_TABLE_SCAN | QueryPlannerParams::INDEX_INTERSECTION; const bool multikey = true; - addIndex(BSON("a" << 1), multikey); + addIndex(BSON("a" << 1), "a_1", multikey); BSONObj query = fromjson("{$and: [{a: 2}, {a: 3}]}"); runQuery(query); @@ -1104,7 +1111,7 @@ TEST_F(CachePlanSelectionTest, params.options = QueryPlannerParams::NO_TABLE_SCAN | QueryPlannerParams::INDEX_INTERSECTION; const bool multikey = true; - addIndex(BSON("a" << 1), multikey); + addIndex(BSON("a" << 1), "a_1", multikey); BSONObj query = fromjson("{$and: [{a: {$gte: 2}}, {a: {$lt: 3}}]}"); runQuery(query); @@ -1121,8 +1128,8 @@ TEST_F(CachePlanSelectionTest, CachedPlanForIntersectionOfMultikeyIndexesWhenUsi params.options = QueryPlannerParams::NO_TABLE_SCAN | QueryPlannerParams::INDEX_INTERSECTION; const bool multikey = true; - addIndex(BSON("a.b" << 1), multikey); - addIndex(BSON("a.c" << 1), multikey); + addIndex(BSON("a.b" << 1), "a.b_1", multikey); + addIndex(BSON("a.c" << 1), "a.c_1", multikey); BSONObj query = fromjson("{a: {$elemMatch: {b: 2, c: 3}}}"); runQuery(query); @@ -1145,8 +1152,8 @@ TEST_F(CachePlanSelectionTest, CachedPlanForIntersectionWithNonMultikeyIndexCanI params.options = QueryPlannerParams::NO_TABLE_SCAN | QueryPlannerParams::INDEX_INTERSECTION; const bool multikey = true; - addIndex(BSON("a.b" << 1), multikey); - addIndex(BSON("a.c" << 1), !multikey); + addIndex(BSON("a.b" << 1), "a.b_1", multikey); + addIndex(BSON("a.c" << 1), "a.c_1", !multikey); BSONObj query = fromjson("{'a.b': 2, 'a.c': {$gte: 0, $lt: 10}}}}"); runQuery(query); @@ -1164,13 +1171,14 @@ TEST_F(CachePlanSelectionTest, CachedPlanForIntersectionWithNonMultikeyIndexCanI TEST_F(CachePlanSelectionTest, GeoNear2DNotCached) { addIndex(BSON("a" - << "2d")); + << "2d"), + "a_2d"); runQuery(fromjson("{a: {$near: [0,0], $maxDistance:0.3 }}")); assertNotCached("{geoNear2d: {a: '2d'}}"); } TEST_F(CachePlanSelectionTest, MinNotCached) { - addIndex(BSON("a" << 1)); + addIndex(BSON("a" << 1), "a_1"); runQueryHintMinMax(BSONObj(), BSONObj(), fromjson("{a: 1}"), BSONObj()); assertNotCached( "{fetch: {filter: null, " @@ -1178,7 +1186,7 @@ TEST_F(CachePlanSelectionTest, MinNotCached) { } TEST_F(CachePlanSelectionTest, MaxNotCached) { - addIndex(BSON("a" << 1)); + addIndex(BSON("a" << 1), "a_1"); runQueryHintMinMax(BSONObj(), BSONObj(), BSONObj(), fromjson("{a: 1}")); assertNotCached( "{fetch: {filter: null, " @@ -1186,8 +1194,8 @@ TEST_F(CachePlanSelectionTest, MaxNotCached) { } TEST_F(CachePlanSelectionTest, NaturalHintNotCached) { - addIndex(BSON("a" << 1)); - addIndex(BSON("b" << 1)); + addIndex(BSON("a" << 1), "a_1"); + addIndex(BSON("b" << 1), "b_1"); runQuerySortHint(BSON("a" << 1), BSON("b" << 1), BSON("$natural" << 1)); assertNotCached( "{sort: {pattern: {b: 1}, limit: 0, node: {sortKeyGen: {node: " @@ -1195,7 +1203,7 @@ TEST_F(CachePlanSelectionTest, NaturalHintNotCached) { } TEST_F(CachePlanSelectionTest, HintValidNotCached) { - addIndex(BSON("a" << 1)); + addIndex(BSON("a" << 1), "a_1"); runQueryHint(BSONObj(), fromjson("{a: 1}")); assertNotCached( "{fetch: {filter: null, " @@ -1208,7 +1216,8 @@ TEST_F(CachePlanSelectionTest, HintValidNotCached) { TEST_F(CachePlanSelectionTest, Basic2DNonNearNotCached) { addIndex(BSON("a" - << "2d")); + << "2d"), + "a_2d"); BSONObj query; // Polygon @@ -1234,9 +1243,11 @@ TEST_F(CachePlanSelectionTest, Basic2DNonNearNotCached) { TEST_F(CachePlanSelectionTest, Or2DNonNearNotCached) { addIndex(BSON("a" - << "2d")); + << "2d"), + "a_2d"); addIndex(BSON("b" - << "2d")); + << "2d"), + "b_2d"); BSONObj query = fromjson( "{$or: [ {a : { $within : { $polygon : [[0,0], [2,0], [4,0]] } }}," " {b : { $within : { $center : [[ 5, 5 ], 7 ] } }} ]}"); @@ -1253,7 +1264,7 @@ TEST_F(CachePlanSelectionTest, Or2DNonNearNotCached) { TEST_F(CachePlanSelectionTest, MatchingCollation) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - addIndex(BSON("x" << 1), &collator); + addIndex(BSON("x" << 1), "x_1", &collator); runQueryAsCommand(fromjson( "{find: 'testns', filter: {x: 'foo'}, collation: {locale: 'mock_reverse_string'}}")); diff --git a/src/mongo/db/query/planner_access.cpp b/src/mongo/db/query/planner_access.cpp index 896e56d5253..fd53efbc3c0 100644 --- a/src/mongo/db/query/planner_access.cpp +++ b/src/mongo/db/query/planner_access.cpp @@ -174,8 +174,7 @@ QuerySolutionNode* QueryPlannerAccess::makeLeafNode( bool indexIs2D = (String == elt.type() && "2d" == elt.String()); if (indexIs2D) { - GeoNear2DNode* ret = new GeoNear2DNode(); - ret->indexKeyPattern = index.keyPattern; + GeoNear2DNode* ret = new GeoNear2DNode(index); ret->nq = &nearExpr->getData(); ret->baseBounds.fields.resize(index.keyPattern.nFields()); if (NULL != query.getProj()) { @@ -185,8 +184,7 @@ QuerySolutionNode* QueryPlannerAccess::makeLeafNode( return ret; } else { - GeoNear2DSphereNode* ret = new GeoNear2DSphereNode(); - ret->indexKeyPattern = index.keyPattern; + GeoNear2DSphereNode* ret = new GeoNear2DSphereNode(index); ret->nq = &nearExpr->getData(); ret->baseBounds.fields.resize(index.keyPattern.nFields()); if (NULL != query.getProj()) { @@ -199,20 +197,16 @@ QuerySolutionNode* QueryPlannerAccess::makeLeafNode( // We must not keep the expression node around. *tightnessOut = IndexBoundsBuilder::EXACT; TextMatchExpressionBase* textExpr = static_cast<TextMatchExpressionBase*>(expr); - TextNode* ret = new TextNode(); - ret->indexKeyPattern = index.keyPattern; + TextNode* ret = new TextNode(index); ret->ftsQuery = textExpr->getFTSQuery().clone(); return ret; } else { // Note that indexKeyPattern.firstElement().fieldName() may not equal expr->path() // because expr might be inside an array operator that provides a path prefix. - IndexScanNode* isn = new IndexScanNode(); - isn->indexKeyPattern = index.keyPattern; - isn->indexIsMultiKey = index.multikey; + IndexScanNode* isn = new IndexScanNode(index); isn->bounds.fields.resize(index.keyPattern.nFields()); isn->maxScan = query.getQueryRequest().getMaxScan(); isn->addKeyMetadata = query.getQueryRequest().returnKey(); - isn->indexCollator = index.collator; isn->queryCollator = query.getCollator(); // Get the ixtag->pos-th element of the index key pattern. @@ -402,7 +396,7 @@ void QueryPlannerAccess::finishTextNode(QuerySolutionNode* node, const IndexEntr // For example, say keyPattern = { a: 1, _fts: "text", _ftsx: 1, b: 1 } // prefixEnd should be 1. size_t prefixEnd = 0; - BSONObjIterator it(tn->indexKeyPattern); + BSONObjIterator it(tn->index.keyPattern); // Count how many prefix terms we have. while (it.more()) { // We know that the only key pattern with a type of String is the _fts field @@ -1246,12 +1240,9 @@ QuerySolutionNode* QueryPlannerAccess::scanWholeIndex(const IndexEntry& index, QuerySolutionNode* solnRoot = NULL; // Build an ixscan over the id index, use it, and return it. - unique_ptr<IndexScanNode> isn = make_unique<IndexScanNode>(); - isn->indexKeyPattern = index.keyPattern; - isn->indexIsMultiKey = index.multikey; + unique_ptr<IndexScanNode> isn = make_unique<IndexScanNode>(index); isn->maxScan = query.getQueryRequest().getMaxScan(); isn->addKeyMetadata = query.getQueryRequest().returnKey(); - isn->indexCollator = index.collator; isn->queryCollator = query.getCollator(); IndexBoundsBuilder::allValuesBounds(index.keyPattern, &isn->bounds); @@ -1384,9 +1375,7 @@ QuerySolutionNode* QueryPlannerAccess::makeIndexScan(const IndexEntry& index, QuerySolutionNode* solnRoot = NULL; // Build an ixscan over the id index, use it, and return it. - IndexScanNode* isn = new IndexScanNode(); - isn->indexKeyPattern = index.keyPattern; - isn->indexIsMultiKey = index.multikey; + IndexScanNode* isn = new IndexScanNode(index); isn->direction = 1; isn->maxScan = query.getQueryRequest().getMaxScan(); isn->addKeyMetadata = query.getQueryRequest().returnKey(); @@ -1394,7 +1383,6 @@ QuerySolutionNode* QueryPlannerAccess::makeIndexScan(const IndexEntry& index, isn->bounds.startKey = startKey; isn->bounds.endKey = endKey; isn->bounds.endKeyInclusive = false; - isn->indexCollator = index.collator; isn->queryCollator = query.getCollator(); unique_ptr<MatchExpression> filter = query.root()->shallowClone(); diff --git a/src/mongo/db/query/planner_analysis.cpp b/src/mongo/db/query/planner_analysis.cpp index a5ba08cb211..7298959764f 100644 --- a/src/mongo/db/query/planner_analysis.cpp +++ b/src/mongo/db/query/planner_analysis.cpp @@ -213,13 +213,10 @@ void explodeScan(const IndexScanNode* isn, verify(prefix.size() == fieldsToExplode); // Copy boring fields into new child. - IndexScanNode* child = new IndexScanNode(); - child->indexKeyPattern = isn->indexKeyPattern; + IndexScanNode* child = new IndexScanNode(isn->index); child->direction = isn->direction; child->maxScan = isn->maxScan; child->addKeyMetadata = isn->addKeyMetadata; - child->indexIsMultiKey = isn->indexIsMultiKey; - child->indexCollator = isn->indexCollator; child->queryCollator = isn->queryCollator; // Copy the filter, if there is one. @@ -380,7 +377,7 @@ bool QueryPlannerAnalysis::explodeForSort(const CanonicalQuery& query, // Skip every field that is a union of point intervals and build the resulting sort // order from the remaining fields. - BSONObjIterator kpIt(isn->indexKeyPattern); + BSONObjIterator kpIt(isn->index.keyPattern); size_t boundsIdx = 0; while (kpIt.more()) { const OrderedIntervalList& oil = bounds.fields[boundsIdx]; @@ -773,12 +770,12 @@ QuerySolution* QueryPlannerAnalysis::analyzeDataAccess(const CanonicalQuery& que if (STAGE_IXSCAN == leafNodes[0]->getType()) { projType = ProjectionNode::COVERED_ONE_INDEX; IndexScanNode* ixn = static_cast<IndexScanNode*>(leafNodes[0]); - coveredKeyObj = ixn->indexKeyPattern; + coveredKeyObj = ixn->index.keyPattern; LOG(5) << "PROJECTION: covered via IXSCAN, using COVERED fast path"; } else if (STAGE_DISTINCT_SCAN == leafNodes[0]->getType()) { projType = ProjectionNode::COVERED_ONE_INDEX; DistinctNode* dn = static_cast<DistinctNode*>(leafNodes[0]); - coveredKeyObj = dn->indexKeyPattern; + coveredKeyObj = dn->index.keyPattern; LOG(5) << "PROJECTION: covered via DISTINCT, using COVERED fast path"; } } diff --git a/src/mongo/db/query/planner_analysis_test.cpp b/src/mongo/db/query/planner_analysis_test.cpp index c02c9c25cfb..a47e6780787 100644 --- a/src/mongo/db/query/planner_analysis_test.cpp +++ b/src/mongo/db/query/planner_analysis_test.cpp @@ -103,8 +103,7 @@ TEST(QueryPlannerAnalysis, GetSortPatternSpecialIndexTypes) { // Test the generation of sort orders provided by an index scan done by // IndexScanNode::computeProperties(). TEST(QueryPlannerAnalysis, IxscanSortOrdersBasic) { - IndexScanNode ixscan; - ixscan.indexKeyPattern = fromjson("{a: 1, b: 1, c: 1, d: 1, e: 1}"); + IndexScanNode ixscan(IndexEntry(fromjson("{a: 1, b: 1, c: 1, d: 1, e: 1}"))); // Bounds are {a: [[1,1]], b: [[2,2]], c: [[3,3]], d: [[1,5]], e:[[1,1],[2,2]]}, // all inclusive. diff --git a/src/mongo/db/query/query_planner.cpp b/src/mongo/db/query/query_planner.cpp index 83c7c500fc7..5665b333a56 100644 --- a/src/mongo/db/query/query_planner.cpp +++ b/src/mongo/db/query/query_planner.cpp @@ -32,8 +32,10 @@ #include "mongo/db/query/query_planner.h" +#include <boost/optional.hpp> #include <vector> +#include "mongo/base/string_data.h" #include "mongo/client/dbclientinterface.h" // For QueryOption_foobar #include "mongo/db/bson/dotted_path_support.h" #include "mongo/db/matcher/expression_algo.h" @@ -292,7 +294,7 @@ Status QueryPlanner::cacheDataFromTaggedTree(const MatchExpression* const tagged // static Status QueryPlanner::tagAccordingToCache(MatchExpression* filter, const PlanCacheIndexTree* const indexTree, - const map<BSONObj, size_t>& indexMap) { + const map<StringData, size_t>& indexMap) { if (NULL == filter) { return Status(ErrorCodes::BadValue, "Cannot tag tree: filter is NULL."); } @@ -321,10 +323,10 @@ Status QueryPlanner::tagAccordingToCache(MatchExpression* filter, } if (NULL != indexTree->entry.get()) { - map<BSONObj, size_t>::const_iterator got = indexMap.find(indexTree->entry->keyPattern); + map<StringData, size_t>::const_iterator got = indexMap.find(indexTree->entry->name); if (got == indexMap.end()) { mongoutils::str::stream ss; - ss << "Did not find index with keyPattern: " << indexTree->entry->keyPattern.toString(); + ss << "Did not find index with name: " << indexTree->entry->name; return Status(ErrorCodes::BadValue, ss); } filter->setTag( @@ -386,11 +388,11 @@ Status QueryPlanner::planFromCache(const CanonicalQuery& query, // Map from index name to index number. // TODO: can we assume that the index numbering has the same lifetime // as the cache state? - map<BSONObj, size_t> indexMap; + map<StringData, size_t> indexMap; for (size_t i = 0; i < params.indices.size(); ++i) { const IndexEntry& ie = params.indices[i]; - indexMap[ie.keyPattern] = i; - LOG(5) << "Index " << i << ": " << ie.keyPattern.toString() << endl; + indexMap[ie.name] = i; + LOG(5) << "Index " << i << ": " << ie.name << endl; } Status s = tagAccordingToCache(clone.get(), winnerCacheData.tree.get(), indexMap); @@ -531,7 +533,7 @@ Status QueryPlanner::plan(const CanonicalQuery& query, } } - size_t hintIndexNumber = numeric_limits<size_t>::max(); + boost::optional<size_t> hintIndexNumber; if (hintIndex.isEmpty()) { QueryPlannerIXSelect::findRelevantIndices(fields, params.indices, &relevantIndices); @@ -558,13 +560,20 @@ Status QueryPlanner::plan(const CanonicalQuery& query, relevantIndices.push_back(params.indices[i]); LOG(5) << "Hint specified, restricting indices to " << hintIndex.toString() << endl; + if (hintIndexNumber) { + return Status(ErrorCodes::IndexNotFound, + str::stream() << "Hint matched multiple indexes, " + << "must hint by index name. Matched: " + << params.indices[i].toString() + << " and " + << params.indices[*hintIndexNumber].toString()); + } hintIndexNumber = i; - break; } } } - if (hintIndexNumber == numeric_limits<size_t>::max()) { + if (!hintIndexNumber) { return Status(ErrorCodes::BadValue, "bad hint"); } } @@ -598,7 +607,7 @@ Status QueryPlanner::plan(const CanonicalQuery& query, return Status(ErrorCodes::BadValue, "hint provided does not work with max query"); } - const BSONObj& kp = params.indices[hintIndexNumber].keyPattern; + const BSONObj& kp = params.indices[*hintIndexNumber].keyPattern; finishedMinObj = finishMinObj(kp, minObj, maxObj); finishedMaxObj = finishMaxObj(kp, minObj, maxObj); @@ -609,7 +618,7 @@ Status QueryPlanner::plan(const CanonicalQuery& query, "hint provided does not work with min/max query"); } - idxNo = hintIndexNumber; + idxNo = *hintIndexNumber; } else { // No hinted index, look for one that is compatible (has same field names and // ordering thereof). @@ -807,7 +816,7 @@ Status QueryPlanner::plan(const CanonicalQuery& query, // desired behavior when an index is hinted that is not relevant to the query. if (!hintIndex.isEmpty()) { if (0 == out->size()) { - QuerySolution* soln = buildWholeIXSoln(params.indices[hintIndexNumber], query, params); + QuerySolution* soln = buildWholeIXSoln(params.indices[*hintIndexNumber], query, params); verify(NULL != soln); LOG(5) << "Planner: outputting soln that uses hinted index as scan." << endl; out->push_back(soln); diff --git a/src/mongo/db/query/query_planner.h b/src/mongo/db/query/query_planner.h index 0b33b109b4e..da4e7225541 100644 --- a/src/mongo/db/query/query_planner.h +++ b/src/mongo/db/query/query_planner.h @@ -28,6 +28,7 @@ #pragma once +#include "mongo/base/string_data.h" #include "mongo/db/query/canonical_query.h" #include "mongo/db/query/query_planner_params.h" #include "mongo/db/query/query_solution.h" @@ -111,7 +112,7 @@ public: */ static Status tagAccordingToCache(MatchExpression* filter, const PlanCacheIndexTree* const indexTree, - const std::map<BSONObj, size_t>& indexMap); + const std::map<StringData, size_t>& indexMap); }; } // namespace mongo diff --git a/src/mongo/db/query/query_planner_common.cpp b/src/mongo/db/query/query_planner_common.cpp index 2ae977a825e..d97b9f39345 100644 --- a/src/mongo/db/query/query_planner_common.cpp +++ b/src/mongo/db/query/query_planner_common.cpp @@ -60,7 +60,7 @@ void QueryPlannerCommon::reverseScans(QuerySolutionNode* node) { } } - if (!isn->bounds.isValidFor(isn->indexKeyPattern, isn->direction)) { + if (!isn->bounds.isValidFor(isn->index.keyPattern, isn->direction)) { LOG(5) << "Invalid bounds: " << isn->bounds.toString() << std::endl; invariant(0); } diff --git a/src/mongo/db/query/query_planner_test.cpp b/src/mongo/db/query/query_planner_test.cpp index 4fc0145d0dc..b063e12db0b 100644 --- a/src/mongo/db/query/query_planner_test.cpp +++ b/src/mongo/db/query/query_planner_test.cpp @@ -35,6 +35,7 @@ #include "mongo/db/jsobj.h" #include "mongo/db/json.h" #include "mongo/db/matcher/extensions_callback_disallow_extensions.h" +#include "mongo/db/query/collation/collator_interface_mock.h" #include "mongo/db/query/query_planner.h" #include "mongo/db/query/query_planner_test_fixture.h" @@ -4172,9 +4173,9 @@ TEST_F(QueryPlannerTest, TagAccordingToCacheFailsOnBadInput) { std::unique_ptr<CanonicalQuery> scopedCq = std::move(statusWithCQ.getValue()); std::unique_ptr<PlanCacheIndexTree> indexTree(new PlanCacheIndexTree()); - indexTree->setIndexEntry(IndexEntry(BSON("a" << 1))); + indexTree->setIndexEntry(IndexEntry(BSON("a" << 1), "a_1")); - std::map<BSONObj, size_t> indexMap; + std::map<StringData, size_t> indexMap; // Null filter. Status s = QueryPlanner::tagAccordingToCache(NULL, indexTree.get(), indexMap); @@ -4189,7 +4190,7 @@ TEST_F(QueryPlannerTest, TagAccordingToCacheFailsOnBadInput) { ASSERT_NOT_OK(s); // Index found once added to the map. - indexMap[BSON("a" << 1)] = 0; + indexMap["a_1"_sd] = 0; s = QueryPlanner::tagAccordingToCache(scopedCq->root(), indexTree.get(), indexMap); ASSERT_OK(s); @@ -4248,4 +4249,19 @@ TEST_F(QueryPlannerTest, NorWithSingleChildCanUseIndexAfterComplementingBounds) "{a: [['MinKey', -Infinity, true, false], [3, 'MaxKey', true, true]]}}}}}"); } +// Multiple indexes +TEST_F(QueryPlannerTest, PlansForMultipleIndexesOnTheSameKeyPatternAreGenerated) { + CollatorInterfaceMock reverseCollator(CollatorInterfaceMock::MockType::kReverseString); + CollatorInterfaceMock equalCollator(CollatorInterfaceMock::MockType::kAlwaysEqual); + addIndex(BSON("a" << 1), &reverseCollator, "reverse"_sd); + addIndex(BSON("a" << 1), &equalCollator, "forward"_sd); + + runQuery(BSON("a" << 1)); + + assertNumSolutions(3U); + assertSolutionExists("{fetch: {node: {ixscan: {name: 'reverse'}}}}"); + assertSolutionExists("{fetch: {node: {ixscan: {name: 'forward'}}}}"); + assertSolutionExists("{cscan: {dir: 1}}}}"); +} + } // namespace diff --git a/src/mongo/db/query/query_planner_test_fixture.cpp b/src/mongo/db/query/query_planner_test_fixture.cpp index 46762b3eb4a..d97856b849a 100644 --- a/src/mongo/db/query/query_planner_test_fixture.cpp +++ b/src/mongo/db/query/query_planner_test_fixture.cpp @@ -140,6 +140,20 @@ void QueryPlannerTest::addIndex(BSONObj keyPattern, const CollatorInterface* col } void QueryPlannerTest::addIndex(BSONObj keyPattern, + const CollatorInterface* collator, + StringData indexName) { + const bool sparse = false; + const bool unique = false; + const bool multikey = false; + const auto name = indexName.toString(); + const MatchExpression* filterExpr = nullptr; + const BSONObj infoObj; + IndexEntry entry(keyPattern, multikey, sparse, unique, name, filterExpr, infoObj); + entry.collator = collator; + params.indices.push_back(entry); +} + +void QueryPlannerTest::addIndex(BSONObj keyPattern, MatchExpression* filterExpr, const CollatorInterface* collator) { const bool sparse = false; diff --git a/src/mongo/db/query/query_planner_test_fixture.h b/src/mongo/db/query/query_planner_test_fixture.h index 6c649702c74..c55182c226c 100644 --- a/src/mongo/db/query/query_planner_test_fixture.h +++ b/src/mongo/db/query/query_planner_test_fixture.h @@ -72,6 +72,8 @@ protected: MatchExpression* filterExpr, const CollatorInterface* collator); + void addIndex(BSONObj keyPattern, const CollatorInterface* collator, StringData indexName); + // // Execute planner. // diff --git a/src/mongo/db/query/query_planner_test_lib.cpp b/src/mongo/db/query/query_planner_test_lib.cpp index a52505acc23..9e71f1d0f8c 100644 --- a/src/mongo/db/query/query_planner_test_lib.cpp +++ b/src/mongo/db/query/query_planner_test_lib.cpp @@ -266,10 +266,26 @@ bool QueryPlannerTestLib::solutionMatches(const BSONObj& testSoln, BSONObj ixscanObj = el.Obj(); BSONElement pattern = ixscanObj["pattern"]; - if (pattern.eoo() || !pattern.isABSONObj()) { - return false; + if (!pattern.eoo()) { + if (!pattern.isABSONObj()) { + return false; + } + if (pattern.Obj() != ixn->index.keyPattern) { + return false; + } } - if (pattern.Obj() != ixn->indexKeyPattern) { + + BSONElement name = ixscanObj["name"]; + if (!name.eoo()) { + if (name.type() != BSONType::String) { + return false; + } + if (name.valueStringData() != ixn->index.name) { + return false; + } + } + + if (name.eoo() && pattern.eoo()) { return false; } @@ -314,7 +330,7 @@ bool QueryPlannerTestLib::solutionMatches(const BSONObj& testSoln, return false; } BSONObj geoObj = el.Obj(); - return geoObj == node->indexKeyPattern; + return geoObj == node->index.keyPattern; } else if (STAGE_GEO_NEAR_2DSPHERE == trueSoln->getType()) { const GeoNear2DSphereNode* node = static_cast<const GeoNear2DSphereNode*>(trueSoln); BSONElement el = testSoln["geoNear2dsphere"]; @@ -327,7 +343,7 @@ bool QueryPlannerTestLib::solutionMatches(const BSONObj& testSoln, if (pattern.eoo() || !pattern.isABSONObj()) { return false; } - if (pattern.Obj() != node->indexKeyPattern) { + if (pattern.Obj() != node->index.keyPattern) { return false; } diff --git a/src/mongo/db/query/query_solution.cpp b/src/mongo/db/query/query_solution.cpp index 51ff8aea132..60fd5cc84fc 100644 --- a/src/mongo/db/query/query_solution.cpp +++ b/src/mongo/db/query/query_solution.cpp @@ -182,7 +182,9 @@ void TextNode::appendToString(mongoutils::str::stream* ss, int indent) const { addIndent(ss, indent); *ss << "TEXT\n"; addIndent(ss, indent + 1); - *ss << "keyPattern = " << indexKeyPattern.toString() << '\n'; + *ss << "name = " << index.name << '\n'; + addIndent(ss, indent + 1); + *ss << "keyPattern = " << index.keyPattern.toString() << '\n'; addIndent(ss, indent + 1); *ss << "query = " << ftsQuery->getQuery() << '\n'; addIndent(ss, indent + 1); @@ -201,11 +203,10 @@ void TextNode::appendToString(mongoutils::str::stream* ss, int indent) const { } QuerySolutionNode* TextNode::clone() const { - TextNode* copy = new TextNode(); + TextNode* copy = new TextNode(this->index); cloneBaseData(copy); copy->_sort = this->_sort; - copy->indexKeyPattern = this->indexKeyPattern; copy->ftsQuery = this->ftsQuery->clone(); copy->indexPrefix = this->indexPrefix; @@ -503,19 +504,19 @@ QuerySolutionNode* FetchNode::clone() const { // IndexScanNode // -IndexScanNode::IndexScanNode() - : indexIsMultiKey(false), +IndexScanNode::IndexScanNode(IndexEntry index) + : index(std::move(index)), direction(1), maxScan(0), addKeyMetadata(false), - indexCollator(nullptr), queryCollator(nullptr) {} void IndexScanNode::appendToString(mongoutils::str::stream* ss, int indent) const { addIndent(ss, indent); *ss << "IXSCAN\n"; addIndent(ss, indent + 1); - *ss << "keyPattern = " << indexKeyPattern << '\n'; + *ss << "indexName = " << index.name << '\n'; + *ss << "keyPattern = " << index.keyPattern << '\n'; if (NULL != filter) { addIndent(ss, indent + 1); *ss << "filter = " << filter->toString(); @@ -530,17 +531,17 @@ void IndexScanNode::appendToString(mongoutils::str::stream* ss, int indent) cons bool IndexScanNode::hasField(const string& field) const { // There is no covering in a multikey index because you don't know whether or not the field // in the key was extracted from an array in the original document. - if (indexIsMultiKey) { + if (index.multikey) { return false; } // Custom index access methods may return non-exact key data - this function is currently // used for covering exact key data only. - if (IndexNames::BTREE != IndexNames::findPluginName(indexKeyPattern)) { + if (IndexNames::BTREE != IndexNames::findPluginName(index.keyPattern)) { return false; } - BSONObjIterator it(indexKeyPattern); + BSONObjIterator it(index.keyPattern); while (it.more()) { if (field == it.next().fieldName()) { return true; @@ -556,7 +557,7 @@ bool IndexScanNode::sortedByDiskLoc() const { // If it's a simple range query, it's easy to determine if the range is a point. if (bounds.isSimpleRange) { - return 0 == bounds.startKey.woCompare(bounds.endKey, indexKeyPattern); + return 0 == bounds.startKey.woCompare(bounds.endKey, index.keyPattern); } // If it's a more complex bounds query, we make sure that each field is a point. @@ -627,7 +628,7 @@ std::set<StringData> IndexScanNode::getFieldsWithStringBounds(const IndexBounds& void IndexScanNode::computeProperties() { _sorts.clear(); - BSONObj sortPattern = QueryPlannerAnalysis::getSortPattern(indexKeyPattern); + BSONObj sortPattern = QueryPlannerAnalysis::getSortPattern(index.keyPattern); if (direction == -1) { sortPattern = QueryPlannerCommon::reverseSortObj(sortPattern); } @@ -668,7 +669,7 @@ void IndexScanNode::computeProperties() { equalityFields.insert(oil.name); } } else { - BSONObjIterator keyIter(indexKeyPattern); + BSONObjIterator keyIter(index.keyPattern); BSONObjIterator startIter(bounds.startKey); BSONObjIterator endIter(bounds.endKey); while (keyIter.more() && startIter.more() && endIter.more()) { @@ -683,9 +684,9 @@ void IndexScanNode::computeProperties() { addEqualityFieldSorts(sortPattern, equalityFields, &_sorts); } - if (!CollatorInterface::collatorsMatch(queryCollator, indexCollator)) { + if (!CollatorInterface::collatorsMatch(queryCollator, index.collator)) { // Prune sorts containing fields that don't match the collation. - std::set<StringData> collatedFields = getFieldsWithStringBounds(bounds, indexKeyPattern); + std::set<StringData> collatedFields = getFieldsWithStringBounds(bounds, index.keyPattern); auto sortsIt = _sorts.begin(); while (sortsIt != _sorts.end()) { bool matched = false; @@ -705,17 +706,14 @@ void IndexScanNode::computeProperties() { } QuerySolutionNode* IndexScanNode::clone() const { - IndexScanNode* copy = new IndexScanNode(); + IndexScanNode* copy = new IndexScanNode(this->index); cloneBaseData(copy); copy->_sorts = this->_sorts; - copy->indexKeyPattern = this->indexKeyPattern; - copy->indexIsMultiKey = this->indexIsMultiKey; copy->direction = this->direction; copy->maxScan = this->maxScan; copy->addKeyMetadata = this->addKeyMetadata; copy->bounds = this->bounds; - copy->indexCollator = this->indexCollator; copy->queryCollator = this->queryCollator; return copy; @@ -736,8 +734,7 @@ bool filtersAreEquivalent(const MatchExpression* lhs, const MatchExpression* rhs } // namespace bool IndexScanNode::operator==(const IndexScanNode& other) const { - return filtersAreEquivalent(filter.get(), other.filter.get()) && - indexKeyPattern == other.indexKeyPattern && indexIsMultiKey == other.indexIsMultiKey && + return filtersAreEquivalent(filter.get(), other.filter.get()) && index == other.index && direction == other.direction && maxScan == other.maxScan && addKeyMetadata == other.addKeyMetadata && bounds == other.bounds; } @@ -916,7 +913,9 @@ void GeoNear2DNode::appendToString(mongoutils::str::stream* ss, int indent) cons addIndent(ss, indent); *ss << "GEO_NEAR_2D\n"; addIndent(ss, indent + 1); - *ss << "keyPattern = " << indexKeyPattern.toString() << '\n'; + *ss << "name = " << index.name << '\n'; + addIndent(ss, indent + 1); + *ss << "keyPattern = " << index.keyPattern.toString() << '\n'; addCommon(ss, indent); *ss << "nearQuery = " << nq->toString() << '\n'; if (NULL != filter) { @@ -926,13 +925,12 @@ void GeoNear2DNode::appendToString(mongoutils::str::stream* ss, int indent) cons } QuerySolutionNode* GeoNear2DNode::clone() const { - GeoNear2DNode* copy = new GeoNear2DNode(); + GeoNear2DNode* copy = new GeoNear2DNode(this->index); cloneBaseData(copy); copy->_sorts = this->_sorts; copy->nq = this->nq; copy->baseBounds = this->baseBounds; - copy->indexKeyPattern = this->indexKeyPattern; copy->addPointMeta = this->addPointMeta; copy->addDistMeta = this->addDistMeta; @@ -947,7 +945,9 @@ void GeoNear2DSphereNode::appendToString(mongoutils::str::stream* ss, int indent addIndent(ss, indent); *ss << "GEO_NEAR_2DSPHERE\n"; addIndent(ss, indent + 1); - *ss << "keyPattern = " << indexKeyPattern.toString() << '\n'; + *ss << "name = " << index.name << '\n'; + addIndent(ss, indent + 1); + *ss << "keyPattern = " << index.keyPattern.toString() << '\n'; addCommon(ss, indent); *ss << "baseBounds = " << baseBounds.toString() << '\n'; addIndent(ss, indent + 1); @@ -959,13 +959,12 @@ void GeoNear2DSphereNode::appendToString(mongoutils::str::stream* ss, int indent } QuerySolutionNode* GeoNear2DSphereNode::clone() const { - GeoNear2DSphereNode* copy = new GeoNear2DSphereNode(); + GeoNear2DSphereNode* copy = new GeoNear2DSphereNode(this->index); cloneBaseData(copy); copy->_sorts = this->_sorts; copy->nq = this->nq; copy->baseBounds = this->baseBounds; - copy->indexKeyPattern = this->indexKeyPattern; copy->addPointMeta = this->addPointMeta; copy->addDistMeta = this->addDistMeta; @@ -1035,7 +1034,9 @@ void DistinctNode::appendToString(mongoutils::str::stream* ss, int indent) const addIndent(ss, indent); *ss << "DISTINCT\n"; addIndent(ss, indent + 1); - *ss << "keyPattern = " << indexKeyPattern << '\n'; + *ss << "name = " << index.name << '\n'; + addIndent(ss, indent + 1); + *ss << "keyPattern = " << index.keyPattern << '\n'; addIndent(ss, indent + 1); *ss << "direction = " << direction << '\n'; addIndent(ss, indent + 1); @@ -1043,11 +1044,10 @@ void DistinctNode::appendToString(mongoutils::str::stream* ss, int indent) const } QuerySolutionNode* DistinctNode::clone() const { - DistinctNode* copy = new DistinctNode(); + DistinctNode* copy = new DistinctNode(this->index); cloneBaseData(copy); copy->sorts = this->sorts; - copy->indexKeyPattern = this->indexKeyPattern; copy->direction = this->direction; copy->bounds = this->bounds; copy->fieldNo = this->fieldNo; @@ -1063,7 +1063,9 @@ void CountScanNode::appendToString(mongoutils::str::stream* ss, int indent) cons addIndent(ss, indent); *ss << "COUNT\n"; addIndent(ss, indent + 1); - *ss << "keyPattern = " << indexKeyPattern << '\n'; + *ss << "name = " << index.name << '\n'; + addIndent(ss, indent + 1); + *ss << "keyPattern = " << index.keyPattern << '\n'; addIndent(ss, indent + 1); *ss << "startKey = " << startKey << '\n'; addIndent(ss, indent + 1); @@ -1071,11 +1073,10 @@ void CountScanNode::appendToString(mongoutils::str::stream* ss, int indent) cons } QuerySolutionNode* CountScanNode::clone() const { - CountScanNode* copy = new CountScanNode(); + CountScanNode* copy = new CountScanNode(this->index); cloneBaseData(copy); copy->sorts = this->sorts; - copy->indexKeyPattern = this->indexKeyPattern; copy->startKey = this->startKey; copy->startKeyInclusive = this->startKeyInclusive; copy->endKey = this->endKey; diff --git a/src/mongo/db/query/query_solution.h b/src/mongo/db/query/query_solution.h index 224b9512b3c..aca56fcded6 100644 --- a/src/mongo/db/query/query_solution.h +++ b/src/mongo/db/query/query_solution.h @@ -217,7 +217,7 @@ private: }; struct TextNode : public QuerySolutionNode { - TextNode() {} + TextNode(IndexEntry index) : index(std::move(index)) {} virtual ~TextNode() {} virtual StageType getType() const { @@ -244,7 +244,7 @@ struct TextNode : public QuerySolutionNode { BSONObjSet _sort; - BSONObj indexKeyPattern; + IndexEntry index; std::unique_ptr<fts::FTSQuery> ftsQuery; // "Prefix" fields of a text index can handle equality predicates. We group them with the @@ -433,7 +433,7 @@ struct FetchNode : public QuerySolutionNode { }; struct IndexScanNode : public QuerySolutionNode { - IndexScanNode(); + IndexScanNode(IndexEntry index); virtual ~IndexScanNode() {} virtual void computeProperties(); @@ -467,8 +467,7 @@ struct IndexScanNode : public QuerySolutionNode { BSONObjSet _sorts; - BSONObj indexKeyPattern; - bool indexIsMultiKey; + IndexEntry index; int direction; @@ -480,7 +479,6 @@ struct IndexScanNode : public QuerySolutionNode { IndexBounds bounds; - const CollatorInterface* indexCollator; const CollatorInterface* queryCollator; }; @@ -699,7 +697,8 @@ struct SkipNode : public QuerySolutionNode { // This is a standalone stage. struct GeoNear2DNode : public QuerySolutionNode { - GeoNear2DNode() : addPointMeta(false), addDistMeta(false) {} + GeoNear2DNode(IndexEntry index) + : index(std::move(index)), addPointMeta(false), addDistMeta(false) {} virtual ~GeoNear2DNode() {} virtual StageType getType() const { @@ -728,14 +727,15 @@ struct GeoNear2DNode : public QuerySolutionNode { const GeoNearExpression* nq; IndexBounds baseBounds; - BSONObj indexKeyPattern; + IndexEntry index; bool addPointMeta; bool addDistMeta; }; // This is actually its own standalone stage. struct GeoNear2DSphereNode : public QuerySolutionNode { - GeoNear2DSphereNode() : addPointMeta(false), addDistMeta(false) {} + GeoNear2DSphereNode(IndexEntry index) + : index(std::move(index)), addPointMeta(false), addDistMeta(false) {} virtual ~GeoNear2DSphereNode() {} virtual StageType getType() const { @@ -764,7 +764,7 @@ struct GeoNear2DSphereNode : public QuerySolutionNode { const GeoNearExpression* nq; IndexBounds baseBounds; - BSONObj indexKeyPattern; + IndexEntry index; bool addPointMeta; bool addDistMeta; }; @@ -846,7 +846,7 @@ struct KeepMutationsNode : public QuerySolutionNode { * *always* skip over the current key to the next key. */ struct DistinctNode : public QuerySolutionNode { - DistinctNode() {} + DistinctNode(IndexEntry index) : index(std::move(index)) {} virtual ~DistinctNode() {} virtual StageType getType() const { @@ -860,7 +860,7 @@ struct DistinctNode : public QuerySolutionNode { return false; } bool hasField(const std::string& field) const { - return !indexKeyPattern[field].eoo(); + return !index.keyPattern[field].eoo(); } bool sortedByDiskLoc() const { return false; @@ -873,10 +873,10 @@ struct DistinctNode : public QuerySolutionNode { BSONObjSet sorts; - BSONObj indexKeyPattern; + IndexEntry index; int direction; IndexBounds bounds; - // We are distinct-ing over the 'fieldNo'-th field of 'indexKeyPattern'. + // We are distinct-ing over the 'fieldNo'-th field of 'index.keyPattern'. int fieldNo; }; @@ -885,7 +885,7 @@ struct DistinctNode : public QuerySolutionNode { * Btree. */ struct CountScanNode : public QuerySolutionNode { - CountScanNode() {} + CountScanNode(IndexEntry index) : index(std::move(index)) {} virtual ~CountScanNode() {} virtual StageType getType() const { @@ -910,7 +910,7 @@ struct CountScanNode : public QuerySolutionNode { BSONObjSet sorts; - BSONObj indexKeyPattern; + IndexEntry index; BSONObj startKey; bool startKeyInclusive; diff --git a/src/mongo/db/query/query_solution_test.cpp b/src/mongo/db/query/query_solution_test.cpp index e80f62176ae..7654ca57867 100644 --- a/src/mongo/db/query/query_solution_test.cpp +++ b/src/mongo/db/query/query_solution_test.cpp @@ -33,6 +33,7 @@ #include "mongo/db/matcher/extensions_callback_disallow_extensions.h" #include "mongo/db/query/collation/collator_interface_mock.h" #include "mongo/db/query/index_bounds_builder.h" +#include "mongo/db/query/index_entry.h" #include "mongo/db/query/query_solution.h" #include "mongo/stdx/memory.h" #include "mongo/unittest/unittest.h" @@ -45,8 +46,7 @@ using namespace mongo; // Min: {a: 1, b: 1, c: 1, d: 1, e: 1} // Max: {a: 1, b: 1, c: 1, d: 1, e: 1} TEST(QuerySolutionTest, SimpleRangeAllEqual) { - IndexScanNode node{}; - node.indexKeyPattern = BSON("a" << 1 << "b" << 1 << "c" << 1 << "d" << 1 << "e" << 1); + IndexScanNode node{IndexEntry(BSON("a" << 1 << "b" << 1 << "c" << 1 << "d" << 1 << "e" << 1))}; node.bounds.isSimpleRange = true; node.bounds.startKey = BSON("a" << 1 << "b" << 1 << "c" << 1 << "d" << 1 << "e" << 1); node.bounds.endKey = BSON("a" << 1 << "b" << 1 << "c" << 1 << "d" << 1 << "e" << 1); @@ -69,8 +69,7 @@ TEST(QuerySolutionTest, SimpleRangeAllEqual) { // Min: {a: 1, b: 1, c: 1, d: 1, e: 1} // Max: {a: 2, b: 2, c: 2, d: 2, e: 2} TEST(QuerySolutionTest, SimpleRangeNoneEqual) { - IndexScanNode node{}; - node.indexKeyPattern = BSON("a" << 1 << "b" << 1 << "c" << 1 << "d" << 1 << "e" << 1); + IndexScanNode node{IndexEntry(BSON("a" << 1 << "b" << 1 << "c" << 1 << "d" << 1 << "e" << 1))}; node.bounds.isSimpleRange = true; node.bounds.startKey = BSON("a" << 1 << "b" << 1 << "c" << 1 << "d" << 1 << "e" << 1); node.bounds.endKey = BSON("a" << 2 << "b" << 2 << "c" << 2 << "d" << 2 << "e" << 2); @@ -89,8 +88,7 @@ TEST(QuerySolutionTest, SimpleRangeNoneEqual) { // Min: {a: 1, b: 1, c: 1, d: 1, e: 1} // Max: {a: 1, b: 1, c: 2, d: 2, e: 2} TEST(QuerySolutionTest, SimpleRangeSomeEqual) { - IndexScanNode node{}; - node.indexKeyPattern = BSON("a" << 1 << "b" << 1 << "c" << 1 << "d" << 1 << "e" << 1); + IndexScanNode node{IndexEntry(BSON("a" << 1 << "b" << 1 << "c" << 1 << "d" << 1 << "e" << 1))}; node.bounds.isSimpleRange = true; node.bounds.startKey = BSON("a" << 1 << "b" << 1 << "c" << 1 << "d" << 1 << "e" << 1); node.bounds.endKey = BSON("a" << 1 << "b" << 1 << "c" << 2 << "d" << 2 << "e" << 2); @@ -112,8 +110,7 @@ TEST(QuerySolutionTest, SimpleRangeSomeEqual) { // Index: {a: 1, b: 1, c: 1, d: 1, e: 1} // Intervals: a: [1,1], b: [1,1], c: [1,1], d: [1,1], e: [1,1] TEST(QuerySolutionTest, IntervalListAllPoints) { - IndexScanNode node{}; - node.indexKeyPattern = BSON("a" << 1 << "b" << 1 << "c" << 1 << "d" << 1 << "e" << 1); + IndexScanNode node{IndexEntry(BSON("a" << 1 << "b" << 1 << "c" << 1 << "d" << 1 << "e" << 1))}; OrderedIntervalList a{}; a.name = "a"; @@ -159,8 +156,7 @@ TEST(QuerySolutionTest, IntervalListAllPoints) { // Index: {a: 1, b: 1, c: 1, d: 1, e: 1} // Intervals: a: [1,2], b: [1,2], c: [1,2], d: [1,2], e: [1,2] TEST(QuerySolutionTest, IntervalListNoPoints) { - IndexScanNode node{}; - node.indexKeyPattern = BSON("a" << 1 << "b" << 1 << "c" << 1 << "d" << 1 << "e" << 1); + IndexScanNode node{IndexEntry(BSON("a" << 1 << "b" << 1 << "c" << 1 << "d" << 1 << "e" << 1))}; OrderedIntervalList a{}; a.name = "a"; @@ -206,8 +202,7 @@ TEST(QuerySolutionTest, IntervalListNoPoints) { // Index: {a: 1, b: 1, c: 1, d: 1, e: 1} // Intervals: a: [1,1], b: [1,1], c: [1,2], d: [1,2], e: [1,2] TEST(QuerySolutionTest, IntervalListSomePoints) { - IndexScanNode node{}; - node.indexKeyPattern = BSON("a" << 1 << "b" << 1 << "c" << 1 << "d" << 1 << "e" << 1); + IndexScanNode node{IndexEntry(BSON("a" << 1 << "b" << 1 << "c" << 1 << "d" << 1 << "e" << 1))}; OrderedIntervalList a{}; a.name = "a"; @@ -368,10 +363,8 @@ TEST(QuerySolutionTest, GetFieldsWithStringBoundsIdentifiesStringsFromObjectType } TEST(QuerySolutionTest, IndexScanNodeRemovesNonMatchingCollatedFieldsFromSortsOnSimpleBounds) { - IndexScanNode node{}; + IndexScanNode node{IndexEntry(BSON("a" << 1 << "b" << 1))}; CollatorInterfaceMock queryCollator(CollatorInterfaceMock::MockType::kReverseString); - - node.indexKeyPattern = BSON("a" << 1 << "b" << 1); node.queryCollator = &queryCollator; node.bounds.isSimpleRange = true; @@ -387,10 +380,8 @@ TEST(QuerySolutionTest, IndexScanNodeRemovesNonMatchingCollatedFieldsFromSortsOn } TEST(QuerySolutionTest, IndexScanNodeGetFieldsWithStringBoundsCorrectlyHandlesEndKeyInclusive) { - IndexScanNode node{}; + IndexScanNode node{IndexEntry(BSON("a" << 1 << "b" << 1))}; CollatorInterfaceMock queryCollator(CollatorInterfaceMock::MockType::kReverseString); - - node.indexKeyPattern = BSON("a" << 1 << "b" << 1); node.queryCollator = &queryCollator; node.bounds.isSimpleRange = true; @@ -419,10 +410,8 @@ TEST(QuerySolutionTest, IndexScanNodeGetFieldsWithStringBoundsCorrectlyHandlesEn // Index: {a: 1} // Bounds: [MINKEY, MAXKEY] TEST(QuerySolutionTest, IndexScanNodeRemovesCollatedFieldsFromSortsIfCollationDifferent) { - IndexScanNode node{}; + IndexScanNode node{IndexEntry(BSON("a" << 1))}; CollatorInterfaceMock queryCollator(CollatorInterfaceMock::MockType::kReverseString); - - node.indexKeyPattern = BSON("a" << 1); node.queryCollator = &queryCollator; OrderedIntervalList oilA{}; @@ -438,11 +427,9 @@ TEST(QuerySolutionTest, IndexScanNodeRemovesCollatedFieldsFromSortsIfCollationDi } TEST(QuerySolutionTest, IndexScanNodeDoesNotRemoveCollatedFieldsFromSortsIfCollationMatches) { - IndexScanNode node{}; + IndexScanNode node{IndexEntry(BSON("a" << 1))}; CollatorInterfaceMock queryCollator(CollatorInterfaceMock::MockType::kReverseString); - node.indexKeyPattern = BSON("a" << 1); - OrderedIntervalList oilA{}; oilA.name = "a"; oilA.intervals.push_back( @@ -459,11 +446,11 @@ TEST(QuerySolutionTest, IndexScanNodeDoesNotRemoveCollatedFieldsFromSortsIfColla // Index: {a: 1, b: 1, c: 1, d: 1, e: 1} // Intervals: a: [1,1], b: [1,1], c: [MinKey, MaxKey], d: [1,2], e: [1,2] TEST(QuerySolutionTest, CompoundIndexWithNonMatchingCollationFiltersAllSortsWithCollatedField) { - IndexScanNode node{}; + IndexScanNode node{IndexEntry(BSON("a" << 1 << "b" << 1 << "c" << 1 << "d" << 1 << "e" << 1))}; CollatorInterfaceMock queryCollator(CollatorInterfaceMock::MockType::kReverseString); node.queryCollator = &queryCollator; - node.indexKeyPattern = BSON("a" << 1 << "b" << 1 << "c" << 1 << "d" << 1 << "e" << 1); + node.index = IndexEntry(BSON("a" << 1 << "b" << 1 << "c" << 1 << "d" << 1 << "e" << 1)); OrderedIntervalList a{}; a.name = "a"; @@ -504,10 +491,8 @@ TEST(QuerySolutionTest, CompoundIndexWithNonMatchingCollationFiltersAllSortsWith // Index: {a : 1} // Bounds: [{}, {}] TEST(QuerySolutionTest, IndexScanNodeWithNonMatchingCollationFiltersObjectField) { - IndexScanNode node{}; + IndexScanNode node{IndexEntry(BSON("a" << 1))}; CollatorInterfaceMock queryCollator(CollatorInterfaceMock::MockType::kReverseString); - - node.indexKeyPattern = BSON("a" << 1); node.queryCollator = &queryCollator; OrderedIntervalList oilA{}; @@ -525,10 +510,8 @@ TEST(QuerySolutionTest, IndexScanNodeWithNonMatchingCollationFiltersObjectField) // Index: {a : 1} // Bounds: [[], []] TEST(QuerySolutionTest, IndexScanNodeWithNonMatchingCollationFiltersArrayField) { - IndexScanNode node{}; + IndexScanNode node{IndexEntry(BSON("a" << 1))}; CollatorInterfaceMock queryCollator(CollatorInterfaceMock::MockType::kReverseString); - - node.indexKeyPattern = BSON("a" << 1); node.queryCollator = &queryCollator; OrderedIntervalList oilA{}; @@ -544,12 +527,10 @@ TEST(QuerySolutionTest, IndexScanNodeWithNonMatchingCollationFiltersArrayField) } TEST(QuerySolutionTest, WithNonMatchingCollatorAndNoEqualityPrefixSortsAreNotDuplicated) { - IndexScanNode node{}; + IndexScanNode node{IndexEntry(BSON("a" << 1 << "b" << 1))}; CollatorInterfaceMock queryCollator(CollatorInterfaceMock::MockType::kReverseString); node.queryCollator = &queryCollator; - node.indexKeyPattern = BSON("a" << 1 << "b" << 1); - OrderedIntervalList oilA{}; oilA.name = "a"; oilA.intervals.push_back( @@ -593,8 +574,8 @@ std::unique_ptr<ParsedProjection> createParsedProjection(const BSONObj& query, } TEST(QuerySolutionTest, InclusionProjectionPreservesSort) { - auto node = stdx::make_unique<IndexScanNode>(); - node->indexKeyPattern = BSON("a" << 1); + IndexEntry index(BSON("a" << 1)); + auto node = stdx::make_unique<IndexScanNode>(index); BSONObj projection = BSON("a" << 1); BSONObj match; @@ -610,8 +591,8 @@ TEST(QuerySolutionTest, InclusionProjectionPreservesSort) { } TEST(QuerySolutionTest, ExclusionProjectionDoesNotPreserveSort) { - auto node = stdx::make_unique<IndexScanNode>(); - node->indexKeyPattern = BSON("a" << 1); + IndexEntry index(BSON("a" << 1)); + auto node = stdx::make_unique<IndexScanNode>(index); BSONObj projection = BSON("a" << 0); BSONObj match; @@ -626,8 +607,7 @@ TEST(QuerySolutionTest, ExclusionProjectionDoesNotPreserveSort) { } TEST(QuerySolutionTest, InclusionProjectionTruncatesSort) { - auto node = stdx::make_unique<IndexScanNode>(); - node->indexKeyPattern = BSON("a" << 1 << "b" << 1); + auto node = stdx::make_unique<IndexScanNode>(IndexEntry(BSON("a" << 1 << "b" << 1))); BSONObj projection = BSON("a" << 1); BSONObj match; @@ -643,8 +623,7 @@ TEST(QuerySolutionTest, InclusionProjectionTruncatesSort) { } TEST(QuerySolutionTest, ExclusionProjectionTruncatesSort) { - auto node = stdx::make_unique<IndexScanNode>(); - node->indexKeyPattern = BSON("a" << 1 << "b" << 1); + auto node = stdx::make_unique<IndexScanNode>(IndexEntry(BSON("a" << 1 << "b" << 1))); BSONObj projection = BSON("b" << 0); BSONObj match; diff --git a/src/mongo/db/query/stage_builder.cpp b/src/mongo/db/query/stage_builder.cpp index 9df048b60a0..e790325a439 100644 --- a/src/mongo/db/query/stage_builder.cpp +++ b/src/mongo/db/query/stage_builder.cpp @@ -90,8 +90,7 @@ PlanStage* buildStages(OperationContext* txn, IndexScanParams params; - params.descriptor = - collection->getIndexCatalog()->findIndexByKeyPattern(txn, ixn->indexKeyPattern); + params.descriptor = collection->getIndexCatalog()->findIndexByName(txn, ixn->index.name); invariant(params.descriptor); params.bounds = ixn->bounds; @@ -223,7 +222,7 @@ PlanStage* buildStages(OperationContext* txn, params.addDistMeta = node->addDistMeta; IndexDescriptor* twoDIndex = - collection->getIndexCatalog()->findIndexByKeyPattern(txn, node->indexKeyPattern); + collection->getIndexCatalog()->findIndexByName(txn, node->index.name); invariant(twoDIndex); GeoNear2DStage* nearStage = new GeoNear2DStage(params, txn, ws, collection, twoDIndex); @@ -240,14 +239,14 @@ PlanStage* buildStages(OperationContext* txn, params.addDistMeta = node->addDistMeta; IndexDescriptor* s2Index = - collection->getIndexCatalog()->findIndexByKeyPattern(txn, node->indexKeyPattern); + collection->getIndexCatalog()->findIndexByName(txn, node->index.name); invariant(s2Index); return new GeoNear2DSphereStage(params, txn, ws, collection, s2Index); } else if (STAGE_TEXT == root->getType()) { const TextNode* node = static_cast<const TextNode*>(root); IndexDescriptor* desc = - collection->getIndexCatalog()->findIndexByKeyPattern(txn, node->indexKeyPattern); + collection->getIndexCatalog()->findIndexByName(txn, node->index.name); invariant(desc); const FTSAccessMethod* fam = static_cast<FTSAccessMethod*>(collection->getIndexCatalog()->getIndex(desc)); @@ -290,8 +289,7 @@ PlanStage* buildStages(OperationContext* txn, DistinctParams params; - params.descriptor = - collection->getIndexCatalog()->findIndexByKeyPattern(txn, dn->indexKeyPattern); + params.descriptor = collection->getIndexCatalog()->findIndexByName(txn, dn->index.name); invariant(params.descriptor); params.direction = dn->direction; params.bounds = dn->bounds; @@ -307,8 +305,7 @@ PlanStage* buildStages(OperationContext* txn, CountScanParams params; - params.descriptor = - collection->getIndexCatalog()->findIndexByKeyPattern(txn, csn->indexKeyPattern); + params.descriptor = collection->getIndexCatalog()->findIndexByName(txn, csn->index.name); invariant(params.descriptor); params.startKey = csn->startKey; params.startKeyInclusive = csn->startKeyInclusive; diff --git a/src/mongo/db/repl/oplog_buffer_collection.cpp b/src/mongo/db/repl/oplog_buffer_collection.cpp index 818a1e205fd..90a8f120c97 100644 --- a/src/mongo/db/repl/oplog_buffer_collection.cpp +++ b/src/mongo/db/repl/oplog_buffer_collection.cpp @@ -47,7 +47,7 @@ namespace { const char kDefaultOplogCollectionNamespace[] = "local.temp_oplog_buffer"; const char kOplogEntryFieldName[] = "entry"; -const BSONObj kIdObj = BSON("_id" << 1); +const StringData kIdIdxName = "_id_"_sd; } // namespace @@ -222,7 +222,7 @@ bool OplogBufferCollection::_doPop_inlock(OperationContext* txn, Value* value) { return true; } auto scanDirection = StorageInterface::ScanDirection::kForward; - auto result = _storageInterface->deleteOne(txn, _nss, kIdObj, scanDirection); + auto result = _storageInterface->deleteOne(txn, _nss, kIdIdxName, scanDirection); if (!result.isOK()) { if (result != ErrorCodes::CollectionIsEmpty) { fassert(40162, result.getStatus()); @@ -249,7 +249,7 @@ bool OplogBufferCollection::_peekOneSide_inlock(OperationContext* txn, } auto scanDirection = front ? StorageInterface::ScanDirection::kForward : StorageInterface::ScanDirection::kBackward; - auto result = _storageInterface->findOne(txn, _nss, kIdObj, scanDirection); + auto result = _storageInterface->findOne(txn, _nss, kIdIdxName, scanDirection); if (!result.isOK()) { if (result != ErrorCodes::CollectionIsEmpty) { fassert(40163, result.getStatus()); diff --git a/src/mongo/db/repl/storage_interface.h b/src/mongo/db/repl/storage_interface.h index cf06e9caae0..25873b366ea 100644 --- a/src/mongo/db/repl/storage_interface.h +++ b/src/mongo/db/repl/storage_interface.h @@ -29,10 +29,12 @@ #pragma once +#include <boost/optional.hpp> #include <iosfwd> #include <string> #include "mongo/base/disallow_copying.h" +#include "mongo/base/string_data.h" #include "mongo/db/namespace_string.h" #include "mongo/db/repl/collection_bulk_loader.h" #include "mongo/db/repl/optime.h" @@ -219,7 +221,7 @@ public: /** * Finds the first document returned by a collection or index scan on the collection in the * requested direction. - * If "indexKeyPattern" is empty, a collection scan is used to locate the document. + * If "indexName" is boost::none, a collection scan is used to locate the document. */ enum class ScanDirection { kForward = 1, @@ -227,17 +229,17 @@ public: }; virtual StatusWith<BSONObj> findOne(OperationContext* txn, const NamespaceString& nss, - const BSONObj& indexKeyPattern, + boost::optional<StringData> indexName, ScanDirection scanDirection) = 0; /** * Deletes the first document returned by a collection or index scan on the collection in the * requested direction. Returns deleted document on success. - * If "indexKeyPattern" is empty, a collection scan is used to locate the document. + * If "indexName" is null, a collection scan is used to locate the document. */ virtual StatusWith<BSONObj> deleteOne(OperationContext* txn, const NamespaceString& nss, - const BSONObj& indexKeyPattern, + boost::optional<StringData> indexName, ScanDirection scanDirection) = 0; }; diff --git a/src/mongo/db/repl/storage_interface_impl.cpp b/src/mongo/db/repl/storage_interface_impl.cpp index e7d410b978e..f2fe3bcf5e2 100644 --- a/src/mongo/db/repl/storage_interface_impl.cpp +++ b/src/mongo/db/repl/storage_interface_impl.cpp @@ -33,10 +33,12 @@ #include "mongo/db/repl/storage_interface_impl.h" #include <algorithm> +#include <boost/optional.hpp> #include <utility> #include "mongo/base/status.h" #include "mongo/base/status_with.h" +#include "mongo/base/string_data.h" #include "mongo/bson/bsonobj.h" #include "mongo/bson/bsonobjbuilder.h" #include "mongo/db/catalog/collection.h" @@ -412,7 +414,7 @@ DeleteStageParams makeDeleteStageParamsForDeleteOne() { enum class FindDeleteMode { kFind, kDelete }; StatusWith<BSONObj> _findOrDeleteOne(OperationContext* txn, const NamespaceString& nss, - const BSONObj& indexKeyPattern, + boost::optional<StringData> indexName, StorageInterface::ScanDirection scanDirection, FindDeleteMode mode) { auto isFind = mode == FindDeleteMode::kFind; @@ -432,7 +434,7 @@ StatusWith<BSONObj> _findOrDeleteOne(OperationContext* txn, auto direction = isForward ? InternalPlanner::FORWARD : InternalPlanner::BACKWARD; std::unique_ptr<PlanExecutor> planExecutor; - if (indexKeyPattern.isEmpty()) { + if (!indexName) { // Use collection scan. planExecutor = isFind ? InternalPlanner::collectionScan( @@ -447,22 +449,22 @@ StatusWith<BSONObj> _findOrDeleteOne(OperationContext* txn, auto indexCatalog = collection->getIndexCatalog(); invariant(indexCatalog); bool includeUnfinishedIndexes = false; - auto indexDescriptor = - indexCatalog->findIndexByKeyPattern(txn, indexKeyPattern, includeUnfinishedIndexes); + IndexDescriptor* indexDescriptor = + indexCatalog->findIndexByName(txn, *indexName, includeUnfinishedIndexes); if (!indexDescriptor) { return {ErrorCodes::IndexNotFound, str::stream() << "Index not found, ns:" << nss.ns() << ", index: " - << indexKeyPattern}; + << *indexName}; } if (indexDescriptor->isPartial()) { return {ErrorCodes::IndexOptionsConflict, str::stream() << "Partial index is not allowed for this operation, ns:" << nss.ns() << ", index: " - << indexKeyPattern}; + << *indexName}; } - KeyPattern keyPattern(indexKeyPattern); + KeyPattern keyPattern(indexDescriptor->keyPattern()); auto minKey = Helpers::toKeyFormat(keyPattern.extendRangeBound({}, false)); auto maxKey = Helpers::toKeyFormat(keyPattern.extendRangeBound({}, true)); auto bounds = @@ -494,8 +496,7 @@ StatusWith<BSONObj> _findOrDeleteOne(OperationContext* txn, auto state = planExecutor->getNext(&doc, nullptr); if (PlanExecutor::IS_EOF == state) { return {ErrorCodes::CollectionIsEmpty, - str::stream() << "Collection is empty, ns: " << nss.ns() << ", index: " - << indexKeyPattern}; + str::stream() << "Collection is empty, ns: " << nss.ns()}; } invariant(PlanExecutor::ADVANCED == state); return doc; @@ -508,16 +509,16 @@ StatusWith<BSONObj> _findOrDeleteOne(OperationContext* txn, StatusWith<BSONObj> StorageInterfaceImpl::findOne(OperationContext* txn, const NamespaceString& nss, - const BSONObj& indexKeyPattern, + boost::optional<StringData> indexName, ScanDirection scanDirection) { - return _findOrDeleteOne(txn, nss, indexKeyPattern, scanDirection, FindDeleteMode::kFind); + return _findOrDeleteOne(txn, nss, indexName, scanDirection, FindDeleteMode::kFind); } StatusWith<BSONObj> StorageInterfaceImpl::deleteOne(OperationContext* txn, const NamespaceString& nss, - const BSONObj& indexKeyPattern, + boost::optional<StringData> indexName, ScanDirection scanDirection) { - return _findOrDeleteOne(txn, nss, indexKeyPattern, scanDirection, FindDeleteMode::kDelete); + return _findOrDeleteOne(txn, nss, indexName, scanDirection, FindDeleteMode::kDelete); } Status StorageInterfaceImpl::isAdminDbValid(OperationContext* txn) { diff --git a/src/mongo/db/repl/storage_interface_impl.h b/src/mongo/db/repl/storage_interface_impl.h index ff8a59b1ecd..7e45c77fae2 100644 --- a/src/mongo/db/repl/storage_interface_impl.h +++ b/src/mongo/db/repl/storage_interface_impl.h @@ -32,6 +32,7 @@ #include "mongo/base/disallow_copying.h" #include "mongo/base/status.h" #include "mongo/base/status_with.h" +#include "mongo/base/string_data.h" #include "mongo/bson/bsonobj.h" #include "mongo/db/catalog/index_create.h" #include "mongo/db/db_raii.h" @@ -106,12 +107,12 @@ public: StatusWith<BSONObj> findOne(OperationContext* txn, const NamespaceString& nss, - const BSONObj& indexKeyPattern, + boost::optional<StringData> indexName, ScanDirection scanDirection) override; StatusWith<BSONObj> deleteOne(OperationContext* txn, const NamespaceString& nss, - const BSONObj& indexKeyPattern, + boost::optional<StringData> indexName, ScanDirection scanDirection) override; Status isAdminDbValid(OperationContext* txn) override; diff --git a/src/mongo/db/repl/storage_interface_impl_test.cpp b/src/mongo/db/repl/storage_interface_impl_test.cpp index 43da79635cb..8c56cfc5fad 100644 --- a/src/mongo/db/repl/storage_interface_impl_test.cpp +++ b/src/mongo/db/repl/storage_interface_impl_test.cpp @@ -28,6 +28,7 @@ #include "mongo/platform/basic.h" +#include <boost/optional.hpp> #include <memory> #include "mongo/bson/bsonmisc.h" @@ -549,19 +550,19 @@ TEST_F(StorageInterfaceImplWithReplCoordTest, FindOneReturnsInvalidNamespaceIfCo auto txn = getOperationContext(); StorageInterfaceImpl storage; auto nss = makeNamespace(_agent); - auto keyPattern = BSON("_id" << 1); + auto indexName = "_id_"_sd; ASSERT_EQUALS(ErrorCodes::NamespaceNotFound, - storage.findOne(txn, nss, keyPattern, StorageInterface::ScanDirection::kForward)); + storage.findOne(txn, nss, indexName, StorageInterface::ScanDirection::kForward)); } TEST_F(StorageInterfaceImplWithReplCoordTest, FindOneReturnsIndexNotFoundIfIndexIsMissing) { auto txn = getOperationContext(); StorageInterfaceImpl storage; auto nss = makeNamespace(_agent); - auto keyPattern = BSON("x" << 1); + auto indexName = "nonexistent"_sd; ASSERT_OK(storage.createCollection(txn, nss, CollectionOptions())); ASSERT_EQUALS(ErrorCodes::IndexNotFound, - storage.findOne(txn, nss, keyPattern, StorageInterface::ScanDirection::kForward)); + storage.findOne(txn, nss, indexName, StorageInterface::ScanDirection::kForward)); } TEST_F(StorageInterfaceImplWithReplCoordTest, @@ -581,19 +582,19 @@ TEST_F(StorageInterfaceImplWithReplCoordTest, std::vector<BSONObj> docs = {BSON("_id" << 1), BSON("_id" << 1), BSON("_id" << 2)}; ASSERT_OK(loader->insertDocuments(docs.begin(), docs.end())); ASSERT_OK(loader->commit()); - auto keyPattern = BSON("x" << 1); + auto indexName = "x_1"_sd; ASSERT_EQUALS(ErrorCodes::IndexOptionsConflict, - storage.findOne(txn, nss, keyPattern, StorageInterface::ScanDirection::kForward)); + storage.findOne(txn, nss, indexName, StorageInterface::ScanDirection::kForward)); } TEST_F(StorageInterfaceImplWithReplCoordTest, FindOneReturnsCollectionIsEmptyIfCollectionIsEmpty) { auto txn = getOperationContext(); StorageInterfaceImpl storage; auto nss = makeNamespace(_agent); - auto keyPattern = BSON("_id" << 1); + auto indexName = "_id_"_sd; ASSERT_OK(storage.createCollection(txn, nss, CollectionOptions())); ASSERT_EQUALS(ErrorCodes::CollectionIsEmpty, - storage.findOne(txn, nss, keyPattern, StorageInterface::ScanDirection::kForward)); + storage.findOne(txn, nss, indexName, StorageInterface::ScanDirection::kForward)); } TEST_F(StorageInterfaceImplWithReplCoordTest, @@ -601,12 +602,12 @@ TEST_F(StorageInterfaceImplWithReplCoordTest, auto txn = getOperationContext(); StorageInterfaceImpl storage; auto nss = makeNamespace(_agent); - auto keyPattern = BSON("_id" << 1); + auto indexName = "_id_"_sd; ASSERT_OK(storage.createCollection(txn, nss, CollectionOptions())); ASSERT_OK( storage.insertDocuments(txn, nss, {BSON("_id" << 0), BSON("_id" << 1), BSON("_id" << 2)})); ASSERT_EQUALS(BSON("_id" << 0), - storage.findOne(txn, nss, keyPattern, StorageInterface::ScanDirection::kForward)); + storage.findOne(txn, nss, indexName, StorageInterface::ScanDirection::kForward)); // Check collection contents. OplogInterface returns documents in reverse natural order. OplogInterfaceLocal oplog(txn, nss.ns()); @@ -622,13 +623,12 @@ TEST_F(StorageInterfaceImplWithReplCoordTest, auto txn = getOperationContext(); StorageInterfaceImpl storage; auto nss = makeNamespace(_agent); - auto keyPattern = BSON("_id" << 1); + auto indexName = "_id_"_sd; ASSERT_OK(storage.createCollection(txn, nss, CollectionOptions())); ASSERT_OK( storage.insertDocuments(txn, nss, {BSON("_id" << 0), BSON("_id" << 1), BSON("_id" << 2)})); - ASSERT_EQUALS( - BSON("_id" << 2), - storage.findOne(txn, nss, keyPattern, StorageInterface::ScanDirection::kBackward)); + ASSERT_EQUALS(BSON("_id" << 2), + storage.findOne(txn, nss, indexName, StorageInterface::ScanDirection::kBackward)); // Check collection contents. OplogInterface returns documents in reverse natural order. OplogInterfaceLocal oplog(txn, nss.ns()); @@ -647,8 +647,9 @@ TEST_F(StorageInterfaceImplWithReplCoordTest, ASSERT_OK(storage.createCollection(txn, nss, CollectionOptions())); ASSERT_OK( storage.insertDocuments(txn, nss, {BSON("_id" << 1), BSON("_id" << 2), BSON("_id" << 0)})); - ASSERT_EQUALS(BSON("_id" << 1), - storage.findOne(txn, nss, BSONObj(), StorageInterface::ScanDirection::kForward)); + ASSERT_EQUALS( + BSON("_id" << 1), + storage.findOne(txn, nss, boost::none, StorageInterface::ScanDirection::kForward)); // Check collection contents. OplogInterface returns documents in reverse natural order. OplogInterfaceLocal oplog(txn, nss.ns()); @@ -667,8 +668,9 @@ TEST_F(StorageInterfaceImplWithReplCoordTest, ASSERT_OK(storage.createCollection(txn, nss, CollectionOptions())); ASSERT_OK( storage.insertDocuments(txn, nss, {BSON("_id" << 1), BSON("_id" << 2), BSON("_id" << 0)})); - ASSERT_EQUALS(BSON("_id" << 0), - storage.findOne(txn, nss, BSONObj(), StorageInterface::ScanDirection::kBackward)); + ASSERT_EQUALS( + BSON("_id" << 0), + storage.findOne(txn, nss, boost::none, StorageInterface::ScanDirection::kBackward)); // Check collection contents. OplogInterface returns documents in reverse natural order. OplogInterfaceLocal oplog(txn, nss.ns()); @@ -684,21 +686,21 @@ TEST_F(StorageInterfaceImplWithReplCoordTest, auto txn = getOperationContext(); StorageInterfaceImpl storage; auto nss = makeNamespace(_agent); - auto keyPattern = BSON("_id" << 1); + auto indexName = "_id_"_sd; ASSERT_EQUALS( ErrorCodes::NamespaceNotFound, - storage.deleteOne(txn, nss, keyPattern, StorageInterface::ScanDirection::kForward)); + storage.deleteOne(txn, nss, indexName, StorageInterface::ScanDirection::kForward)); } TEST_F(StorageInterfaceImplWithReplCoordTest, DeleteOneReturnsIndexNotFoundIfIndexIsMissing) { auto txn = getOperationContext(); StorageInterfaceImpl storage; auto nss = makeNamespace(_agent); - auto keyPattern = BSON("x" << 1); + auto indexName = "nonexistent"_sd; ASSERT_OK(storage.createCollection(txn, nss, CollectionOptions())); ASSERT_EQUALS( ErrorCodes::IndexNotFound, - storage.deleteOne(txn, nss, keyPattern, StorageInterface::ScanDirection::kForward)); + storage.deleteOne(txn, nss, indexName, StorageInterface::ScanDirection::kForward)); } TEST_F(StorageInterfaceImplWithReplCoordTest, @@ -706,11 +708,11 @@ TEST_F(StorageInterfaceImplWithReplCoordTest, auto txn = getOperationContext(); StorageInterfaceImpl storage; auto nss = makeNamespace(_agent); - auto keyPattern = BSON("_id" << 1); + auto indexName = "_id_"_sd; ASSERT_OK(storage.createCollection(txn, nss, CollectionOptions())); ASSERT_EQUALS( ErrorCodes::CollectionIsEmpty, - storage.deleteOne(txn, nss, keyPattern, StorageInterface::ScanDirection::kForward)); + storage.deleteOne(txn, nss, indexName, StorageInterface::ScanDirection::kForward)); } TEST_F(StorageInterfaceImplWithReplCoordTest, @@ -718,13 +720,13 @@ TEST_F(StorageInterfaceImplWithReplCoordTest, auto txn = getOperationContext(); StorageInterfaceImpl storage; auto nss = makeNamespace(_agent); - auto keyPattern = BSON("_id" << 1); + auto indexName = "_id_"_sd; ASSERT_OK(storage.createCollection(txn, nss, CollectionOptions())); ASSERT_OK( storage.insertDocuments(txn, nss, {BSON("_id" << 0), BSON("_id" << 1), BSON("_id" << 2)})); ASSERT_EQUALS( BSON("_id" << 0), - storage.deleteOne(txn, nss, keyPattern, StorageInterface::ScanDirection::kForward)); + storage.deleteOne(txn, nss, indexName, StorageInterface::ScanDirection::kForward)); // Check collection contents. OplogInterface returns documents in reverse natural order. OplogInterfaceLocal oplog(txn, nss.ns()); @@ -739,13 +741,13 @@ TEST_F(StorageInterfaceImplWithReplCoordTest, auto txn = getOperationContext(); StorageInterfaceImpl storage; auto nss = makeNamespace(_agent); - auto keyPattern = BSON("_id" << 1); + auto indexName = "_id_"_sd; ASSERT_OK(storage.createCollection(txn, nss, CollectionOptions())); ASSERT_OK( storage.insertDocuments(txn, nss, {BSON("_id" << 0), BSON("_id" << 1), BSON("_id" << 2)})); ASSERT_EQUALS( BSON("_id" << 2), - storage.deleteOne(txn, nss, keyPattern, StorageInterface::ScanDirection::kBackward)); + storage.deleteOne(txn, nss, indexName, StorageInterface::ScanDirection::kBackward)); // Check collection contents. OplogInterface returns documents in reverse natural order. OplogInterfaceLocal oplog(txn, nss.ns()); @@ -765,7 +767,7 @@ TEST_F(StorageInterfaceImplWithReplCoordTest, storage.insertDocuments(txn, nss, {BSON("_id" << 1), BSON("_id" << 2), BSON("_id" << 0)})); ASSERT_EQUALS( BSON("_id" << 1), - storage.deleteOne(txn, nss, BSONObj(), StorageInterface::ScanDirection::kForward)); + storage.deleteOne(txn, nss, boost::none, StorageInterface::ScanDirection::kForward)); // Check collection contents. OplogInterface returns documents in reverse natural order. OplogInterfaceLocal oplog(txn, nss.ns()); @@ -785,7 +787,7 @@ TEST_F(StorageInterfaceImplWithReplCoordTest, storage.insertDocuments(txn, nss, {BSON("_id" << 1), BSON("_id" << 2), BSON("_id" << 0)})); ASSERT_EQUALS( BSON("_id" << 0), - storage.deleteOne(txn, nss, BSONObj(), StorageInterface::ScanDirection::kBackward)); + storage.deleteOne(txn, nss, boost::none, StorageInterface::ScanDirection::kBackward)); // Check collection contents. OplogInterface returns documents in reverse natural order. OplogInterfaceLocal oplog(txn, nss.ns()); diff --git a/src/mongo/db/repl/storage_interface_mock.h b/src/mongo/db/repl/storage_interface_mock.h index 4b19194832d..30f868f0520 100644 --- a/src/mongo/db/repl/storage_interface_mock.h +++ b/src/mongo/db/repl/storage_interface_mock.h @@ -32,6 +32,7 @@ #include "mongo/base/disallow_copying.h" #include "mongo/base/status.h" #include "mongo/base/status_with.h" +#include "mongo/base/string_data.h" #include "mongo/bson/bsonobj.h" #include "mongo/db/namespace_string.h" #include "mongo/db/repl/storage_interface.h" @@ -107,11 +108,11 @@ public: stdx::function<Status(OperationContext* txn, const NamespaceString& nss)>; using FindOneFn = stdx::function<StatusWith<BSONObj>(OperationContext* txn, const NamespaceString& nss, - const BSONObj& indexKeyPattern, + boost::optional<StringData> indexName, ScanDirection scanDirection)>; using DeleteOneFn = stdx::function<StatusWith<BSONObj>(OperationContext* txn, const NamespaceString& nss, - const BSONObj& indexKeyPattern, + boost::optional<StringData> indexName, ScanDirection scanDirection)>; StorageInterfaceMock() = default; @@ -169,16 +170,16 @@ public: StatusWith<BSONObj> findOne(OperationContext* txn, const NamespaceString& nss, - const BSONObj& indexKeyPattern, + boost::optional<StringData> indexName, ScanDirection scanDirection) override { - return findOneFn(txn, nss, indexKeyPattern, scanDirection); + return findOneFn(txn, nss, indexName, scanDirection); } StatusWith<BSONObj> deleteOne(OperationContext* txn, const NamespaceString& nss, - const BSONObj& indexKeyPattern, + boost::optional<StringData> indexName, ScanDirection scanDirection) override { - return deleteOneFn(txn, nss, indexKeyPattern, scanDirection); + return deleteOneFn(txn, nss, indexName, scanDirection); } Status isAdminDbValid(OperationContext* txn) override { @@ -218,13 +219,13 @@ public: }; FindOneFn findOneFn = [](OperationContext* txn, const NamespaceString& nss, - const BSONObj& indexKeyPattern, + boost::optional<StringData> indexName, ScanDirection scanDirection) { return Status{ErrorCodes::IllegalOperation, "FindOneFn not implemented."}; }; DeleteOneFn deleteOneFn = [](OperationContext* txn, const NamespaceString& nss, - const BSONObj& indexKeyPattern, + boost::optional<StringData> indexName, ScanDirection scanDirection) { return Status{ErrorCodes::IllegalOperation, "DeleteOneFn not implemented."}; }; diff --git a/src/mongo/db/ttl.cpp b/src/mongo/db/ttl.cpp index bd5f863258a..917f0237c10 100644 --- a/src/mongo/db/ttl.cpp +++ b/src/mongo/db/ttl.cpp @@ -182,12 +182,13 @@ private: } const BSONObj key = idx["key"].Obj(); + const StringData name = idx["name"].valueStringData(); if (key.nFields() != 1) { error() << "key for ttl index can only have 1 field, skipping ttl job for: " << idx; return; } - LOG(1) << "TTL -- ns: " << collectionNSS << " key: " << key; + LOG(1) << "TTL -- ns: " << collectionNSS << " key: " << key << " name: " << name; AutoGetCollection autoGetCollection(txn, collectionNSS, MODE_IX); Collection* collection = autoGetCollection.getCollection(); @@ -202,7 +203,7 @@ private: return; } - IndexDescriptor* desc = collection->getIndexCatalog()->findIndexByKeyPattern(txn, key); + IndexDescriptor* desc = collection->getIndexCatalog()->findIndexByName(txn, name); if (!desc) { LOG(1) << "index not found (index build in progress? index dropped?), skipping " << "ttl job for: " << idx; |