diff options
author | Gregory Noma <gregory.noma@gmail.com> | 2021-05-24 09:55:04 -0400 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2021-05-24 20:31:30 +0000 |
commit | e465a2c8dcc8d8f0676eaf266e809a71d946c07c (patch) | |
tree | b622964ffdae1599979aea1ee92ddfe6b8c69fda | |
parent | b5a61bb4682788503b4e4808069754ba5d914b1b (diff) | |
download | mongo-e465a2c8dcc8d8f0676eaf266e809a71d946c07c.tar.gz |
SERVER-56934 Make expireAfterSeconds a top-level collection option
(cherry picked from commit b173494aa6b84b5d44c5f958fd78dd469596a314)
28 files changed, 145 insertions, 190 deletions
diff --git a/buildscripts/idl/idl_compatibility_errors.py b/buildscripts/idl/idl_compatibility_errors.py index 02961c50ccf..edf869c180f 100644 --- a/buildscripts/idl/idl_compatibility_errors.py +++ b/buildscripts/idl/idl_compatibility_errors.py @@ -126,7 +126,7 @@ SKIPPED_COMMANDS = { "abortTransaction": [ERROR_ID_ADDED_ACCESS_CHECK_FIELD], "aggregate": [ERROR_ID_ADDED_ACCESS_CHECK_FIELD], "authenticate": [ERROR_ID_ADDED_ACCESS_CHECK_FIELD], - "collMod": [ERROR_ID_ADDED_ACCESS_CHECK_FIELD], + "collMod": [ERROR_ID_ADDED_ACCESS_CHECK_FIELD, ERROR_ID_REMOVED_COMMAND_PARAMETER], "commitTransaction": [ERROR_ID_ADDED_ACCESS_CHECK_FIELD], "create": [ERROR_ID_ADDED_ACCESS_CHECK_FIELD], "createIndexes": [ERROR_ID_ADDED_ACCESS_CHECK_FIELD], diff --git a/jstests/concurrency/fsm_workloads/insert_ttl_timeseries.js b/jstests/concurrency/fsm_workloads/insert_ttl_timeseries.js index 9cb5664b45f..483bc6e537f 100644 --- a/jstests/concurrency/fsm_workloads/insert_ttl_timeseries.js +++ b/jstests/concurrency/fsm_workloads/insert_ttl_timeseries.js @@ -185,8 +185,8 @@ var $config = (function() { timeseries: { timeField: timeFieldName, metaField: metaFieldName, - expireAfterSeconds: ttlSeconds, - } + }, + expireAfterSeconds: ttlSeconds, })); } diff --git a/jstests/core/timeseries/clustered_index_options.js b/jstests/core/timeseries/clustered_index_options.js index ad8a620f6af..8bdf755c72a 100644 --- a/jstests/core/timeseries/clustered_index_options.js +++ b/jstests/core/timeseries/clustered_index_options.js @@ -32,20 +32,20 @@ const tsColl = testDB.clustered_index_options; const tsCollName = tsColl.getName(); const bucketsCollName = 'system.buckets.' + tsCollName; -assert.commandWorked(testDB.createCollection(bucketsCollName, {clusteredIndex: {}})); +assert.commandWorked(testDB.createCollection(bucketsCollName, {clusteredIndex: false})); assert.commandWorked(testDB.dropDatabase()); -assert.commandWorked(testDB.createCollection(bucketsCollName, {clusteredIndex: {}})); +assert.commandWorked(testDB.createCollection(bucketsCollName, {clusteredIndex: true})); assert.commandWorked(testDB.dropDatabase()); assert.commandWorked( - testDB.createCollection(bucketsCollName, {clusteredIndex: {expireAfterSeconds: 10}})); + testDB.createCollection(bucketsCollName, {clusteredIndex: true, expireAfterSeconds: 10})); assert.commandWorked(testDB.dropDatabase()); // Round-trip creating a time-series collection. Use the output of listCollections to re-create // the buckets collection. assert.commandWorked( - testDB.createCollection(tsCollName, {timeseries: {timeField: 'time', expireAfterSeconds: 10}})); + testDB.createCollection(tsCollName, {timeseries: {timeField: 'time'}, expireAfterSeconds: 10})); let res = assert.commandWorked(testDB.runCommand({listCollections: 1, filter: {name: bucketsCollName}})); @@ -59,16 +59,25 @@ res = assert.eq(options, res.cursor.firstBatch[0].options); assert.commandWorked(testDB.dropDatabase()); -assert.commandFailedWithCode(testDB.createCollection(bucketsCollName, {clusteredIndex: {bad: 1}}), - 40415); +assert.commandFailedWithCode(testDB.createCollection(bucketsCollName, {clusteredIndex: {}}), + ErrorCodes.TypeMismatch); +assert.commandFailedWithCode(testDB.createCollection(bucketsCollName, {clusteredIndex: 'a'}), + ErrorCodes.TypeMismatch); assert.commandFailedWithCode( testDB.createCollection(bucketsCollName, - {clusteredIndex: {}, idIndex: {key: {_id: 1}, name: '_id_'}}), + {clusteredIndex: true, idIndex: {key: {_id: 1}, name: '_id_'}}), ErrorCodes.InvalidOptions); // Using the 'clusteredIndex' option on any namespace other than a buckets namespace should fail. -assert.commandFailedWithCode(testDB.createCollection(tsCollName, {clusteredIndex: {}}), +assert.commandFailedWithCode(testDB.createCollection(tsCollName, {clusteredIndex: true}), ErrorCodes.InvalidOptions); -assert.commandFailedWithCode(testDB.createCollection('test', {clusteredIndex: {}}), +assert.commandFailedWithCode(testDB.createCollection('test', {clusteredIndex: true}), + ErrorCodes.InvalidOptions); + +// Using the 'expireAfterSeconds' option on any namespace other than a time-series namespace or a +// clustered time-series buckets namespace should fail. +assert.commandFailedWithCode(testDB.createCollection('test', {expireAfterSeconds: 10}), + ErrorCodes.InvalidOptions); +assert.commandFailedWithCode(testDB.createCollection(bucketsCollName, {expireAfterSeconds: 10}), ErrorCodes.InvalidOptions); })();
\ No newline at end of file diff --git a/jstests/core/timeseries/clustered_index_types.js b/jstests/core/timeseries/clustered_index_types.js index 5bb30ad007c..b5241af6aaf 100644 --- a/jstests/core/timeseries/clustered_index_types.js +++ b/jstests/core/timeseries/clustered_index_types.js @@ -26,7 +26,7 @@ const collName = 'system.buckets.test'; const coll = db[collName]; coll.drop(); -assert.commandWorked(db.createCollection(collName, {clusteredIndex: {}})); +assert.commandWorked(db.createCollection(collName, {clusteredIndex: true})); // Expect that duplicates are rejected. let oid = new ObjectId(); diff --git a/jstests/core/timeseries/timeseries_create_drop.js b/jstests/core/timeseries/timeseries_create_drop.js index 71c48bf5397..50f78c446fd 100644 --- a/jstests/core/timeseries/timeseries_create_drop.js +++ b/jstests/core/timeseries/timeseries_create_drop.js @@ -42,7 +42,7 @@ coll.drop(); // Create should create both bucket collection and view assert.commandWorked(db.createCollection( coll.getName(), - {timeseries: {timeField: timeFieldName, expireAfterSeconds: expireAfterSecondsNum}})); + {timeseries: {timeField: timeFieldName}, expireAfterSeconds: expireAfterSecondsNum})); assert.contains(viewName, db.getCollectionNames()); assert.contains(bucketsCollName, db.getCollectionNames()); @@ -56,7 +56,7 @@ assert.commandWorked( db.adminCommand({configureFailPoint: failpoint, mode: "alwaysOn", data: {ns: viewNs}})); assert.commandFailed(db.createCollection( coll.getName(), - {timeseries: {timeField: timeFieldName, expireAfterSeconds: expireAfterSecondsNum}})); + {timeseries: {timeField: timeFieldName}, expireAfterSeconds: expireAfterSecondsNum})); assert.eq(db.getCollectionNames().findIndex(c => c == viewName), -1); assert.contains(bucketsCollName, db.getCollectionNames()); @@ -69,7 +69,7 @@ assert.eq(db.getCollectionNames().findIndex(c => c == bucketsCollName), -1); // Trying to create again yields the same result as fail point is still enabled assert.commandFailed(db.createCollection( coll.getName(), - {timeseries: {timeField: timeFieldName, expireAfterSeconds: expireAfterSecondsNum}})); + {timeseries: {timeField: timeFieldName}, expireAfterSeconds: expireAfterSecondsNum})); assert.eq(db.getCollectionNames().findIndex(c => c == viewName), -1); assert.contains(bucketsCollName, db.getCollectionNames()); @@ -79,14 +79,14 @@ assert.commandWorked(db.adminCommand({configureFailPoint: failpoint, mode: "off" // Different timeField should fail assert.commandFailed(db.createCollection( coll.getName(), - {timeseries: {timeField: timeFieldName + "2", expireAfterSeconds: expireAfterSecondsNum}})); + {timeseries: {timeField: timeFieldName + "2"}, expireAfterSeconds: expireAfterSecondsNum})); assert.eq(db.getCollectionNames().findIndex(c => c == viewName), -1); assert.contains(bucketsCollName, db.getCollectionNames()); // Different expireAfterSeconds should fail assert.commandFailed(db.createCollection( coll.getName(), - {timeseries: {timeField: timeFieldName, expireAfterSeconds: expireAfterSecondsNum + 1}})); + {timeseries: {timeField: timeFieldName}, expireAfterSeconds: expireAfterSecondsNum + 1})); assert.eq(db.getCollectionNames().findIndex(c => c == viewName), -1); assert.contains(bucketsCollName, db.getCollectionNames()); @@ -98,7 +98,7 @@ assert.contains(bucketsCollName, db.getCollectionNames()); // Same parameters should succeed assert.commandWorked(db.createCollection( coll.getName(), - {timeseries: {timeField: timeFieldName, expireAfterSeconds: expireAfterSecondsNum}})); + {timeseries: {timeField: timeFieldName}, expireAfterSeconds: expireAfterSecondsNum})); assert.contains(viewName, db.getCollectionNames()); assert.contains(bucketsCollName, db.getCollectionNames()); })(); diff --git a/jstests/core/timeseries/timeseries_expire.js b/jstests/core/timeseries/timeseries_expire.js index 9cf35126e84..9a4f20bdf7f 100644 --- a/jstests/core/timeseries/timeseries_expire.js +++ b/jstests/core/timeseries/timeseries_expire.js @@ -25,7 +25,7 @@ TimeseriesTest.run((insert) => { const expireAfterSeconds = NumberLong(5); assert.commandWorked(db.createCollection( coll.getName(), - {timeseries: {timeField: timeFieldName, expireAfterSeconds: expireAfterSeconds}})); + {timeseries: {timeField: timeFieldName}, expireAfterSeconds: expireAfterSeconds})); assert.contains(bucketsColl.getName(), db.getCollectionNames()); // Inserts a measurement with a time in the past to ensure the measurement will be removed diff --git a/jstests/core/timeseries/timeseries_expire_collmod.js b/jstests/core/timeseries/timeseries_expire_collmod.js index 8a0cc2d4faf..75bf199e0b1 100644 --- a/jstests/core/timeseries/timeseries_expire_collmod.js +++ b/jstests/core/timeseries/timeseries_expire_collmod.js @@ -33,7 +33,7 @@ const timeFieldName = 'time'; const expireAfterSeconds = NumberLong(5); assert.commandWorked(db.createCollection( coll.getName(), - {timeseries: {timeField: timeFieldName, expireAfterSeconds: expireAfterSeconds}})); + {timeseries: {timeField: timeFieldName}, expireAfterSeconds: expireAfterSeconds})); const bucketsColl = db.getCollection('system.buckets.' + coll.getName()); @@ -42,45 +42,37 @@ const collNotClustered = db.getCollection(coll.getName() + '_not_clustered'); collNotClustered.drop(); assert.commandWorked(db.createCollection(collNotClustered.getName())); assert.commandFailedWithCode( - db.runCommand({collMod: collNotClustered.getName(), clusteredIndex: {expireAfterSeconds: 10}}), + db.runCommand({collMod: collNotClustered.getName(), expireAfterSeconds: 10}), ErrorCodes.InvalidOptions); // Check for invalid input on the time-series collection. -assert.commandFailedWithCode( - db.runCommand({collMod: coll.getName(), clusteredIndex: {expireAfterSeconds: "10"}}), - ErrorCodes.InvalidOptions); -assert.commandFailedWithCode( - db.runCommand({collMod: coll.getName(), clusteredIndex: {expireAfterSeconds: {}}}), - ErrorCodes.TypeMismatch); -assert.commandFailedWithCode( - db.runCommand({collMod: coll.getName(), clusteredIndex: {expireAfterSeconds: -10}}), - ErrorCodes.InvalidOptions); -assert.commandFailedWithCode(db.runCommand({collMod: coll.getName(), clusteredIndex: {}}), 40414); +assert.commandFailedWithCode(db.runCommand({collMod: coll.getName(), expireAfterSeconds: "10"}), + ErrorCodes.InvalidOptions); +assert.commandFailedWithCode(db.runCommand({collMod: coll.getName(), expireAfterSeconds: {}}), + ErrorCodes.TypeMismatch); +assert.commandFailedWithCode(db.runCommand({collMod: coll.getName(), expireAfterSeconds: -10}), + ErrorCodes.InvalidOptions); // Check for invalid input on the underlying bucket collection. assert.commandFailedWithCode( - db.runCommand({collMod: bucketsColl.getName(), clusteredIndex: {expireAfterSeconds: "10"}}), + db.runCommand({collMod: bucketsColl.getName(), expireAfterSeconds: "10"}), ErrorCodes.InvalidOptions); assert.commandFailedWithCode( - db.runCommand({collMod: bucketsColl.getName(), clusteredIndex: {expireAfterSeconds: {}}}), + db.runCommand({collMod: bucketsColl.getName(), expireAfterSeconds: {}}), ErrorCodes.TypeMismatch); assert.commandFailedWithCode( - db.runCommand({collMod: bucketsColl.getName(), clusteredIndex: {expireAfterSeconds: -10}}), + db.runCommand({collMod: bucketsColl.getName(), expireAfterSeconds: -10}), ErrorCodes.InvalidOptions); assert.commandFailedWithCode(db.runCommand({ collMod: bucketsColl.getName(), - clusteredIndex: {expireAfterSeconds: NumberLong("4611686018427387904")} + expireAfterSeconds: NumberLong("4611686018427387904"), }), ErrorCodes.InvalidOptions); -assert.commandFailedWithCode(db.runCommand({collMod: bucketsColl.getName(), clusteredIndex: {}}), - 40414); let res = assert.commandWorked( db.runCommand({listCollections: 1, filter: {name: bucketsColl.getName()}})); -assert(res.cursor.firstBatch[0].options.hasOwnProperty("clusteredIndex"), - bucketsColl.getName() + ': ' + expireAfterSeconds + ': ' + tojson(res)); assert.eq(expireAfterSeconds, - res.cursor.firstBatch[0].options.clusteredIndex.expireAfterSeconds, + res.cursor.firstBatch[0].options.expireAfterSeconds, bucketsColl.getName() + ': ' + expireAfterSeconds + ': ' + tojson(res)); /** @@ -90,19 +82,18 @@ assert.eq(expireAfterSeconds, const runTest = function(collToChange, expireAfterSeconds) { assert.commandWorked(db.runCommand({ collMod: collToChange.getName(), - clusteredIndex: {expireAfterSeconds: expireAfterSeconds} + expireAfterSeconds: expireAfterSeconds, })); res = assert.commandWorked( db.runCommand({listCollections: 1, filter: {name: bucketsColl.getName()}})); if (expireAfterSeconds !== 'off') { assert.eq(expireAfterSeconds, - res.cursor.firstBatch[0].options.clusteredIndex.expireAfterSeconds, + res.cursor.firstBatch[0].options.expireAfterSeconds, collToChange.getFullName() + ': ' + expireAfterSeconds + ': ' + tojson(res)); } else { - assert( - !res.cursor.firstBatch[0].options.clusteredIndex.hasOwnProperty("expireAfterSeconds"), - collToChange.getFullName() + ': ' + expireAfterSeconds + ': ' + tojson(res)); + assert(!res.cursor.firstBatch[0].options.hasOwnProperty("expireAfterSeconds"), + collToChange.getFullName() + ': ' + expireAfterSeconds + ': ' + tojson(res)); } }; diff --git a/jstests/noPassthrough/timeseries_create.js b/jstests/noPassthrough/timeseries_create.js index aa4a830e99b..182e8efba5f 100644 --- a/jstests/noPassthrough/timeseries_create.js +++ b/jstests/noPassthrough/timeseries_create.js @@ -55,19 +55,19 @@ const testOptions = function(allowed, assert(bucketsColl, collections); assert.eq(bucketsColl.type, "collection", bucketsColl); if (TimeseriesTest.supportsClusteredIndexes(conn)) { - assert(bucketsColl.options.hasOwnProperty('clusteredIndex'), bucketsColl); + assert(bucketsColl.options.clusteredIndex, bucketsColl); } - if (timeseriesOptions.expireAfterSeconds) { + if (createOptions.expireAfterSeconds) { if (TimeseriesTest.supportsClusteredIndexes(conn)) { - assert.eq(bucketsColl.options.clusteredIndex.expireAfterSeconds, - timeseriesOptions.expireAfterSeconds, + assert.eq(bucketsColl.options.expireAfterSeconds, + createOptions.expireAfterSeconds, bucketsColl); } else { assert.docEq(testDB[collName].getIndexes(), [{ v: 2, key: {time: 1}, name: 'control.min.time_1', - expireAfterSeconds: timeseriesOptions.expireAfterSeconds.valueOf(), + expireAfterSeconds: createOptions.expireAfterSeconds.valueOf(), }]); } } @@ -91,8 +91,8 @@ const testInvalidTimeseriesOptions = function(timeseriesOptions, errorCode) { testOptions(false, {}, timeseriesOptions, errorCode); }; -const testIncompatibleCreateOptions = function(createOptions) { - testOptions(false, createOptions); +const testIncompatibleCreateOptions = function(createOptions, errorCode) { + testOptions(false, createOptions, {timeField: 'time'}, errorCode); }; const testCompatibleCreateOptions = function(createOptions) { @@ -110,9 +110,6 @@ const testTimeseriesNamespaceExists = function(setUp) { testValidTimeseriesOptions({timeField: "time"}); testValidTimeseriesOptions({timeField: "time", metaField: "meta"}); -testValidTimeseriesOptions({timeField: "time", expireAfterSeconds: NumberLong(100)}); -testValidTimeseriesOptions( - {timeField: "time", metaField: "meta", expireAfterSeconds: NumberLong(100)}); testValidTimeseriesOptions({timeField: "time", metaField: "meta", granularity: "seconds"}); // A bucketMaxSpanSeconds may be provided, but only if they are the default for the granularity. @@ -137,16 +134,6 @@ testValidTimeseriesOptions({timeField: "time", metaField: "meta", granularity: " testInvalidTimeseriesOptions("", ErrorCodes.TypeMismatch); testInvalidTimeseriesOptions({timeField: 100}, ErrorCodes.TypeMismatch); testInvalidTimeseriesOptions({timeField: "time", metaField: 100}, ErrorCodes.TypeMismatch); -testInvalidTimeseriesOptions({timeField: "time", expireAfterSeconds: ""}, ErrorCodes.TypeMismatch); - -const errorCodeForInvalidExpireAfterSecondsValue = TimeseriesTest.supportsClusteredIndexes(conn) - ? ErrorCodes.InvalidOptions - : ErrorCodes.CannotCreateIndex; -testInvalidTimeseriesOptions({timeField: "time", expireAfterSeconds: NumberLong(-10)}, - errorCodeForInvalidExpireAfterSecondsValue); -testInvalidTimeseriesOptions( - {timeField: "time", expireAfterSeconds: NumberLong("4611686018427387904")}, - errorCodeForInvalidExpireAfterSecondsValue); testInvalidTimeseriesOptions({timeField: "time", invalidOption: {}}, 40415); testInvalidTimeseriesOptions({timeField: "sub.time"}, ErrorCodes.InvalidOptions); @@ -159,12 +146,21 @@ testInvalidTimeseriesOptions( {timeField: "time", metaField: "meta", granularity: 'minutes', bucketMaxSpanSeconds: 3600}, 5510500); +testCompatibleCreateOptions({expireAfterSeconds: NumberLong(100)}); testCompatibleCreateOptions({storageEngine: {}}); testCompatibleCreateOptions({indexOptionDefaults: {}}); testCompatibleCreateOptions({collation: {locale: "ja"}}); testCompatibleCreateOptions({writeConcern: {}}); testCompatibleCreateOptions({comment: ""}); +const errorCodeForInvalidExpireAfterSecondsValue = TimeseriesTest.supportsClusteredIndexes(conn) + ? ErrorCodes.InvalidOptions + : ErrorCodes.CannotCreateIndex; +testIncompatibleCreateOptions({expireAfterSeconds: NumberLong(-10)}, + errorCodeForInvalidExpireAfterSecondsValue); +testIncompatibleCreateOptions({expireAfterSeconds: NumberLong("4611686018427387904")}, + errorCodeForInvalidExpireAfterSecondsValue); +testIncompatibleCreateOptions({expireAfterSeconds: ""}, ErrorCodes.TypeMismatch); testIncompatibleCreateOptions({capped: true, size: 100}); testIncompatibleCreateOptions({capped: true, max: 100}); testIncompatibleCreateOptions({autoIndexId: true}); @@ -174,6 +170,8 @@ testIncompatibleCreateOptions({validationLevel: "off"}); testIncompatibleCreateOptions({validationAction: "warn"}); testIncompatibleCreateOptions({viewOn: "coll"}); testIncompatibleCreateOptions({viewOn: "coll", pipeline: []}); +testIncompatibleCreateOptions({clusteredIndex: true}); +testIncompatibleCreateOptions({clusteredIndex: false}); testTimeseriesNamespaceExists((testDB, collName) => { assert.commandWorked(testDB.createCollection(collName)); diff --git a/jstests/noPassthrough/timeseries_non_clustered_collmod.js b/jstests/noPassthrough/timeseries_non_clustered_collmod.js index a21046c63f7..2785ca4f436 100644 --- a/jstests/noPassthrough/timeseries_non_clustered_collmod.js +++ b/jstests/noPassthrough/timeseries_non_clustered_collmod.js @@ -29,7 +29,7 @@ const timeFieldName = 'time'; const expireAfterSeconds = NumberLong(5); assert.commandWorked(db.createCollection( coll.getName(), - {timeseries: {timeField: timeFieldName, expireAfterSeconds: expireAfterSeconds}})); + {timeseries: {timeField: timeFieldName}, expireAfterSeconds: expireAfterSeconds})); let indexes = assert.commandWorked(db.runCommand({listIndexes: coll.getName()})).cursor.firstBatch; assert.eq(1, indexes.length); @@ -45,7 +45,7 @@ assert.commandWorked(db.runCommand({ // Changing the clustered expireAfterSeconds option in the catalog can't happen on a non-clustered // time-series collection. assert.commandFailedWithCode( - db.runCommand({collMod: coll.getName(), clusteredIndex: {expireAfterSeconds: NumberLong(10)}}), + db.runCommand({collMod: coll.getName(), expireAfterSeconds: NumberLong(10)}), ErrorCodes.InvalidOptions); MongoRunner.stopMongod(conn); diff --git a/jstests/noPassthrough/timeseries_ttl.js b/jstests/noPassthrough/timeseries_ttl.js index 73c648196ec..97842c8f120 100644 --- a/jstests/noPassthrough/timeseries_ttl.js +++ b/jstests/noPassthrough/timeseries_ttl.js @@ -50,8 +50,8 @@ const testCase = (testFn) => { timeseries: { timeField: timeFieldName, metaField: metaFieldName, - expireAfterSeconds: expireAfterSeconds, - } + }, + expireAfterSeconds: expireAfterSeconds, })); testFn(coll, bucketsColl); @@ -166,7 +166,7 @@ testCase((coll, bucketsColl) => { if (TimeseriesTest.supportsClusteredIndexes(conn)) { assert.commandWorked(testDB.runCommand({ collMod: 'system.buckets.ts', - clusteredIndex: {expireAfterSeconds: expireAfterSeconds} + expireAfterSeconds: expireAfterSeconds, })); } else { assert.commandWorked(bucketsColl.createIndex({['control.min.' + timeFieldName]: 1}, diff --git a/jstests/replsets/rollback_clustered_indexes.js b/jstests/replsets/rollback_clustered_indexes.js index 5c20f841d60..479be9c6026 100644 --- a/jstests/replsets/rollback_clustered_indexes.js +++ b/jstests/replsets/rollback_clustered_indexes.js @@ -19,7 +19,7 @@ const collName = 'test.system.buckets.t'; const collNameShort = 'system.buckets.t'; let commonOps = (node) => { const db = node.getDB(dbName); - assert.commandWorked(db.createCollection(collNameShort, {clusteredIndex: {}})); + assert.commandWorked(db.createCollection(collNameShort, {clusteredIndex: true})); const coll = node.getCollection(collName); assert.commandWorked(coll.createIndex({a: 1, b: -1})); assert.commandWorked(coll.insert({a: 0, b: 0})); diff --git a/src/mongo/db/catalog/SConscript b/src/mongo/db/catalog/SConscript index 97d52070ac0..f1efdba05b0 100644 --- a/src/mongo/db/catalog/SConscript +++ b/src/mongo/db/catalog/SConscript @@ -27,23 +27,11 @@ env.Library( ) env.Library( - target='clustered_index_options_idl', - source=[ - 'clustered_index_options.idl', - ], - LIBDEPS_PRIVATE=[ - '$BUILD_DIR/mongo/base', - '$BUILD_DIR/mongo/idl/idl_parser', - ], -) - -env.Library( target='collection_options', source=[ 'collection_options.cpp', ], LIBDEPS=[ - 'clustered_index_options_idl', 'collection_options_idl', ], LIBDEPS_PRIVATE=[ diff --git a/src/mongo/db/catalog/clustered_index_options.idl b/src/mongo/db/catalog/clustered_index_options.idl deleted file mode 100644 index 32ed09cb0d5..00000000000 --- a/src/mongo/db/catalog/clustered_index_options.idl +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright (C) 2021-present MongoDB, Inc. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the Server Side Public License, version 1, -# as published by MongoDB, Inc. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# Server Side Public License for more details. -# -# You should have received a copy of the Server Side Public License -# along with this program. If not, see -# <http://www.mongodb.com/licensing/server-side-public-license>. -# -# As a special exception, the copyright holders give permission to link the -# code of portions of this program with the OpenSSL library under certain -# conditions as described in each individual source file and distribute -# linked combinations including the program with the OpenSSL library. You -# must comply with the Server Side Public License in all respects for -# all of the code used other than as permitted herein. If you modify file(s) -# with this exception, you may extend this exception to your version of the -# file(s), but you are not obligated to do so. If you do not wish to do so, -# delete this exception statement from your version. If you delete this -# exception statement from all source files in the program, then also delete -# it in the license file. - -global: - cpp_namespace: "mongo" - -imports: - - "mongo/idl/basic_types.idl" - -structs: - ClusteredIndexOptions: - description: "The options that define a clustered _id index on a collection." - strict: true - fields: - expireAfterSeconds: - description: "The number of seconds after which old data should be deleted." - type: safeInt64 - optional: true diff --git a/src/mongo/db/catalog/coll_mod.cpp b/src/mongo/db/catalog/coll_mod.cpp index f0504822d73..381c1810bf3 100644 --- a/src/mongo/db/catalog/coll_mod.cpp +++ b/src/mongo/db/catalog/coll_mod.cpp @@ -306,16 +306,15 @@ StatusWith<CollModRequest> parseCollModRequest(OperationContext* opCtx, } cmr.recordPreImages = e.trueValue(); - } else if (fieldName == "clusteredIndex") { + } else if (fieldName == "expireAfterSeconds") { if (coll->getRecordStore()->keyFormat() != KeyFormat::String) { - return Status( - ErrorCodes::InvalidOptions, - "'clusteredIndex' option is only supported on collections clustered by _id"); + return Status(ErrorCodes::InvalidOptions, + "'expireAfterSeconds' option is only supported on collections " + "clustered by _id"); } - BSONElement elem = e.Obj()["expireAfterSeconds"]; - if (elem.type() == mongo::String) { - const std::string elemStr = elem.String(); + if (e.type() == mongo::String) { + const std::string elemStr = e.String(); if (elemStr != "off") { return Status( ErrorCodes::InvalidOptions, @@ -324,12 +323,12 @@ StatusWith<CollModRequest> parseCollModRequest(OperationContext* opCtx, << "option. Got: '" << elemStr << "'. Accepted value is 'off'"); } } else { - invariant(elem.type() == mongo::NumberLong); - const int64_t elemNum = elem.safeNumberLong(); + invariant(e.type() == mongo::NumberLong); + const int64_t elemNum = e.safeNumberLong(); uassertStatusOK(index_key_validate::validateExpireAfterSeconds(elemNum)); } - cmr.clusteredIndexExpireAfterSeconds = e.Obj()["expireAfterSeconds"]; + cmr.clusteredIndexExpireAfterSeconds = e; } else { if (isView) { return Status(ErrorCodes::InvalidOptions, @@ -386,10 +385,9 @@ void _setClusteredExpireAfterSeconds(OperationContext* opCtx, const CollectionOptions& oldCollOptions, Collection* coll, const BSONElement& clusteredIndexExpireAfterSeconds) { - invariant(oldCollOptions.clusteredIndex.has_value()); + invariant(oldCollOptions.clusteredIndex); - boost::optional<int64_t> oldExpireAfterSeconds = - oldCollOptions.clusteredIndex->getExpireAfterSeconds(); + boost::optional<int64_t> oldExpireAfterSeconds = oldCollOptions.expireAfterSeconds; if (clusteredIndexExpireAfterSeconds.type() == mongo::String) { const std::string newExpireAfterSeconds = clusteredIndexExpireAfterSeconds.String(); diff --git a/src/mongo/db/catalog/collection_impl.cpp b/src/mongo/db/catalog/collection_impl.cpp index a7f84f1007c..df567000f73 100644 --- a/src/mongo/db/catalog/collection_impl.cpp +++ b/src/mongo/db/catalog/collection_impl.cpp @@ -383,7 +383,7 @@ void CollectionImpl::init(OperationContext* opCtx) { if (collectionOptions.clusteredIndex) { _clustered = true; - if (collectionOptions.clusteredIndex->getExpireAfterSeconds()) { + if (collectionOptions.expireAfterSeconds) { // TTL indexes are not compatible with capped collections. invariant(!collectionOptions.capped); @@ -1283,7 +1283,7 @@ void CollectionImpl::updateClusteredIndexTTLSetting(OperationContext* opCtx, _metadata->options.clusteredIndex); _writeMetadata(opCtx, [&](BSONCollectionCatalogEntry::MetaData& md) { - md.options.clusteredIndex->setExpireAfterSeconds(expireAfterSeconds); + md.options.expireAfterSeconds = expireAfterSeconds; }); } diff --git a/src/mongo/db/catalog/collection_options.cpp b/src/mongo/db/catalog/collection_options.cpp index a2149f9a0ba..b5f1d32afed 100644 --- a/src/mongo/db/catalog/collection_options.cpp +++ b/src/mongo/db/catalog/collection_options.cpp @@ -194,16 +194,17 @@ StatusWith<CollectionOptions> CollectionOptions::parse(const BSONObj& options, P collectionOptions.collation = e.Obj().getOwned(); } else if (fieldName == "clusteredIndex") { - if (e.type() != mongo::Object) { - return Status(ErrorCodes::BadValue, "'clusteredIndex' has to be a document."); + if (e.type() != mongo::Bool) { + return Status(ErrorCodes::BadValue, "'clusteredIndex' has to be a boolean."); } - try { - collectionOptions.clusteredIndex = - ClusteredIndexOptions::parse({"CollectionOptions::parse"}, e.Obj()); - } catch (const DBException& ex) { - return ex.toStatus(); + collectionOptions.clusteredIndex = e.Bool(); + } else if (fieldName == "expireAfterSeconds") { + if (e.type() != mongo::NumberLong) { + return {ErrorCodes::BadValue, "'expireAfterSeconds' must be a number."}; } + + collectionOptions.expireAfterSeconds = e.Long(); } else if (fieldName == "viewOn") { if (e.type() != mongo::String) { return Status(ErrorCodes::BadValue, "'viewOn' has to be a string."); @@ -303,7 +304,10 @@ CollectionOptions CollectionOptions::fromCreateCommand(const CreateCommand& cmd) options.timeseries = std::move(*timeseries); } if (auto clusteredIndex = cmd.getClusteredIndex()) { - options.clusteredIndex = std::move(*clusteredIndex); + options.clusteredIndex = *clusteredIndex; + } + if (auto expireAfterSeconds = cmd.getExpireAfterSeconds()) { + options.expireAfterSeconds = expireAfterSeconds; } if (auto temp = cmd.getTemp()) { options.temp = *temp; @@ -366,7 +370,11 @@ void CollectionOptions::appendBSON(BSONObjBuilder* builder, bool includeUUID) co } if (clusteredIndex) { - builder->append("clusteredIndex", clusteredIndex->toBSON()); + builder->append("clusteredIndex", true); + } + + if (expireAfterSeconds) { + builder->append("expireAfterSeconds", *expireAfterSeconds); } if (!viewOn.empty()) { @@ -458,9 +466,11 @@ bool CollectionOptions::matchesStorageOptions(const CollectionOptions& other, return false; } - if ((clusteredIndex && other.clusteredIndex && - clusteredIndex->toBSON().woCompare(other.clusteredIndex->toBSON())) || - (!clusteredIndex != !other.clusteredIndex)) { + if (clusteredIndex != other.clusteredIndex) { + return false; + } + + if (expireAfterSeconds != other.expireAfterSeconds) { return false; } diff --git a/src/mongo/db/catalog/collection_options.h b/src/mongo/db/catalog/collection_options.h index a8dcf0d28df..16be9667804 100644 --- a/src/mongo/db/catalog/collection_options.h +++ b/src/mongo/db/catalog/collection_options.h @@ -34,7 +34,6 @@ #include <boost/optional.hpp> #include "mongo/base/status.h" -#include "mongo/db/catalog/clustered_index_options_gen.h" #include "mongo/db/catalog/collection_options_gen.h" #include "mongo/db/jsobj.h" #include "mongo/db/timeseries/timeseries_gen.h" @@ -146,8 +145,12 @@ struct CollectionOptions { // The namespace's default collation. BSONObj collation; - // If present, defines how this collection is clustered on _id. - boost::optional<ClusteredIndexOptions> clusteredIndex; + // Whether this collection is clustered on _id. + bool clusteredIndex = false; + + // If present, the number of seconds after which old data should be deleted. Only for + // collections which are clustered on _id. + boost::optional<int64_t> expireAfterSeconds; // View-related options. // The namespace of the view or collection that "backs" this view, or the empty string if this diff --git a/src/mongo/db/catalog/create_collection.cpp b/src/mongo/db/catalog/create_collection.cpp index b9c532386bd..e5d825c2c49 100644 --- a/src/mongo/db/catalog/create_collection.cpp +++ b/src/mongo/db/catalog/create_collection.cpp @@ -245,15 +245,14 @@ Status _createTimeseries(OperationContext* opCtx, // If possible, cluster time-series buckets collections by _id. const bool useClusteredIdIndex = gTimeseriesBucketsCollectionClusterById && opCtx->getServiceContext()->getStorageEngine()->supportsClusteredIdIndex(); - auto expireAfterSeconds = options.timeseries->getExpireAfterSeconds(); + auto expireAfterSeconds = options.expireAfterSeconds; if (useClusteredIdIndex) { - ClusteredIndexOptions clusteredOptions; if (expireAfterSeconds) { uassertStatusOK( index_key_validate::validateExpireAfterSeconds(*expireAfterSeconds)); - clusteredOptions.setExpireAfterSeconds(*expireAfterSeconds); + bucketsOptions.expireAfterSeconds = expireAfterSeconds; } - bucketsOptions.clusteredIndex = clusteredOptions; + bucketsOptions.clusteredIndex = true; } // Create a TTL index on 'control.min.[timeField]' if 'expireAfterSeconds' is provided diff --git a/src/mongo/db/coll_mod.idl b/src/mongo/db/coll_mod.idl index ead45bab5cd..638c0f7ccc9 100644 --- a/src/mongo/db/coll_mod.idl +++ b/src/mongo/db/coll_mod.idl @@ -71,16 +71,6 @@ structs: optional: true type: safeBool - CollModClusteredIndex: - description: "A type representing the adjustable options on clustered indexes" - strict: true - fields: - expireAfterSeconds: - description: "The number of seconds after which old data should be deleted. This can - be disabled by passing in 'off' as a value" - type: - variant: [string, safeInt64] - commands: collMod: description: "Specify collMod Command." @@ -137,8 +127,10 @@ commands: document in the oplog" optional: true type: safeBool - clusteredIndex: - description: "Adjusts the options on clustered indexes" + expireAfterSeconds: + description: "The number of seconds after which old data should be deleted. This can + be disabled by passing in 'off' as a value" optional: true - type: CollModClusteredIndex + type: + variant: [string, safeInt64] reply_type: CollModReply diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript index 2442d34a331..95a776a5705 100644 --- a/src/mongo/db/commands/SConscript +++ b/src/mongo/db/commands/SConscript @@ -263,7 +263,6 @@ env.Library( 'create_command_validation.cpp', ], LIBDEPS=[ - '$BUILD_DIR/mongo/db/catalog/clustered_index_options_idl', '$BUILD_DIR/mongo/db/timeseries/timeseries_idl', ], LIBDEPS_PRIVATE=[ diff --git a/src/mongo/db/commands/create.idl b/src/mongo/db/commands/create.idl index 37ad939d6ae..5314b402650 100644 --- a/src/mongo/db/commands/create.idl +++ b/src/mongo/db/commands/create.idl @@ -35,7 +35,6 @@ imports: - "mongo/db/auth/access_checks.idl" - "mongo/db/auth/action_type.idl" - "mongo/db/catalog/collection_options.idl" - - "mongo/db/catalog/clustered_index_options.idl" - "mongo/db/timeseries/timeseries.idl" structs: @@ -165,8 +164,12 @@ commands: type: TimeseriesOptions optional: true clusteredIndex: - description: "Specifies how this collection should be clustered on _id." - type: ClusteredIndexOptions + description: "Specifies whether this collection should be clustered on _id." + type: safeBool + optional: true + expireAfterSeconds: + description: "The number of seconds after which old data should be deleted." + type: safeInt64 optional: true temp: description: "DEPRECATED" diff --git a/src/mongo/db/commands/create_command.cpp b/src/mongo/db/commands/create_command.cpp index 160c466f765..40442e764dc 100644 --- a/src/mongo/db/commands/create_command.cpp +++ b/src/mongo/db/commands/create_command.cpp @@ -166,12 +166,16 @@ public: ErrorCodes::InvalidOptions, timeseriesNotAllowedWith("size"), !cmd.getSize()); uassert(ErrorCodes::InvalidOptions, timeseriesNotAllowedWith("max"), !cmd.getMax()); - // The 'timeseries' option may be passed with a 'validator' if a buckets collection - // is being restored. We assume the caller knows what they are doing. + // The 'timeseries' option may be passed with a 'validator' or 'clusteredIndex' if a + // buckets collection is being restored. We assume the caller knows what they are + // doing. if (!cmd.getNamespace().isTimeseriesBucketsCollection()) { uassert(ErrorCodes::InvalidOptions, timeseriesNotAllowedWith("validator"), !cmd.getValidator()); + uassert(ErrorCodes::InvalidOptions, + timeseriesNotAllowedWith("clusteredIndex"), + !cmd.getClusteredIndex()); } uassert(ErrorCodes::InvalidOptions, timeseriesNotAllowedWith("validationLevel"), @@ -211,6 +215,14 @@ public: } } + if (cmd.getExpireAfterSeconds()) { + uassert(ErrorCodes::InvalidOptions, + "'expireAfterSeconds' is only supported on time-series collections", + cmd.getTimeseries() || + (cmd.getClusteredIndex() && + cmd.getNamespace().isTimeseriesBucketsCollection())); + } + // Validate _id index spec and fill in missing fields. if (cmd.getIdIndex()) { auto idIndexSpec = *cmd.getIdIndex(); diff --git a/src/mongo/db/commands/dbcommands.cpp b/src/mongo/db/commands/dbcommands.cpp index e20b53a358d..28bd1ec7ec2 100644 --- a/src/mongo/db/commands/dbcommands.cpp +++ b/src/mongo/db/commands/dbcommands.cpp @@ -143,7 +143,7 @@ std::unique_ptr<CollMod> makeTimeseriesCollModCommand(OperationContext* opCtx, cmd->setViewOn(origCmd.getViewOn()); cmd->setPipeline(origCmd.getPipeline()); cmd->setRecordPreImages(origCmd.getRecordPreImages()); - cmd->setClusteredIndex(origCmd.getClusteredIndex()); + cmd->setExpireAfterSeconds(origCmd.getExpireAfterSeconds()); return cmd; } diff --git a/src/mongo/db/storage/record_store_test_harness.cpp b/src/mongo/db/storage/record_store_test_harness.cpp index ec8aed31d1d..3ca1cafd935 100644 --- a/src/mongo/db/storage/record_store_test_harness.cpp +++ b/src/mongo/db/storage/record_store_test_harness.cpp @@ -415,7 +415,7 @@ TEST(RecordStoreTestHarness, ClusteredRecordStore) { const std::string ns = "test.system.buckets.a"; CollectionOptions options; - options.clusteredIndex = ClusteredIndexOptions{}; + options.clusteredIndex = true; std::unique_ptr<RecordStore> rs = harnessHelper->newNonCappedRecordStore(ns, options); invariant(rs->keyFormat() == KeyFormat::String); @@ -526,7 +526,7 @@ TEST(RecordStoreTestHarness, ClusteredRecordStoreSeekNear) { const std::string ns = "test.system.buckets.a"; CollectionOptions options; - options.clusteredIndex = ClusteredIndexOptions{}; + options.clusteredIndex = true; std::unique_ptr<RecordStore> rs = harnessHelper->newNonCappedRecordStore(ns, options); invariant(rs->keyFormat() == KeyFormat::String); diff --git a/src/mongo/db/timeseries/timeseries.idl b/src/mongo/db/timeseries/timeseries.idl index 6638041645c..046e7652346 100644 --- a/src/mongo/db/timeseries/timeseries.idl +++ b/src/mongo/db/timeseries/timeseries.idl @@ -87,11 +87,6 @@ structs: be \"_id\" or the same as 'timeField'." type: string optional: true - expireAfterSeconds: - description: "The number of seconds after which old time-series data should be - deleted." - type: safeInt64 - optional: true granularity: description: "Describes the expected interval between subsequent measurements" type: BucketGranularity diff --git a/src/mongo/db/ttl.cpp b/src/mongo/db/ttl.cpp index bf7bca72dc1..24d321b7a5f 100644 --- a/src/mongo/db/ttl.cpp +++ b/src/mongo/db/ttl.cpp @@ -453,7 +453,7 @@ private: collOptions.clusteredIndex); invariant(collection->isClustered()); - auto expireAfterSeconds = collOptions.clusteredIndex->getExpireAfterSeconds(); + auto expireAfterSeconds = collOptions.expireAfterSeconds; if (!expireAfterSeconds) { ttlCollectionCache->deregisterTTLInfo(collection->uuid(), TTLCollectionCache::ClusteredId{}); diff --git a/src/mongo/dbtests/query_stage_collscan.cpp b/src/mongo/dbtests/query_stage_collscan.cpp index 6b1479f32bc..cb9a8038509 100644 --- a/src/mongo/dbtests/query_stage_collscan.cpp +++ b/src/mongo/dbtests/query_stage_collscan.cpp @@ -179,7 +179,7 @@ public: WriteUnitOfWork wuow(&_opCtx); CollectionOptions collOptions; - collOptions.clusteredIndex = ClusteredIndexOptions{}; + collOptions.clusteredIndex = true; const bool createIdIndex = false; db->createCollection(&_opCtx, ns, collOptions, createIdIndex); wuow.commit(); diff --git a/src/mongo/dbtests/validate_tests.cpp b/src/mongo/dbtests/validate_tests.cpp index a1db385e352..cc00ef98b8b 100644 --- a/src/mongo/dbtests/validate_tests.cpp +++ b/src/mongo/dbtests/validate_tests.cpp @@ -76,7 +76,7 @@ public: CollectionOptions options; if (clustered && _supportsClusteredIdIndex) { - options.clusteredIndex = ClusteredIndexOptions{}; + options.clusteredIndex = true; } const bool createIdIndex = !clustered; |