summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGregory Noma <gregory.noma@gmail.com>2021-05-24 09:55:04 -0400
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2021-05-24 20:31:30 +0000
commite465a2c8dcc8d8f0676eaf266e809a71d946c07c (patch)
treeb622964ffdae1599979aea1ee92ddfe6b8c69fda
parentb5a61bb4682788503b4e4808069754ba5d914b1b (diff)
downloadmongo-e465a2c8dcc8d8f0676eaf266e809a71d946c07c.tar.gz
SERVER-56934 Make expireAfterSeconds a top-level collection option
(cherry picked from commit b173494aa6b84b5d44c5f958fd78dd469596a314)
-rw-r--r--buildscripts/idl/idl_compatibility_errors.py2
-rw-r--r--jstests/concurrency/fsm_workloads/insert_ttl_timeseries.js4
-rw-r--r--jstests/core/timeseries/clustered_index_options.js27
-rw-r--r--jstests/core/timeseries/clustered_index_types.js2
-rw-r--r--jstests/core/timeseries/timeseries_create_drop.js12
-rw-r--r--jstests/core/timeseries/timeseries_expire.js2
-rw-r--r--jstests/core/timeseries/timeseries_expire_collmod.js43
-rw-r--r--jstests/noPassthrough/timeseries_create.js38
-rw-r--r--jstests/noPassthrough/timeseries_non_clustered_collmod.js4
-rw-r--r--jstests/noPassthrough/timeseries_ttl.js6
-rw-r--r--jstests/replsets/rollback_clustered_indexes.js2
-rw-r--r--src/mongo/db/catalog/SConscript12
-rw-r--r--src/mongo/db/catalog/clustered_index_options.idl42
-rw-r--r--src/mongo/db/catalog/coll_mod.cpp24
-rw-r--r--src/mongo/db/catalog/collection_impl.cpp4
-rw-r--r--src/mongo/db/catalog/collection_options.cpp34
-rw-r--r--src/mongo/db/catalog/collection_options.h9
-rw-r--r--src/mongo/db/catalog/create_collection.cpp7
-rw-r--r--src/mongo/db/coll_mod.idl18
-rw-r--r--src/mongo/db/commands/SConscript1
-rw-r--r--src/mongo/db/commands/create.idl9
-rw-r--r--src/mongo/db/commands/create_command.cpp16
-rw-r--r--src/mongo/db/commands/dbcommands.cpp2
-rw-r--r--src/mongo/db/storage/record_store_test_harness.cpp4
-rw-r--r--src/mongo/db/timeseries/timeseries.idl5
-rw-r--r--src/mongo/db/ttl.cpp2
-rw-r--r--src/mongo/dbtests/query_stage_collscan.cpp2
-rw-r--r--src/mongo/dbtests/validate_tests.cpp2
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;