summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGregory Noma <gregory.noma@gmail.com>2021-05-24 18:24:17 -0400
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2021-05-25 04:22:29 +0000
commit61710dae9c72f603f15882f3005b729c7f84bcf6 (patch)
treef0b3117b9775888235d934249de0c9ae3452d14c
parentfcddb1393fa511f68706b1ff8e74c65f40c086bc (diff)
downloadmongo-61710dae9c72f603f15882f3005b729c7f84bcf6.tar.gz
SERVER-56933 Return options to create an identical time-series collection from listCollections
(cherry picked from commit 8d5547cdd6c45dd68c95dcf353e459e62da5222b)
-rw-r--r--jstests/core/list_collections_no_views.js2
-rw-r--r--jstests/core/timeseries/timeseries_list_collections.js120
-rw-r--r--jstests/core/timeseries/timeseries_list_collections_filter_name.js37
-rw-r--r--jstests/core/timeseries/timeseries_list_collections_invalid_view.js51
-rw-r--r--jstests/core/timeseries/timeseries_list_collections_missing_buckets_collection.js39
-rw-r--r--jstests/core/timeseries/timeseries_list_collections_missing_view.js30
-rw-r--r--jstests/hooks/run_validate_collections_background.js1
-rw-r--r--src/mongo/db/catalog/collection_options.cpp83
-rw-r--r--src/mongo/db/catalog/collection_options.h9
-rw-r--r--src/mongo/db/commands/create_command.cpp63
-rw-r--r--src/mongo/db/commands/list_collections.cpp106
-rw-r--r--src/mongo/db/exec/bucket_unpacker.cpp2
-rw-r--r--src/mongo/db/exec/sample_from_timeseries_bucket.cpp2
-rw-r--r--src/mongo/db/pipeline/document_source_internal_convert_bucket_index_stats.cpp2
-rw-r--r--src/mongo/db/pipeline/document_source_internal_unpack_bucket.cpp2
-rw-r--r--src/mongo/db/pipeline/document_source_internal_unpack_bucket_test/unpack_bucket_exec_test.cpp2
-rw-r--r--src/mongo/db/timeseries/timeseries_constants.h (renamed from src/mongo/db/timeseries/timeseries_field_names.h)9
-rw-r--r--src/mongo/db/timeseries/timeseries_index_schema_conversion_functions.cpp2
-rw-r--r--src/mongo/db/timeseries/timeseries_index_schema_conversion_functions_test.cpp2
-rw-r--r--src/mongo/db/views/resolved_view.cpp2
20 files changed, 472 insertions, 94 deletions
diff --git a/jstests/core/list_collections_no_views.js b/jstests/core/list_collections_no_views.js
index 378b15f1085..45e796c50e5 100644
--- a/jstests/core/list_collections_no_views.js
+++ b/jstests/core/list_collections_no_views.js
@@ -52,7 +52,7 @@ assert.eq(allExpected,
return 0;
}));
-// {type: {$exists: false}} is needed for versions <= 3.2
+// TODO (SERVER-25493): {type: {$exists: false}} is needed for versions <= 3.2
let collOnlyCommand = {
listCollections: 1,
filter: {$or: [{type: 'collection'}, {type: {$exists: false}}]}
diff --git a/jstests/core/timeseries/timeseries_list_collections.js b/jstests/core/timeseries/timeseries_list_collections.js
new file mode 100644
index 00000000000..9038fb53e05
--- /dev/null
+++ b/jstests/core/timeseries/timeseries_list_collections.js
@@ -0,0 +1,120 @@
+/**
+ * Tests the result of running listCollections when there are time-series collections present.
+ *
+ * @tags: [
+ * assumes_no_implicit_collection_creation_after_drop,
+ * does_not_support_transactions,
+ * requires_fcv_49,
+ * requires_find_command,
+ * requires_getmore,
+ * ]
+ */
+(function() {
+'use strict';
+
+const testDB = db.getSiblingDB(jsTestName());
+assert.commandWorked(testDB.dropDatabase());
+
+const timeFieldName = 'time';
+const metaFieldName = 'meta';
+const coll = testDB.getCollection('t');
+
+const getBucketMaxSpanSeconds = function(granularity) {
+ switch (granularity) {
+ case 'seconds':
+ return 60 * 60;
+ case 'minutes':
+ return 60 * 60 * 24;
+ case 'hours':
+ return 60 * 60 * 24 * 30;
+ default:
+ assert(false, 'Invalid granularity: ' + granularity);
+ }
+};
+
+const testOptions = function(options) {
+ coll.drop();
+ jsTestLog('Creating time-series collection with options: ' + tojson(options));
+ assert.commandWorked(testDB.createCollection(coll.getName(), options));
+
+ if (!options.timeseries.hasOwnProperty('granularity')) {
+ Object.assign(options.timeseries, {granularity: 'seconds'});
+ }
+ if (!options.timeseries.hasOwnProperty('bucketMaxSpanSeconds')) {
+ Object.assign(
+ options.timeseries,
+ {bucketMaxSpanSeconds: getBucketMaxSpanSeconds(options.timeseries.granularity)});
+ }
+
+ if (options.hasOwnProperty('collation')) {
+ Object.assign(options.collation, {
+ caseLevel: false,
+ caseFirst: 'off',
+ strength: 3,
+ numericOrdering: false,
+ alternate: 'non-ignorable',
+ maxVariable: 'punct',
+ normalization: false,
+ backwards: false,
+ version: '57.1',
+ });
+ }
+
+ const collections =
+ assert.commandWorked(testDB.runCommand({listCollections: 1})).cursor.firstBatch;
+ jsTestLog('Checking listCollections result: ' + tojson(collections));
+ assert.eq(collections.length, 3);
+ assert(collections.find(entry => entry.name === 'system.views'));
+ assert(collections.find(entry => entry.name === 'system.buckets.' + coll.getName()));
+ assert.docEq(
+ collections.find(entry => entry.name === coll.getName()),
+ {name: coll.getName(), type: 'timeseries', options: options, info: {readOnly: false}});
+};
+
+testOptions({timeseries: {timeField: timeFieldName}});
+testOptions({timeseries: {timeField: timeFieldName, metaField: metaFieldName}});
+testOptions({
+ timeseries: {
+ timeField: timeFieldName,
+ granularity: 'minutes',
+ }
+});
+testOptions({
+ timeseries: {
+ timeField: timeFieldName,
+ granularity: 'minutes',
+ bucketMaxSpanSeconds: 60 * 60 * 24,
+ }
+});
+testOptions({
+ timeseries: {
+ timeField: timeFieldName,
+ },
+ storageEngine: {wiredTiger: {}},
+});
+testOptions({
+ timeseries: {
+ timeField: timeFieldName,
+ },
+ indexOptionDefaults: {storageEngine: {wiredTiger: {}}},
+});
+testOptions({
+ timeseries: {
+ timeField: timeFieldName,
+ },
+ collation: {locale: 'ja'},
+});
+testOptions({timeseries: {timeField: timeFieldName}, expireAfterSeconds: NumberLong(100)});
+testOptions({
+ timeseries: {
+ timeField: timeFieldName,
+ metaField: metaFieldName,
+ granularity: 'minutes',
+ bucketMaxSpanSeconds: 60 * 60 * 24,
+ },
+ storageEngine: {wiredTiger: {}},
+ indexOptionDefaults: {storageEngine: {wiredTiger: {}}},
+ collation: {locale: 'ja'},
+ expireAfterSeconds: NumberLong(100),
+});
+})();
diff --git a/jstests/core/timeseries/timeseries_list_collections_filter_name.js b/jstests/core/timeseries/timeseries_list_collections_filter_name.js
new file mode 100644
index 00000000000..6f88fe1fe82
--- /dev/null
+++ b/jstests/core/timeseries/timeseries_list_collections_filter_name.js
@@ -0,0 +1,37 @@
+/**
+ * Tests that listCollections includes time-series collections and their options when filtering on
+ * name.
+ *
+ * @tags: [
+ * assumes_no_implicit_collection_creation_after_drop,
+ * does_not_support_transactions,
+ * requires_fcv_49,
+ * requires_find_command,
+ * requires_getmore,
+ * ]
+ */
+(function() {
+'use strict';
+
+const testDB = db.getSiblingDB(jsTestName());
+assert.commandWorked(testDB.dropDatabase());
+
+const timeFieldName = 'time';
+const coll = testDB.getCollection('t');
+
+assert.commandWorked(
+ testDB.createCollection(coll.getName(), {timeseries: {timeField: timeFieldName}}));
+
+const collections =
+ assert.commandWorked(testDB.runCommand({listCollections: 1, filter: {name: coll.getName()}}))
+ .cursor.firstBatch;
+assert.eq(collections, [{
+ name: coll.getName(),
+ type: 'timeseries',
+ options: {
+ timeseries:
+ {timeField: timeFieldName, granularity: 'seconds', bucketMaxSpanSeconds: 3600}
+ },
+ info: {readOnly: false},
+ }]);
+})();
diff --git a/jstests/core/timeseries/timeseries_list_collections_invalid_view.js b/jstests/core/timeseries/timeseries_list_collections_invalid_view.js
new file mode 100644
index 00000000000..0ef1c0874ca
--- /dev/null
+++ b/jstests/core/timeseries/timeseries_list_collections_invalid_view.js
@@ -0,0 +1,51 @@
+/**
+ * Tests the behavior of listCollections in the presence of both a time-series collection and an
+ * invalid view definition.
+ *
+ * @tags: [
+ * assumes_against_mongod_not_mongos,
+ * assumes_no_implicit_collection_creation_after_drop,
+ * does_not_support_transactions,
+ * requires_fcv_49,
+ * requires_find_command,
+ * requires_getmore,
+ * ]
+ */
+(function() {
+'use strict';
+
+const testDB = db.getSiblingDB(jsTestName());
+assert.commandWorked(testDB.dropDatabase());
+
+const timeFieldName = 'time';
+const coll = testDB.getCollection('t');
+
+assert.commandWorked(
+ testDB.createCollection(coll.getName(), {timeseries: {timeField: timeFieldName}}));
+assert.commandWorked(testDB.adminCommand({
+ applyOps: [
+ {op: 'i', ns: testDB.getName() + '.system.views', o: {_id: 'invalid', pipeline: 'invalid'}}
+ ]
+}));
+
+assert.commandFailedWithCode(testDB.runCommand({listCollections: 1}),
+ ErrorCodes.InvalidViewDefinition);
+assert.commandFailedWithCode(testDB.runCommand({listCollections: 1, filter: {type: 'timeseries'}}),
+ ErrorCodes.InvalidViewDefinition);
+assert.commandFailedWithCode(
+ testDB.runCommand({listCollections: 1, filter: {name: coll.getName()}}),
+ ErrorCodes.InvalidViewDefinition);
+
+// TODO (SERVER-25493): Change filter to {type: 'collection'}.
+const collections =
+ assert
+ .commandWorked(testDB.runCommand(
+ {listCollections: 1, filter: {$or: [{type: 'collection'}, {type: {$exists: false}}]}}))
+ .cursor.firstBatch;
+jsTestLog('Checking listCollections result: ' + tojson(collections));
+assert.eq(collections.length, 2);
+assert(collections.find(entry => entry.name === 'system.views'));
+assert(collections.find(entry => entry.name === 'system.buckets.' + coll.getName()));
+
+assert(testDB.system.views.drop());
+})();
diff --git a/jstests/core/timeseries/timeseries_list_collections_missing_buckets_collection.js b/jstests/core/timeseries/timeseries_list_collections_missing_buckets_collection.js
new file mode 100644
index 00000000000..c0f779425f9
--- /dev/null
+++ b/jstests/core/timeseries/timeseries_list_collections_missing_buckets_collection.js
@@ -0,0 +1,39 @@
+/**
+ * Tests that listCollections shows the time-series view, but not the buckets collection, if the
+ * backing time-series buckets collection is missing.
+ *
+ * @tags: [
+ * assumes_no_implicit_collection_creation_after_drop,
+ * does_not_support_transactions,
+ * requires_fcv_49,
+ * requires_find_command,
+ * requires_getmore,
+ * ]
+ */
+(function() {
+'use strict';
+
+const testDB = db.getSiblingDB(jsTestName());
+assert.commandWorked(testDB.dropDatabase());
+
+const timeFieldName = 'time';
+const coll = testDB.getCollection('t');
+const bucketsColl = testDB.getCollection('system.buckets.' + coll.getName());
+
+assert.commandWorked(
+ testDB.createCollection(coll.getName(), {timeseries: {timeField: timeFieldName}}));
+assert(bucketsColl.drop());
+
+let collections = assert.commandWorked(testDB.runCommand({listCollections: 1})).cursor.firstBatch;
+jsTestLog('Checking listCollections result: ' + tojson(collections));
+assert.eq(collections.length, 2);
+assert(collections.find(entry => entry.name === 'system.views'));
+assert.docEq(collections.find(entry => entry.name === coll.getName()),
+ {name: coll.getName(), type: 'timeseries', options: {}, info: {readOnly: false}});
+
+collections =
+ assert.commandWorked(testDB.runCommand({listCollections: 1, filter: {name: coll.getName()}}))
+ .cursor.firstBatch;
+assert.eq(collections,
+ [{name: coll.getName(), type: 'timeseries', options: {}, info: {readOnly: false}}]);
+})();
diff --git a/jstests/core/timeseries/timeseries_list_collections_missing_view.js b/jstests/core/timeseries/timeseries_list_collections_missing_view.js
new file mode 100644
index 00000000000..44f74ad31ef
--- /dev/null
+++ b/jstests/core/timeseries/timeseries_list_collections_missing_view.js
@@ -0,0 +1,30 @@
+/**
+ * Tests that listCollections shows the time-series buckets collection, but not the view, if the
+ * time-series view is missing.
+ *
+ * @tags: [
+ * assumes_no_implicit_collection_creation_after_drop,
+ * does_not_support_transactions,
+ * requires_fcv_49,
+ * requires_find_command,
+ * requires_getmore,
+ * ]
+ */
+(function() {
+'use strict';
+
+const testDB = db.getSiblingDB(jsTestName());
+assert.commandWorked(testDB.dropDatabase());
+
+const timeFieldName = 'time';
+const coll = testDB.getCollection('t');
+
+assert.commandWorked(
+ testDB.createCollection(coll.getName(), {timeseries: {timeField: timeFieldName}}));
+assert(testDB.system.views.drop());
+
+const collections = assert.commandWorked(testDB.runCommand({listCollections: 1})).cursor.firstBatch;
+jsTestLog('Checking listCollections result: ' + tojson(collections));
+assert.eq(collections.length, 1);
+assert(collections.find(entry => entry.name === 'system.buckets.' + coll.getName()));
+})();
diff --git a/jstests/hooks/run_validate_collections_background.js b/jstests/hooks/run_validate_collections_background.js
index 31c46298768..fc5b928d8aa 100644
--- a/jstests/hooks/run_validate_collections_background.js
+++ b/jstests/hooks/run_validate_collections_background.js
@@ -94,6 +94,7 @@ const validateCollectionsBackgroundThread = function validateCollectionsBackgrou
for (let dbName of dbNames) {
let db = conn.getDB(dbName);
+ // TODO (SERVER-25493): Change filter to {type: 'collection'}.
const listCollRes = assert.commandWorked(db.runCommand({
"listCollections": 1,
"nameOnly": true,
diff --git a/src/mongo/db/catalog/collection_options.cpp b/src/mongo/db/catalog/collection_options.cpp
index b5f1d32afed..2eadc1b11f8 100644
--- a/src/mongo/db/catalog/collection_options.cpp
+++ b/src/mongo/db/catalog/collection_options.cpp
@@ -316,81 +316,90 @@ CollectionOptions CollectionOptions::fromCreateCommand(const CreateCommand& cmd)
return options;
}
-BSONObj CollectionOptions::toBSON(bool includeUUID) const {
+BSONObj CollectionOptions::toBSON(bool includeUUID, const StringDataSet& includeFields) const {
BSONObjBuilder b;
- appendBSON(&b, includeUUID);
+ appendBSON(&b, includeUUID, includeFields);
return b.obj();
}
-void CollectionOptions::appendBSON(BSONObjBuilder* builder, bool includeUUID) const {
+void CollectionOptions::appendBSON(BSONObjBuilder* builder,
+ bool includeUUID,
+ const StringDataSet& includeFields) const {
if (uuid && includeUUID) {
builder->appendElements(uuid->toBSON());
}
- if (capped) {
- builder->appendBool("capped", true);
- builder->appendNumber("size", cappedSize);
+ auto shouldAppend = [&](StringData option) {
+ return includeFields.empty() || includeFields.contains(option);
+ };
+
+ if (capped && shouldAppend(CreateCommand::kCappedFieldName)) {
+ builder->appendBool(CreateCommand::kCappedFieldName, true);
+ builder->appendNumber(CreateCommand::kSizeFieldName, cappedSize);
if (cappedMaxDocs)
- builder->appendNumber("max", cappedMaxDocs);
+ builder->appendNumber(CreateCommand::kMaxFieldName, cappedMaxDocs);
}
- if (autoIndexId != DEFAULT)
- builder->appendBool("autoIndexId", autoIndexId == YES);
+ if (autoIndexId != DEFAULT && shouldAppend(CreateCommand::kAutoIndexIdFieldName))
+ builder->appendBool(CreateCommand::kAutoIndexIdFieldName, autoIndexId == YES);
- if (temp)
- builder->appendBool("temp", true);
+ if (temp && shouldAppend(CreateCommand::kTempFieldName))
+ builder->appendBool(CreateCommand::kTempFieldName, true);
- if (recordPreImages) {
- builder->appendBool("recordPreImages", true);
+ if (recordPreImages && shouldAppend(CreateCommand::kRecordPreImagesFieldName)) {
+ builder->appendBool(CreateCommand::kRecordPreImagesFieldName, true);
}
- if (!storageEngine.isEmpty()) {
- builder->append("storageEngine", storageEngine);
+ if (!storageEngine.isEmpty() && shouldAppend(CreateCommand::kStorageEngineFieldName)) {
+ builder->append(CreateCommand::kStorageEngineFieldName, storageEngine);
}
- if (indexOptionDefaults.getStorageEngine()) {
- builder->append("indexOptionDefaults", indexOptionDefaults.toBSON());
+ if (indexOptionDefaults.getStorageEngine() &&
+ shouldAppend(CreateCommand::kIndexOptionDefaultsFieldName)) {
+ builder->append(CreateCommand::kIndexOptionDefaultsFieldName, indexOptionDefaults.toBSON());
}
- if (!validator.isEmpty()) {
- builder->append("validator", validator);
+ if (!validator.isEmpty() && shouldAppend(CreateCommand::kValidatorFieldName)) {
+ builder->append(CreateCommand::kValidatorFieldName, validator);
}
- if (validationLevel) {
- builder->append("validationLevel", ValidationLevel_serializer(*validationLevel));
+ if (validationLevel && shouldAppend(CreateCommand::kValidationLevelFieldName)) {
+ builder->append(CreateCommand::kValidationLevelFieldName,
+ ValidationLevel_serializer(*validationLevel));
}
- if (validationAction) {
- builder->append("validationAction", ValidationAction_serializer(*validationAction));
+ if (validationAction && shouldAppend(CreateCommand::kValidationActionFieldName)) {
+ builder->append(CreateCommand::kValidationActionFieldName,
+ ValidationAction_serializer(*validationAction));
}
- if (!collation.isEmpty()) {
- builder->append("collation", collation);
+ if (!collation.isEmpty() && shouldAppend(CreateCommand::kCollationFieldName)) {
+ builder->append(CreateCommand::kCollationFieldName, collation);
}
- if (clusteredIndex) {
- builder->append("clusteredIndex", true);
+ if (clusteredIndex && shouldAppend(CreateCommand::kClusteredIndexFieldName)) {
+ builder->append(CreateCommand::kClusteredIndexFieldName, true);
}
- if (expireAfterSeconds) {
- builder->append("expireAfterSeconds", *expireAfterSeconds);
+ if (expireAfterSeconds && shouldAppend(CreateCommand::kExpireAfterSecondsFieldName)) {
+ builder->append(CreateCommand::kExpireAfterSecondsFieldName, *expireAfterSeconds);
}
- if (!viewOn.empty()) {
- builder->append("viewOn", viewOn);
+ if (!viewOn.empty() && shouldAppend(CreateCommand::kViewOnFieldName)) {
+ builder->append(CreateCommand::kViewOnFieldName, viewOn);
}
- if (!pipeline.isEmpty()) {
- builder->appendArray("pipeline", pipeline);
+ if (!pipeline.isEmpty() && shouldAppend(CreateCommand::kPipelineFieldName)) {
+ builder->appendArray(CreateCommand::kPipelineFieldName, pipeline);
}
- if (!idIndex.isEmpty()) {
- builder->append("idIndex", idIndex);
+ if (!idIndex.isEmpty() && shouldAppend(CreateCommand::kIdIndexFieldName)) {
+ builder->append(CreateCommand::kIdIndexFieldName, idIndex);
}
- if (timeseries) {
- builder->append("timeseries", timeseries->toBSON());
+ if (timeseries && shouldAppend(CreateCommand::kTimeseriesFieldName)) {
+ builder->append(CreateCommand::kTimeseriesFieldName, timeseries->toBSON());
}
}
diff --git a/src/mongo/db/catalog/collection_options.h b/src/mongo/db/catalog/collection_options.h
index 16be9667804..7a901f79c28 100644
--- a/src/mongo/db/catalog/collection_options.h
+++ b/src/mongo/db/catalog/collection_options.h
@@ -92,10 +92,13 @@ struct CollectionOptions {
/**
* Serialize to BSON. The 'includeUUID' parameter is used for the listCollections command to do
- * special formatting for the uuid.
+ * special formatting for the uuid. Aside from the UUID, if 'includeFields' is non-empty, only
+ * the specified fields will be included.
*/
- void appendBSON(BSONObjBuilder* builder, bool includeUUID) const;
- BSONObj toBSON(bool includeUUID = true) const;
+ void appendBSON(BSONObjBuilder* builder,
+ bool includeUUID,
+ const StringDataSet& includeFields) const;
+ BSONObj toBSON(bool includeUUID = true, const StringDataSet& includeFields = {}) const;
/**
* Returns true if given options matches to this.
diff --git a/src/mongo/db/commands/create_command.cpp b/src/mongo/db/commands/create_command.cpp
index 40442e764dc..d94c77f5268 100644
--- a/src/mongo/db/commands/create_command.cpp
+++ b/src/mongo/db/commands/create_command.cpp
@@ -39,6 +39,7 @@
#include "mongo/db/query/collation/collator_factory_interface.h"
#include "mongo/db/s/operation_sharding_state.h"
#include "mongo/db/storage/storage_parameters_gen.h"
+#include "mongo/db/timeseries/timeseries_constants.h"
#include "mongo/logv2/log.h"
namespace mongo {
@@ -148,47 +149,33 @@ public:
feature_flags::gTimeseriesCollection.isEnabled(
serverGlobalParams.featureCompatibility));
- const auto timeseriesNotAllowedWith = [&cmd](StringData option) -> std::string {
- return str::stream() << cmd.getNamespace()
- << ": 'timeseries' is not allowed with '" << option << "'";
- };
+ for (auto&& option : cmd.toBSON({})) {
+ auto fieldName = option.fieldNameStringData();
+
+ if (fieldName == CreateCommand::kCommandName) {
+ continue;
+ }
+
+ // The 'capped' option defaults to false. We accept it unless it is set to true.
+ if (fieldName == CreateCommand::kCappedFieldName && !option.Bool()) {
+ continue;
+ }
+
+ // 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 ((fieldName == CreateCommand::kValidatorFieldName ||
+ fieldName == CreateCommand::kClusteredIndexFieldName) &&
+ cmd.getNamespace().isTimeseriesBucketsCollection()) {
+ continue;
+ }
- uassert(ErrorCodes::InvalidOptions,
- timeseriesNotAllowedWith("capped"),
- !cmd.getCapped());
- uassert(ErrorCodes::InvalidOptions,
- timeseriesNotAllowedWith("autoIndexId"),
- !cmd.getAutoIndexId());
- uassert(ErrorCodes::InvalidOptions,
- timeseriesNotAllowedWith("idIndex"),
- !cmd.getIdIndex());
- uassert(
- ErrorCodes::InvalidOptions, timeseriesNotAllowedWith("size"), !cmd.getSize());
- uassert(ErrorCodes::InvalidOptions, timeseriesNotAllowedWith("max"), !cmd.getMax());
-
- // 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());
+ str::stream()
+ << cmd.getNamespace() << ": 'timeseries' is not allowed with '"
+ << fieldName << "'",
+ timeseries::kAllowedCollectionCreationOptions.contains(fieldName));
}
- uassert(ErrorCodes::InvalidOptions,
- timeseriesNotAllowedWith("validationLevel"),
- !cmd.getValidationLevel());
- uassert(ErrorCodes::InvalidOptions,
- timeseriesNotAllowedWith("validationAction"),
- !cmd.getValidationAction());
- uassert(ErrorCodes::InvalidOptions,
- timeseriesNotAllowedWith("viewOn"),
- !cmd.getViewOn());
- uassert(ErrorCodes::InvalidOptions,
- timeseriesNotAllowedWith("pipeline"),
- !cmd.getPipeline());
auto hasDot = [](StringData field) -> bool {
return field.find('.') != std::string::npos;
diff --git a/src/mongo/db/commands/list_collections.cpp b/src/mongo/db/commands/list_collections.cpp
index 03c012f06fa..dd3c3192776 100644
--- a/src/mongo/db/commands/list_collections.cpp
+++ b/src/mongo/db/commands/list_collections.cpp
@@ -63,6 +63,7 @@
#include "mongo/db/service_context.h"
#include "mongo/db/storage/storage_engine.h"
#include "mongo/db/storage/storage_options.h"
+#include "mongo/db/timeseries/timeseries_constants.h"
#include "mongo/db/views/view_catalog.h"
#include "mongo/logv2/log.h"
@@ -142,9 +143,11 @@ void _addWorkingSetMember(OperationContext* opCtx,
}
BSONObj buildViewBson(const ViewDefinition& view, bool nameOnly) {
+ invariant(!view.timeseries());
+
BSONObjBuilder b;
b.append("name", view.name().coll());
- b.append("type", view.timeseries() ? "timeseries" : "view");
+ b.append("type", "view");
if (nameOnly) {
return b.obj();
@@ -158,11 +161,47 @@ BSONObj buildViewBson(const ViewDefinition& view, bool nameOnly) {
}
optionsBuilder.doneFast();
- BSONObj info = BSON("readOnly" << !view.timeseries());
+ BSONObj info = BSON("readOnly" << true);
b.append("info", info);
return b.obj();
}
+BSONObj buildTimeseriesBson(OperationContext* opCtx,
+ const CollectionPtr& collection,
+ bool nameOnly) {
+ invariant(collection);
+
+ BSONObjBuilder builder;
+ builder.append("name", collection->ns().getTimeseriesViewNamespace().coll());
+ builder.append("type", "timeseries");
+
+ if (nameOnly) {
+ return builder.obj();
+ }
+
+ builder.append("options",
+ collection->getCollectionOptions().toBSON(
+ false /* includeUUID */, timeseries::kAllowedCollectionCreationOptions));
+ builder.append("info", BSON("readOnly" << false));
+
+ return builder.obj();
+}
+
+BSONObj buildTimeseriesBson(StringData collName, bool nameOnly) {
+ BSONObjBuilder builder;
+ builder.append("name", collName);
+ builder.append("type", "timeseries");
+
+ if (nameOnly) {
+ return builder.obj();
+ }
+
+ builder.append("options", BSONObj{});
+ builder.append("info", BSON("readOnly" << false));
+
+ return builder.obj();
+}
+
/**
* Return an object describing the collection. Takes a collection lock if nameOnly is false.
*/
@@ -323,11 +362,32 @@ public:
clk.emplace(opCtx, nss, MODE_IS);
}
- CollectionPtr collection =
- CollectionCatalog::get(opCtx)->lookupCollectionByNamespace(opCtx,
- nss);
- BSONObj collBson = buildCollectionBson(
- opCtx, collection, includePendingDrops, nameOnly);
+ auto collBson = [&] {
+ if (auto collection =
+ CollectionCatalog::get(opCtx)->lookupCollectionByNamespace(
+ opCtx, nss)) {
+ return buildCollectionBson(
+ opCtx, collection, includePendingDrops, nameOnly);
+ }
+
+ auto view = viewCatalog->lookupWithoutValidatingDurableViews(
+ opCtx, nss.ns());
+ if (view && view->timeseries()) {
+ if (auto bucketsCollection = CollectionCatalog::get(opCtx)
+ ->lookupCollectionByNamespace(
+ opCtx, view->viewOn())) {
+ return buildTimeseriesBson(
+ opCtx, bucketsCollection, nameOnly);
+ } else {
+ // The buckets collection does not exist, so the time-series
+ // view will be appended when we iterate through the view
+ // catalog below.
+ }
+ }
+
+ return BSONObj{};
+ }();
+
if (!collBson.isEmpty()) {
_addWorkingSetMember(
opCtx, collBson, matcher.get(), ws.get(), root.get());
@@ -340,12 +400,27 @@ public:
ResourcePattern::forExactNamespace(collection->ns())))) {
return true;
}
+
BSONObj collBson = buildCollectionBson(
opCtx, collection, includePendingDrops, nameOnly);
if (!collBson.isEmpty()) {
_addWorkingSetMember(
opCtx, collBson, matcher.get(), ws.get(), root.get());
}
+
+ if (collection && collection->getTimeseriesOptions() &&
+ viewCatalog->lookupWithoutValidatingDurableViews(
+ opCtx, collection->ns().getTimeseriesViewNamespace().ns())) {
+ // The time-series view for this buckets namespace exists, so add it
+ // here while we have the collection options.
+ _addWorkingSetMember(
+ opCtx,
+ buildTimeseriesBson(opCtx, collection, nameOnly),
+ matcher.get(),
+ ws.get(),
+ root.get());
+ }
+
return true;
};
@@ -379,6 +454,23 @@ public:
return true;
}
+ if (view.timeseries()) {
+ if (!CollectionCatalog::get(opCtx)
+ ->lookupCollectionByNamespaceForRead(opCtx,
+ view.viewOn())) {
+ // There is no buckets collection backing this time-series view,
+ // which means that it was not already added along with the
+ // buckets collection above.
+ _addWorkingSetMember(
+ opCtx,
+ buildTimeseriesBson(view.name().coll(), nameOnly),
+ matcher.get(),
+ ws.get(),
+ root.get());
+ }
+ return true;
+ }
+
BSONObj viewBson = buildViewBson(view, nameOnly);
if (!viewBson.isEmpty()) {
_addWorkingSetMember(
diff --git a/src/mongo/db/exec/bucket_unpacker.cpp b/src/mongo/db/exec/bucket_unpacker.cpp
index 1e884dc1714..829e490213b 100644
--- a/src/mongo/db/exec/bucket_unpacker.cpp
+++ b/src/mongo/db/exec/bucket_unpacker.cpp
@@ -30,7 +30,7 @@
#include "mongo/platform/basic.h"
#include "mongo/db/exec/bucket_unpacker.h"
-#include "mongo/db/timeseries/timeseries_field_names.h"
+#include "mongo/db/timeseries/timeseries_constants.h"
namespace mongo {
diff --git a/src/mongo/db/exec/sample_from_timeseries_bucket.cpp b/src/mongo/db/exec/sample_from_timeseries_bucket.cpp
index 2d0fe7e72aa..06bdba2e77a 100644
--- a/src/mongo/db/exec/sample_from_timeseries_bucket.cpp
+++ b/src/mongo/db/exec/sample_from_timeseries_bucket.cpp
@@ -28,7 +28,7 @@
*/
#include "mongo/db/exec/sample_from_timeseries_bucket.h"
-#include "mongo/db/timeseries/timeseries_field_names.h"
+#include "mongo/db/timeseries/timeseries_constants.h"
namespace mongo {
const char* SampleFromTimeseriesBucket::kStageType = "SAMPLE_FROM_TIMESERIES_BUCKET";
diff --git a/src/mongo/db/pipeline/document_source_internal_convert_bucket_index_stats.cpp b/src/mongo/db/pipeline/document_source_internal_convert_bucket_index_stats.cpp
index 216472accc4..3e523696eb8 100644
--- a/src/mongo/db/pipeline/document_source_internal_convert_bucket_index_stats.cpp
+++ b/src/mongo/db/pipeline/document_source_internal_convert_bucket_index_stats.cpp
@@ -36,7 +36,7 @@
#include "mongo/db/list_indexes_gen.h"
#include "mongo/db/pipeline/expression_context.h"
#include "mongo/db/pipeline/lite_parsed_document_source.h"
-#include "mongo/db/timeseries/timeseries_field_names.h"
+#include "mongo/db/timeseries/timeseries_constants.h"
#include "mongo/db/timeseries/timeseries_gen.h"
#include "mongo/db/timeseries/timeseries_index_schema_conversion_functions.h"
diff --git a/src/mongo/db/pipeline/document_source_internal_unpack_bucket.cpp b/src/mongo/db/pipeline/document_source_internal_unpack_bucket.cpp
index 0b63d0aadb5..2fa8291fee2 100644
--- a/src/mongo/db/pipeline/document_source_internal_unpack_bucket.cpp
+++ b/src/mongo/db/pipeline/document_source_internal_unpack_bucket.cpp
@@ -52,7 +52,7 @@
#include "mongo/db/pipeline/expression_context.h"
#include "mongo/db/pipeline/lite_parsed_document_source.h"
#include "mongo/db/query/util/make_data_structure.h"
-#include "mongo/db/timeseries/timeseries_field_names.h"
+#include "mongo/db/timeseries/timeseries_constants.h"
#include "mongo/logv2/log.h"
#include "mongo/util/duration.h"
#include "mongo/util/time_support.h"
diff --git a/src/mongo/db/pipeline/document_source_internal_unpack_bucket_test/unpack_bucket_exec_test.cpp b/src/mongo/db/pipeline/document_source_internal_unpack_bucket_test/unpack_bucket_exec_test.cpp
index 83f2fa6e894..35667959188 100644
--- a/src/mongo/db/pipeline/document_source_internal_unpack_bucket_test/unpack_bucket_exec_test.cpp
+++ b/src/mongo/db/pipeline/document_source_internal_unpack_bucket_test/unpack_bucket_exec_test.cpp
@@ -31,7 +31,7 @@
#include "mongo/db/pipeline/aggregation_context_fixture.h"
#include "mongo/db/pipeline/document_source_internal_unpack_bucket.h"
#include "mongo/db/pipeline/document_source_mock.h"
-#include "mongo/db/timeseries/timeseries_field_names.h"
+#include "mongo/db/timeseries/timeseries_constants.h"
namespace mongo {
namespace {
diff --git a/src/mongo/db/timeseries/timeseries_field_names.h b/src/mongo/db/timeseries/timeseries_constants.h
index b6d24a78bd8..e9a6ebf80ba 100644
--- a/src/mongo/db/timeseries/timeseries_field_names.h
+++ b/src/mongo/db/timeseries/timeseries_constants.h
@@ -30,6 +30,8 @@
#pragma once
#include "mongo/base/string_data.h"
+#include "mongo/db/commands/create_gen.h"
+#include "mongo/util/string_map.h"
namespace mongo {
namespace timeseries {
@@ -49,5 +51,12 @@ static constexpr StringData kMetaFieldName = "metaField"_sd;
// These are hard-coded field names in index specs.
static constexpr StringData kKeyFieldName = "key"_sd;
+static const StringDataSet kAllowedCollectionCreationOptions{
+ CreateCommand::kStorageEngineFieldName,
+ CreateCommand::kIndexOptionDefaultsFieldName,
+ CreateCommand::kCollationFieldName,
+ CreateCommand::kTimeseriesFieldName,
+ CreateCommand::kExpireAfterSecondsFieldName};
+
} // namespace timeseries
} // namespace mongo
diff --git a/src/mongo/db/timeseries/timeseries_index_schema_conversion_functions.cpp b/src/mongo/db/timeseries/timeseries_index_schema_conversion_functions.cpp
index 1aa910b73e9..5909670ff08 100644
--- a/src/mongo/db/timeseries/timeseries_index_schema_conversion_functions.cpp
+++ b/src/mongo/db/timeseries/timeseries_index_schema_conversion_functions.cpp
@@ -33,7 +33,7 @@
#include "mongo/db/timeseries/timeseries_index_schema_conversion_functions.h"
-#include "mongo/db/timeseries/timeseries_field_names.h"
+#include "mongo/db/timeseries/timeseries_constants.h"
#include "mongo/db/timeseries/timeseries_gen.h"
#include "mongo/logv2/log.h"
#include "mongo/logv2/redaction.h"
diff --git a/src/mongo/db/timeseries/timeseries_index_schema_conversion_functions_test.cpp b/src/mongo/db/timeseries/timeseries_index_schema_conversion_functions_test.cpp
index 9eac6a67236..2763b5a3385 100644
--- a/src/mongo/db/timeseries/timeseries_index_schema_conversion_functions_test.cpp
+++ b/src/mongo/db/timeseries/timeseries_index_schema_conversion_functions_test.cpp
@@ -32,7 +32,7 @@
#include "mongo/db/timeseries/timeseries_index_schema_conversion_functions.h"
#include "mongo/bson/bsonobj.h"
-#include "mongo/db/timeseries/timeseries_field_names.h"
+#include "mongo/db/timeseries/timeseries_constants.h"
#include "mongo/db/timeseries/timeseries_gen.h"
#include "mongo/unittest/unittest.h"
diff --git a/src/mongo/db/views/resolved_view.cpp b/src/mongo/db/views/resolved_view.cpp
index 2c1e9900fa6..9c754c559e7 100644
--- a/src/mongo/db/views/resolved_view.cpp
+++ b/src/mongo/db/views/resolved_view.cpp
@@ -36,7 +36,7 @@
#include "mongo/db/pipeline/document_source_index_stats.h"
#include "mongo/db/pipeline/document_source_internal_convert_bucket_index_stats.h"
#include "mongo/db/pipeline/document_source_internal_unpack_bucket.h"
-#include "mongo/db/timeseries/timeseries_field_names.h"
+#include "mongo/db/timeseries/timeseries_constants.h"
#include "mongo/rpc/get_status_from_command_result.h"
namespace mongo {