summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--jstests/core/collmod_convert_to_ttl.js50
-rw-r--r--jstests/core/timeseries/timeseries_collmod.js59
-rw-r--r--src/mongo/db/catalog/coll_mod.cpp46
3 files changed, 106 insertions, 49 deletions
diff --git a/jstests/core/collmod_convert_to_ttl.js b/jstests/core/collmod_convert_to_ttl.js
index b5803db68d2..e1e66ea7a0b 100644
--- a/jstests/core/collmod_convert_to_ttl.js
+++ b/jstests/core/collmod_convert_to_ttl.js
@@ -5,60 +5,62 @@
* # Cannot implicitly shard accessed collections because of collection existing when none
* # expected.
* assumes_no_implicit_collection_creation_after_drop,
- *
* requires_non_retryable_commands,
* requires_ttl_index,
- * multiversion_incompatible,
+ * requires_fcv_51,
* ]
*/
(function() {
'use strict';
-let collName = "collmod_convert_to_ttl";
-let coll = db.getCollection(collName);
+const collName = "collmod_convert_to_ttl";
+const coll = db.getCollection(collName);
coll.drop();
db.createCollection(collName);
function findTTL(key, expireAfterSeconds) {
- let all = coll.getIndexes();
- all = all.filter(function(z) {
+ const all = coll.getIndexes().filter(function(z) {
return z.expireAfterSeconds == expireAfterSeconds && friendlyEqual(z.key, key);
});
return all.length == 1;
}
// Creates a regular index and use collMod to convert it to a TTL index.
-coll.dropIndex({a: 1});
coll.createIndex({a: 1});
// Tries to modify with a string 'expireAfterSeconds' value.
-let res = db.runCommand(
- {"collMod": collName, "index": {"keyPattern": {a: 1}, "expireAfterSeconds": "100"}});
-assert.commandFailedWithCode(res, ErrorCodes.TypeMismatch);
+assert.commandFailedWithCode(
+ db.runCommand(
+ {"collMod": collName, "index": {"keyPattern": {a: 1}, "expireAfterSeconds": "100"}}),
+ ErrorCodes.TypeMismatch);
// Tries to modify with a negative 'expireAfterSeconds' value.
-res =
- db.runCommand({"collMod": collName, "index": {"keyPattern": {a: 1}, "expireAfterSeconds": -1}});
-assert.commandFailedWithCode(res, ErrorCodes.InvalidOptions);
+assert.commandFailedWithCode(
+ db.runCommand({"collMod": collName, "index": {"keyPattern": {a: 1}, "expireAfterSeconds": -1}}),
+ ErrorCodes.InvalidOptions);
// Tries to modify with an 'expireAfterSeconds' value too large.
-res = db.runCommand(
- {"collMod": collName, "index": {"keyPattern": {a: 1}, "expireAfterSeconds": 10000000000000}});
-assert.commandFailedWithCode(res, ErrorCodes.InvalidOptions);
+assert.commandFailedWithCode(db.runCommand({
+ "collMod": collName,
+ "index": {"keyPattern": {a: 1}, "expireAfterSeconds": 10000000000000}
+}),
+ ErrorCodes.InvalidOptions);
// Successfully converts to a TTL index.
-res = db.runCommand(
- {"collMod": collName, "index": {"keyPattern": {a: 1}, "expireAfterSeconds": 100}});
+assert.commandWorked(db.runCommand(
+ {"collMod": collName, "index": {"keyPattern": {a: 1}, "expireAfterSeconds": 100}}));
assert(findTTL({a: 1}, 100), "TTL index should be 100 now");
// Tries to convert a compound index to a TTL index.
coll.createIndex({a: 1, b: 1});
-res = db.runCommand(
- {"collMod": collName, "index": {"keyPattern": {a: 1, b: 1}, "expireAfterSeconds": 100}});
-assert.commandFailedWithCode(res, ErrorCodes.InvalidOptions);
+assert.commandFailedWithCode(
+ db.runCommand(
+ {"collMod": collName, "index": {"keyPattern": {a: 1, b: 1}, "expireAfterSeconds": 100}}),
+ ErrorCodes.InvalidOptions);
// Tries to convert the '_id' index to a TTL index.
-res = db.runCommand(
- {"collMod": collName, "index": {"keyPattern": {_id: 1}, "expireAfterSeconds": 100}});
-assert.commandFailedWithCode(res, ErrorCodes.InvalidOptions);
+assert.commandFailedWithCode(
+ db.runCommand(
+ {"collMod": collName, "index": {"keyPattern": {_id: 1}, "expireAfterSeconds": 100}}),
+ ErrorCodes.InvalidOptions);
})(); \ No newline at end of file
diff --git a/jstests/core/timeseries/timeseries_collmod.js b/jstests/core/timeseries/timeseries_collmod.js
new file mode 100644
index 00000000000..69fd7dbeb82
--- /dev/null
+++ b/jstests/core/timeseries/timeseries_collmod.js
@@ -0,0 +1,59 @@
+/**
+ * This tests which collMod options are allowed on a time-series collection.
+ *
+ * @tags: [
+ * # Cannot implicitly shard accessed collections because of collection existing when none
+ * # expected.
+ * assumes_no_implicit_collection_creation_after_drop,
+ * requires_non_retryable_commands,
+ * requires_fcv_51,
+ * ]
+ */
+
+(function() {
+'use strict';
+const collName = "timeseries_collmod";
+const coll = db.getCollection(collName);
+coll.drop();
+assert.commandWorked(
+ db.createCollection(collName, {timeseries: {timeField: "time", granularity: 'seconds'}}));
+assert.commandWorked(coll.createIndex({"time": 1}));
+
+// Tries to convert a time-series secondary index to TTL index.
+assert.commandFailedWithCode(
+ db.runCommand(
+ {"collMod": collName, "index": {"keyPattern": {"time": 1}, "expireAfterSeconds": 100}}),
+ ErrorCodes.InvalidOptions);
+
+// Successfully hides a time-series secondary index.
+assert.commandWorked(
+ db.runCommand({"collMod": collName, "index": {"keyPattern": {"time": 1}, "hidden": true}}));
+
+// Tries to set the validator for a time-series collection.
+assert.commandFailedWithCode(
+ db.runCommand({"collMod": collName, "validator": {required: ["time"]}}),
+ ErrorCodes.InvalidOptions);
+
+// Tries to set the validationLevel for a time-series collection.
+assert.commandFailedWithCode(db.runCommand({"collMod": collName, "validationLevel": "moderate"}),
+ ErrorCodes.InvalidOptions);
+
+// Tries to set the validationAction for a time-series collection.
+assert.commandFailedWithCode(db.runCommand({"collMod": collName, "validationAction": "warn"}),
+ ErrorCodes.InvalidOptions);
+
+// Tries to modify the view for a time-series collection.
+assert.commandFailedWithCode(db.runCommand({"collMod": collName, "viewOn": "foo", "pipeline": []}),
+ ErrorCodes.InvalidOptions);
+
+// Tries to set 'recordPreImages' for a time-series collection.
+assert.commandFailedWithCode(db.runCommand({"collMod": collName, "recordPreImages": true}),
+ ErrorCodes.InvalidOptions);
+
+// Successfully sets 'expireAfterSeconds' for a time-series collection.
+assert.commandWorked(db.runCommand({"collMod": collName, "expireAfterSeconds": 60}));
+
+// Successfully sets the granularity for a time-series collection.
+assert.commandWorked(
+ db.runCommand({"collMod": collName, "timeseries": {"granularity": "minutes"}}));
+})(); \ No newline at end of file
diff --git a/src/mongo/db/catalog/coll_mod.cpp b/src/mongo/db/catalog/coll_mod.cpp
index f18a6797df5..a683d603d7c 100644
--- a/src/mongo/db/catalog/coll_mod.cpp
+++ b/src/mongo/db/catalog/coll_mod.cpp
@@ -122,6 +122,7 @@ StatusWith<CollModRequest> parseCollModRequest(OperationContext* opCtx,
BSONObjBuilder* oplogEntryBuilder) {
bool isView = !coll;
+ bool isTimeseries = coll && coll->getTimeseriesOptions() != boost::none;
CollModRequest cmr;
@@ -179,6 +180,12 @@ StatusWith<CollModRequest> parseCollModRequest(OperationContext* opCtx,
return Status(ErrorCodes::InvalidOptions, "no expireAfterSeconds or hidden field");
}
if (!cmr.indexExpireAfterSeconds.eoo()) {
+ if (isTimeseries) {
+ return Status(ErrorCodes::InvalidOptions,
+ "TTL indexes are not supported for time-series collections. "
+ "Please refer to the documentation and use the top-level "
+ "'expireAfterSeconds' option instead");
+ }
if (auto status = index_key_validate::validateExpireAfterSeconds(
cmr.indexExpireAfterSeconds.safeNumberLong());
!status.isOK()) {
@@ -261,7 +268,7 @@ StatusWith<CollModRequest> parseCollModRequest(OperationContext* opCtx,
return Status(ErrorCodes::BadValue, "can't hide _id index");
}
}
- } else if (fieldName == "validator" && !isView) {
+ } else if (fieldName == "validator" && !isView && !isTimeseries) {
// If the feature compatibility version is not kLatest, and we are validating features
// as primary, ban the use of new agg features introduced in kLatest to prevent them
// from being persisted in the catalog.
@@ -281,13 +288,13 @@ StatusWith<CollModRequest> parseCollModRequest(OperationContext* opCtx,
if (!cmr.collValidator->isOK()) {
return cmr.collValidator->getStatus();
}
- } else if (fieldName == "validationLevel" && !isView) {
+ } else if (fieldName == "validationLevel" && !isView && !isTimeseries) {
try {
cmr.collValidationLevel = ValidationLevel_parse({"validationLevel"}, e.String());
} catch (const DBException& exc) {
return exc.toStatus();
}
- } else if (fieldName == "validationAction" && !isView) {
+ } else if (fieldName == "validationAction" && !isView && !isTimeseries) {
try {
cmr.collValidationAction = ValidationAction_parse({"validationAction"}, e.String());
} catch (const DBException& exc) {
@@ -311,25 +318,9 @@ StatusWith<CollModRequest> parseCollModRequest(OperationContext* opCtx,
return Status(ErrorCodes::InvalidOptions, "'viewOn' option must be a string");
}
cmr.viewOn = e.str();
- } else if (fieldName == "recordPreImages") {
- if (isView) {
- return {ErrorCodes::InvalidOptions,
- str::stream() << "option not supported on a view: " << fieldName};
- }
-
+ } else if (fieldName == "recordPreImages" && !isView && !isTimeseries) {
cmr.recordPreImages = e.trueValue();
- } else if (fieldName == "changeStreamPreAndPostImages") {
- if (nss.isTimeseriesBucketsCollection()) {
- return {ErrorCodes::InvalidOptions,
- str::stream()
- << "option not supported on a timeseries collection: " << fieldName};
- }
-
- if (isView) {
- return {ErrorCodes::InvalidOptions,
- str::stream() << "option not supported on a view: " << fieldName};
- }
-
+ } else if (fieldName == "changeStreamPreAndPostImages" && !isView && !isTimeseries) {
if (e.type() != mongo::Bool) {
return {ErrorCodes::InvalidOptions,
"'changeStreamPreAndPostImages' option must be a boolean"};
@@ -359,16 +350,21 @@ StatusWith<CollModRequest> parseCollModRequest(OperationContext* opCtx,
}
cmr.clusteredIndexExpireAfterSeconds = e;
- } else if (fieldName == "timeseries" && !isView) {
- auto tsOptions = coll->getTimeseriesOptions();
- if (!tsOptions) {
+ } else if (fieldName == "timeseries") {
+ if (!isTimeseries) {
return Status(ErrorCodes::InvalidOptions,
- str::stream() << "option only supported on a timeseries collection: "
+ str::stream() << "option only supported on a time-series collection: "
<< fieldName);
}
cmr.timeseries = e;
} else {
+ if (isTimeseries) {
+ return Status(ErrorCodes::InvalidOptions,
+ str::stream() << "option not supported on a time-series collection: "
+ << fieldName);
+ }
+
if (isView) {
return Status(ErrorCodes::InvalidOptions,
str::stream() << "option not supported on a view: " << fieldName);