summaryrefslogtreecommitdiff
path: root/src/mongo/db/index
diff options
context:
space:
mode:
authorBernard Gorman <bernard.gorman@gmail.com>2020-02-27 17:31:30 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-04-25 20:23:43 +0000
commit085ffeb310e8fed49739cf8443fcb13ea795d867 (patch)
tree8c5c0b5681ece32c285cf65345f6c98f8f087f6c /src/mongo/db/index
parentb4fedf13f77347b4be11053b59d01f80b769fd7c (diff)
downloadmongo-085ffeb310e8fed49739cf8443fcb13ea795d867.tar.gz
SERVER-25023 Allow multiple indexes on the same fields with different partial index filters
Diffstat (limited to 'src/mongo/db/index')
-rw-r--r--src/mongo/db/index/SConscript2
-rw-r--r--src/mongo/db/index/index_descriptor.cpp80
-rw-r--r--src/mongo/db/index/index_descriptor.h14
3 files changed, 80 insertions, 16 deletions
diff --git a/src/mongo/db/index/SConscript b/src/mongo/db/index/SConscript
index fcff88d1e14..eaa3ebaaf40 100644
--- a/src/mongo/db/index/SConscript
+++ b/src/mongo/db/index/SConscript
@@ -17,6 +17,8 @@ env.Library(
LIBDEPS_PRIVATE=[
'$BUILD_DIR/mongo/db/index_names',
'$BUILD_DIR/mongo/db/namespace_string',
+ '$BUILD_DIR/mongo/db/matcher/expressions',
+ '$BUILD_DIR/mongo/db/query/collation/collator_factory_interface',
],
)
diff --git a/src/mongo/db/index/index_descriptor.cpp b/src/mongo/db/index/index_descriptor.cpp
index 17f765d87cf..c4da010008c 100644
--- a/src/mongo/db/index/index_descriptor.cpp
+++ b/src/mongo/db/index/index_descriptor.cpp
@@ -34,6 +34,8 @@
#include "mongo/db/catalog/collection.h"
#include "mongo/db/catalog/index_catalog.h"
#include "mongo/db/index/index_descriptor.h"
+#include "mongo/db/matcher/expression_parser.h"
+#include "mongo/db/query/collation/collator_factory_interface.h"
#include <algorithm>
@@ -50,9 +52,9 @@ void populateOptionsMap(std::map<StringData, BSONElement>& theMap, const BSONObj
const BSONElement e = it.next();
StringData fieldName = e.fieldNameStringData();
- if (fieldName == IndexDescriptor::kKeyPatternFieldName ||
- fieldName == IndexDescriptor::kNamespaceFieldName || // removed in 4.4
- fieldName == IndexDescriptor::kIndexNameFieldName ||
+ if (fieldName == IndexDescriptor::kKeyPatternFieldName || // checked specially
+ fieldName == IndexDescriptor::kNamespaceFieldName || // removed in 4.4
+ fieldName == IndexDescriptor::kIndexNameFieldName || // checked separately
fieldName ==
IndexDescriptor::kIndexVersionFieldName || // not considered for equivalence
fieldName == IndexDescriptor::kTextVersionFieldName || // same as index version
@@ -60,9 +62,9 @@ void populateOptionsMap(std::map<StringData, BSONElement>& theMap, const BSONObj
fieldName ==
IndexDescriptor::kBackgroundFieldName || // this is a creation time option only
fieldName == IndexDescriptor::kDropDuplicatesFieldName || // this is now ignored
- fieldName == IndexDescriptor::kSparseFieldName || // checked specially
- fieldName == IndexDescriptor::kHiddenFieldName || // not considered for equivalence
- fieldName == IndexDescriptor::kUniqueFieldName // check specially
+ fieldName == IndexDescriptor::kHiddenFieldName || // not considered for equivalence
+ fieldName == IndexDescriptor::kCollationFieldName || // checked specially
+ fieldName == IndexDescriptor::kPartialFilterExprFieldName // checked specially
) {
continue;
}
@@ -176,25 +178,69 @@ const NamespaceString& IndexDescriptor::parentNS() const {
return _collection->ns();
}
-bool IndexDescriptor::areIndexOptionsEquivalent(const IndexDescriptor* other) const {
- if (isSparse() != other->isSparse()) {
- return false;
+IndexDescriptor::Comparison IndexDescriptor::compareIndexOptions(
+ OperationContext* opCtx, const IndexCatalogEntry* other) const {
+ // We first check whether the key pattern is identical for both indexes.
+ if (SimpleBSONObjComparator::kInstance.evaluate(keyPattern() !=
+ other->descriptor()->keyPattern())) {
+ return Comparison::kDifferent;
}
- if (!isIdIndex() && unique() != other->unique()) {
- // Note: { _id: 1 } or { _id: -1 } implies unique: true.
- return false;
+ // Check whether both indexes have the same collation. If not, then they are not equivalent.
+ auto collator = collation().isEmpty()
+ ? nullptr
+ : uassertStatusOK(
+ CollatorFactoryInterface::get(opCtx->getServiceContext())->makeFromBSON(collation()));
+ if (!CollatorInterface::collatorsMatch(collator.get(), other->getCollator())) {
+ return Comparison::kDifferent;
}
- // Then compare the rest of the options.
+ // The partialFilterExpression is only part of the index signature if FCV has been set to 4.6.
+ // TODO SERVER-47766: remove these FCV checks after we branch for 4.7.
+ auto isFCV46 = serverGlobalParams.featureCompatibility.isVersion(
+ ServerGlobalParams::FeatureCompatibility::Version::kFullyUpgradedTo46);
+
+ // If we have a partial filter expression and the other index doesn't, or vice-versa, then the
+ // two indexes are not equivalent. We therefore return Comparison::kDifferent immediately.
+ if (isFCV46 && isPartial() != other->descriptor()->isPartial()) {
+ return Comparison::kDifferent;
+ }
+ // Compare 'partialFilterExpression' in each descriptor to see if they are equivalent. We use
+ // the collator that we parsed earlier to create the filter's ExpressionContext, although we
+ // don't currently consider collation when comparing string predicates for filter equivalence.
+ // For instance, under a case-sensitive collation, the predicates {a: "blah"} and {a: "BLAH"}
+ // would match the same set of documents, but these are not currently considered equivalent.
+ // TODO SERVER-47664: take collation into account while comparing string predicates.
+ if (isFCV46 && other->getFilterExpression()) {
+ auto expCtx =
+ make_intrusive<ExpressionContext>(opCtx, std::move(collator), _collection->ns());
+ auto filter = MatchExpressionParser::parseAndNormalize(partialFilterExpression(), expCtx);
+ if (!filter->equivalent(other->getFilterExpression())) {
+ return Comparison::kDifferent;
+ }
+ }
+
+ // If we are here, then the two descriptors match on all option fields that uniquely distinguish
+ // an index, and so the return value will be at least Comparison::kEquivalent. We now proceed to
+ // compare the rest of the options to see if we should return Comparison::kIdentical instead.
std::map<StringData, BSONElement> existingOptionsMap;
populateOptionsMap(existingOptionsMap, infoObj());
std::map<StringData, BSONElement> newOptionsMap;
- populateOptionsMap(newOptionsMap, other->infoObj());
+ populateOptionsMap(newOptionsMap, other->descriptor()->infoObj());
+
+ // If the FCV has not been upgraded to 4.6, add partialFilterExpression to the options map. It
+ // does not contribute to the index signature, but can determine whether or not the candidate
+ // index is identical to the existing index.
+ if (!isFCV46) {
+ existingOptionsMap[IndexDescriptor::kPartialFilterExprFieldName] =
+ other->descriptor()->infoObj()[IndexDescriptor::kPartialFilterExprFieldName];
+ newOptionsMap[IndexDescriptor::kPartialFilterExprFieldName] =
+ infoObj()[IndexDescriptor::kPartialFilterExprFieldName];
+ }
- return existingOptionsMap.size() == newOptionsMap.size() &&
+ const bool optsIdentical = existingOptionsMap.size() == newOptionsMap.size() &&
std::equal(existingOptionsMap.begin(),
existingOptionsMap.end(),
newOptionsMap.begin(),
@@ -204,6 +250,10 @@ bool IndexDescriptor::areIndexOptionsEquivalent(const IndexDescriptor* other) co
SimpleBSONElementComparator::kInstance.evaluate(lhs.second ==
rhs.second);
});
+
+ // If all non-identifying options also match, the descriptors are identical. Otherwise, we
+ // consider them equivalent; two indexes with these options and the same key cannot coexist.
+ return optsIdentical ? Comparison::kIdentical : Comparison::kEquivalent;
}
} // namespace mongo
diff --git a/src/mongo/db/index/index_descriptor.h b/src/mongo/db/index/index_descriptor.h
index 4eab168a65a..c3d68d7a65d 100644
--- a/src/mongo/db/index/index_descriptor.h
+++ b/src/mongo/db/index/index_descriptor.h
@@ -59,6 +59,13 @@ public:
enum class IndexVersion { kV1 = 1, kV2 = 2 };
static constexpr IndexVersion kLatestIndexVersion = IndexVersion::kV2;
+ // Used to report the result of a comparison between two indexes.
+ enum class Comparison {
+ kDifferent, // Indicates that the indexes do not match.
+ kEquivalent, // Indicates that the options which uniquely identify an index match.
+ kIdentical // Indicates that all applicable index options match.
+ };
+
static constexpr StringData k2dIndexBitsFieldName = "bits"_sd;
static constexpr StringData k2dIndexMinFieldName = "min"_sd;
static constexpr StringData k2dIndexMaxFieldName = "max"_sd;
@@ -218,7 +225,12 @@ public:
}
const IndexCatalog* getIndexCatalog() const;
- bool areIndexOptionsEquivalent(const IndexDescriptor* other) const;
+ /**
+ * Compares the current IndexDescriptor against the given index entry. Returns kIdentical if all
+ * index options are logically identical, kEquivalent if all options which uniquely identify an
+ * index are logically identical, and kDifferent otherwise.
+ */
+ Comparison compareIndexOptions(OperationContext* opCtx, const IndexCatalogEntry* other) const;
const BSONObj& collation() const {
return _collation;