diff options
-rw-r--r-- | jstests/core/create_indexes.js | 14 | ||||
-rw-r--r-- | jstests/noPassthrough/geo_full.js | 7 | ||||
-rw-r--r-- | src/mongo/db/catalog/index_key_validate.cpp | 51 | ||||
-rw-r--r-- | src/mongo/db/catalog/index_key_validate.h | 7 | ||||
-rw-r--r-- | src/mongo/db/catalog/index_spec_validate_test.cpp | 16 | ||||
-rw-r--r-- | src/mongo/db/commands/create_indexes.cpp | 22 | ||||
-rw-r--r-- | src/mongo/db/index/index_descriptor.cpp | 11 | ||||
-rw-r--r-- | src/mongo/db/index/index_descriptor.h | 11 |
8 files changed, 130 insertions, 9 deletions
diff --git a/jstests/core/create_indexes.js b/jstests/core/create_indexes.js index a6cdb99ea89..685dfb2e65e 100644 --- a/jstests/core/create_indexes.js +++ b/jstests/core/create_indexes.js @@ -138,4 +138,18 @@ res = t.runCommand('createIndexes', {indexes: [{key: {d: 1}, name: 'd_1', v: 1}]}); assert.commandWorked(res, 'v1 index creation should succeed'); + // Test that index creation fails with an invalid top-level field. + res = t.runCommand('createIndexes', {indexes: [{key: {e: 1}, name: 'e_1'}], 'invalidField': 1}); + assert.commandFailedWithCode(res, ErrorCodes.BadValue); + + // Test that index creation fails with an invalid field in the index spec for index version V2. + res = t.runCommand('createIndexes', + {indexes: [{key: {e: 1}, name: 'e_1', 'v': 2, 'invalidField': 1}]}); + assert.commandFailedWithCode(res, ErrorCodes.BadValue); + + // Test that index creation fails with an invalid field in the index spec for index version V1. + res = t.runCommand('createIndexes', + {indexes: [{key: {e: 1}, name: 'e_1', 'v': 1, 'invalidField': 1}]}); + assert.commandFailedWithCode(res, ErrorCodes.BadValue); + }()); diff --git a/jstests/noPassthrough/geo_full.js b/jstests/noPassthrough/geo_full.js index bdd4b973b79..8b5048e0b29 100644 --- a/jstests/noPassthrough/geo_full.js +++ b/jstests/noPassthrough/geo_full.js @@ -411,7 +411,12 @@ for (var test = 0; test < numTests; test++) { var indexDoc = {"locs.loc": "2d"}; randIndexAdditions(indexDoc); - t.ensureIndex(indexDoc, env); + + // "earth" is used to drive test setup and not a valid createIndexes option or required at this + // point. It must be removed before calling ensureIndexes(). + delete env.earth; + + assert.commandWorked(t.ensureIndex(indexDoc, env)); assert.isnull(db.getLastError()); var padding = "x"; diff --git a/src/mongo/db/catalog/index_key_validate.cpp b/src/mongo/db/catalog/index_key_validate.cpp index 986454911cc..d83f8fafd4c 100644 --- a/src/mongo/db/catalog/index_key_validate.cpp +++ b/src/mongo/db/catalog/index_key_validate.cpp @@ -33,6 +33,7 @@ #include <boost/optional.hpp> #include <cmath> #include <limits> +#include <set> #include "mongo/base/status.h" #include "mongo/base/status_with.h" @@ -147,6 +148,11 @@ StatusWith<BSONObj> validateIndexSpec( bool hasVersionField = false; bool hasCollationField = false; + auto fieldNamesValidStatus = validateIndexSpecFieldNames(indexSpec); + if (!fieldNamesValidStatus.isOK()) { + return fieldNamesValidStatus; + } + boost::optional<IndexVersion> resolvedIndexVersion; for (auto&& indexSpecElem : indexSpec) { @@ -235,7 +241,8 @@ StatusWith<BSONObj> validateIndexSpec( hasCollationField = true; } else { - // TODO SERVER-769: Validate index options specified in the "createIndexes" command. + // We can assume field name is valid at this point. Validation of fieldname is handled + // prior to this in validateIndexSpecFieldNames(). continue; } } @@ -283,4 +290,46 @@ StatusWith<BSONObj> validateIndexSpec( return indexSpec; } + +Status validateIndexSpecFieldNames(const BSONObj& indexSpec) { + const std::set<StringData> allowedFieldNames = { + IndexDescriptor::k2dIndexMaxFieldName, + IndexDescriptor::k2dIndexBitsFieldName, + IndexDescriptor::k2dIndexMaxFieldName, + IndexDescriptor::k2dIndexMinFieldName, + IndexDescriptor::k2dsphereCoarsestIndexedLevel, + IndexDescriptor::k2dsphereFinestIndexedLevel, + IndexDescriptor::k2dsphereVersionFieldName, + IndexDescriptor::kBackgroundFieldName, + IndexDescriptor::kCollationFieldName, + IndexDescriptor::kDefaultLanguageFieldName, + IndexDescriptor::kDropDuplicatesFieldName, + IndexDescriptor::kExpireAfterSecondsFieldName, + IndexDescriptor::kGeoHaystackBucketSize, + IndexDescriptor::kIndexNameFieldName, + IndexDescriptor::kIndexVersionFieldName, + IndexDescriptor::kKeyPatternFieldName, + IndexDescriptor::kLanguageOverrideFieldName, + IndexDescriptor::kNamespaceFieldName, + IndexDescriptor::kPartialFilterExprFieldName, + IndexDescriptor::kSparseFieldName, + IndexDescriptor::kStorageEngineFieldName, + IndexDescriptor::kTextVersionFieldName, + IndexDescriptor::kUniqueFieldName, + IndexDescriptor::kWeightsFieldName, + // Index creation under legacy writeMode can result in an index spec with an _id field. + "_id"}; + + for (auto&& indexSpecElem : indexSpec) { + auto indexSpecElemFieldName = indexSpecElem.fieldNameStringData(); + if (!allowedFieldNames.count(indexSpecElemFieldName)) { + return {ErrorCodes::BadValue, + str::stream() << "The field '" << indexSpecElemFieldName + << "' is not valid for an index specification"}; + } + } + + return Status::OK(); +} + } // namespace mongo diff --git a/src/mongo/db/catalog/index_key_validate.h b/src/mongo/db/catalog/index_key_validate.h index 036eaa4103d..f8e0e8b3e3c 100644 --- a/src/mongo/db/catalog/index_key_validate.h +++ b/src/mongo/db/catalog/index_key_validate.h @@ -51,4 +51,11 @@ StatusWith<BSONObj> validateIndexSpec( const BSONObj& indexSpec, const NamespaceString& expectedNamespace, ServerGlobalParams::FeatureCompatibility::Version featureCompatibilityVersion); + +/** + * Confirms that 'indexSpec' contains only valid field names. Returns an error if an unexpected + * field name is found. + */ +Status validateIndexSpecFieldNames(const BSONObj& indexSpec); + } // namespace mongo diff --git a/src/mongo/db/catalog/index_spec_validate_test.cpp b/src/mongo/db/catalog/index_spec_validate_test.cpp index c41bc920cad..c1731e08f10 100644 --- a/src/mongo/db/catalog/index_spec_validate_test.cpp +++ b/src/mongo/db/catalog/index_spec_validate_test.cpp @@ -370,5 +370,21 @@ TEST(IndexSpecValidateTest, AcceptsIndexSpecIfCollationIsPresentAndVersionIsEqua sorted(result.getValue())); } +TEST(IndexSpecValidateTest, ReturnsAnErrorIfUnknownFieldIsPresentInSpecV2) { + auto result = + validateIndexSpec(BSON("key" << BSON("field" << 1) << "v" << 2 << "unknownField" << 1), + kTestNamespace, + ServerGlobalParams::FeatureCompatibility::Version::k34); + ASSERT_EQ(ErrorCodes::BadValue, result); +} + +TEST(IndexSpecValidateTest, ReturnsAnErrorIfUnknownFieldIsPresentInSpecV1) { + auto result = + validateIndexSpec(BSON("key" << BSON("field" << 1) << "v" << 1 << "unknownField" << 1), + kTestNamespace, + ServerGlobalParams::FeatureCompatibility::Version::k34); + ASSERT_EQ(ErrorCodes::BadValue, result); +} + } // namespace } // namespace mongo diff --git a/src/mongo/db/commands/create_indexes.cpp b/src/mongo/db/commands/create_indexes.cpp index 5d660f024ce..f4537fe45f9 100644 --- a/src/mongo/db/commands/create_indexes.cpp +++ b/src/mongo/db/commands/create_indexes.cpp @@ -65,6 +65,8 @@ using IndexVersion = IndexDescriptor::IndexVersion; namespace { const StringData kIndexesFieldName = "indexes"_sd; +const StringData kCommandName = "createIndexes"_sd; +const StringData kWriteConcern = "writeConcern"_sd; /** * Parses the index specifications from 'cmdObj', validates them, and returns equivalent index @@ -106,16 +108,22 @@ StatusWith<std::vector<BSONObj>> parseAndValidateIndexSpecs( } hasIndexesField = true; - } else { - // TODO SERVER-769: Validate top-level options to the "createIndexes" command. + } else if (kCommandName == cmdElemFieldName || kWriteConcern == cmdElemFieldName) { + // Both the command name and writeConcern are valid top-level fields. continue; + } else { + return {ErrorCodes::BadValue, + str::stream() << "Invalid field specified for " << kCommandName << " command: " + << cmdElemFieldName}; } } if (!hasIndexesField) { return {ErrorCodes::FailedToParse, str::stream() << "The '" << kIndexesFieldName - << "' field is a required argument of the createIndexes command"}; + << "' field is a required argument of the " + << kCommandName + << " command"}; } if (indexSpecs.empty()) { @@ -205,7 +213,7 @@ StatusWith<std::vector<BSONObj>> resolveCollectionDefaultProperties( */ class CmdCreateIndex : public Command { public: - CmdCreateIndex() : Command("createIndexes") {} + CmdCreateIndex() : Command(kCommandName) {} virtual bool supportsWriteConcern(const BSONObj& cmd) const override { return true; @@ -276,7 +284,7 @@ public: invariant(collection); wunit.commit(); } - MONGO_WRITE_CONFLICT_RETRY_LOOP_END(txn, "createIndexes", ns.ns()); + MONGO_WRITE_CONFLICT_RETRY_LOOP_END(txn, kCommandName, ns.ns()); result.appendBool("createdCollectionAutomatically", true); } @@ -328,7 +336,7 @@ public: MONGO_WRITE_CONFLICT_RETRY_LOOP_BEGIN { indexInfoObjs = uassertStatusOK(indexer.init(specs)); } - MONGO_WRITE_CONFLICT_RETRY_LOOP_END(txn, "createIndexes", ns.ns()); + MONGO_WRITE_CONFLICT_RETRY_LOOP_END(txn, kCommandName, ns.ns()); // If we're a background index, replace exclusive db lock with an intent lock, so that // other readers and writers can proceed during this phase. @@ -401,7 +409,7 @@ public: wunit.commit(); } - MONGO_WRITE_CONFLICT_RETRY_LOOP_END(txn, "createIndexes", ns.ns()); + MONGO_WRITE_CONFLICT_RETRY_LOOP_END(txn, kCommandName, ns.ns()); result.append("numIndexesAfter", collection->getIndexCatalog()->numIndexesTotal(txn)); diff --git a/src/mongo/db/index/index_descriptor.cpp b/src/mongo/db/index/index_descriptor.cpp index fca84057727..ff7b615dd0b 100644 --- a/src/mongo/db/index/index_descriptor.cpp +++ b/src/mongo/db/index/index_descriptor.cpp @@ -70,18 +70,29 @@ void populateOptionsMap(std::map<StringData, BSONElement>& theMap, const BSONObj } } // namespace +constexpr StringData IndexDescriptor::k2dIndexBitsFieldName; +constexpr StringData IndexDescriptor::k2dIndexMaxFieldName; +constexpr StringData IndexDescriptor::k2dIndexMinFieldName; +constexpr StringData IndexDescriptor::k2dsphereCoarsestIndexedLevel; +constexpr StringData IndexDescriptor::k2dsphereFinestIndexedLevel; constexpr StringData IndexDescriptor::k2dsphereVersionFieldName; constexpr StringData IndexDescriptor::kBackgroundFieldName; constexpr StringData IndexDescriptor::kCollationFieldName; +constexpr StringData IndexDescriptor::kDefaultLanguageFieldName; constexpr StringData IndexDescriptor::kDropDuplicatesFieldName; +constexpr StringData IndexDescriptor::kExpireAfterSecondsFieldName; +constexpr StringData IndexDescriptor::kGeoHaystackBucketSize; constexpr StringData IndexDescriptor::kIndexNameFieldName; constexpr StringData IndexDescriptor::kIndexVersionFieldName; constexpr StringData IndexDescriptor::kKeyPatternFieldName; +constexpr StringData IndexDescriptor::kLanguageOverrideFieldName; constexpr StringData IndexDescriptor::kNamespaceFieldName; constexpr StringData IndexDescriptor::kPartialFilterExprFieldName; constexpr StringData IndexDescriptor::kSparseFieldName; +constexpr StringData IndexDescriptor::kStorageEngineFieldName; constexpr StringData IndexDescriptor::kTextVersionFieldName; constexpr StringData IndexDescriptor::kUniqueFieldName; +constexpr StringData IndexDescriptor::kWeightsFieldName; bool IndexDescriptor::isIndexVersionSupported(IndexVersion indexVersion) { switch (indexVersion) { diff --git a/src/mongo/db/index/index_descriptor.h b/src/mongo/db/index/index_descriptor.h index d271e7e5f5a..82ee02766de 100644 --- a/src/mongo/db/index/index_descriptor.h +++ b/src/mongo/db/index/index_descriptor.h @@ -56,18 +56,29 @@ class IndexDescriptor { public: enum class IndexVersion { kV0 = 0, kV1 = 1, kV2 = 2 }; + static constexpr StringData k2dIndexBitsFieldName = "bits"_sd; + static constexpr StringData k2dIndexMinFieldName = "min"_sd; + static constexpr StringData k2dIndexMaxFieldName = "max"_sd; + static constexpr StringData k2dsphereCoarsestIndexedLevel = "coarsestIndexedLevel"_sd; + static constexpr StringData k2dsphereFinestIndexedLevel = "finestIndexedLevel"_sd; static constexpr StringData k2dsphereVersionFieldName = "2dsphereIndexVersion"_sd; static constexpr StringData kBackgroundFieldName = "background"_sd; static constexpr StringData kCollationFieldName = "collation"_sd; + static constexpr StringData kDefaultLanguageFieldName = "default_language"_sd; static constexpr StringData kDropDuplicatesFieldName = "dropDups"_sd; + static constexpr StringData kExpireAfterSecondsFieldName = "expireAfterSeconds"_sd; + static constexpr StringData kGeoHaystackBucketSize = "bucketSize"_sd; static constexpr StringData kIndexNameFieldName = "name"_sd; static constexpr StringData kIndexVersionFieldName = "v"_sd; static constexpr StringData kKeyPatternFieldName = "key"_sd; + static constexpr StringData kLanguageOverrideFieldName = "language_override"_sd; static constexpr StringData kNamespaceFieldName = "ns"_sd; static constexpr StringData kPartialFilterExprFieldName = "partialFilterExpression"_sd; static constexpr StringData kSparseFieldName = "sparse"_sd; + static constexpr StringData kStorageEngineFieldName = "storageEngine"_sd; static constexpr StringData kTextVersionFieldName = "textIndexVersion"_sd; static constexpr StringData kUniqueFieldName = "unique"_sd; + static constexpr StringData kWeightsFieldName = "weights"_sd; /** * OnDiskIndexData is a pointer to the memory mapped per-index data. |