summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
authorDianna Hohensee <dianna.hohensee@mongodb.com>2021-03-08 12:46:37 -0500
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2021-03-12 02:55:04 +0000
commit85b600eec7f060e1a2f52dcdc8e4cde06508415a (patch)
treed81d3d2496ec99fc2a7adf0b3eb394876b63cd4c /src/mongo
parent0757d14ea960b2a30f2b84423551422e5980e41e (diff)
downloadmongo-85b600eec7f060e1a2f52dcdc8e4cde06508415a.tar.gz
SERVER-54978 Unit test helper functions that convert index specs between underlying buckets
collection and time-series collection schema
Diffstat (limited to 'src/mongo')
-rw-r--r--src/mongo/db/pipeline/document_source_internal_unpack_bucket.cpp18
-rw-r--r--src/mongo/db/pipeline/document_source_internal_unpack_bucket.h4
-rw-r--r--src/mongo/db/timeseries/SConscript24
-rw-r--r--src/mongo/db/timeseries/timeseries_index_schema_conversion_functions.cpp26
-rw-r--r--src/mongo/db/timeseries/timeseries_index_schema_conversion_functions_test.cpp259
5 files changed, 306 insertions, 25 deletions
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 c0c59c42500..c9a77ab25fc 100644
--- a/src/mongo/db/pipeline/document_source_internal_unpack_bucket.cpp
+++ b/src/mongo/db/pipeline/document_source_internal_unpack_bucket.cpp
@@ -478,10 +478,12 @@ std::unique_ptr<MatchExpression> createComparisonPredicate(
auto andMatchExpr = std::make_unique<AndMatchExpression>();
andMatchExpr->add(std::make_unique<InternalExprLTEMatchExpression>(
- str::stream() << DocumentSourceInternalUnpackBucket::kControlMinFieldName << path,
+ str::stream() << DocumentSourceInternalUnpackBucket::kControlMinFieldNamePrefix
+ << path,
rhs));
andMatchExpr->add(std::make_unique<InternalExprGTEMatchExpression>(
- str::stream() << DocumentSourceInternalUnpackBucket::kControlMaxFieldName << path,
+ str::stream() << DocumentSourceInternalUnpackBucket::kControlMaxFieldNamePrefix
+ << path,
rhs));
if (path == bucketSpec.timeField) {
@@ -492,17 +494,20 @@ std::unique_ptr<MatchExpression> createComparisonPredicate(
}
case MatchExpression::GT: {
return std::make_unique<InternalExprGTMatchExpression>(
- str::stream() << DocumentSourceInternalUnpackBucket::kControlMaxFieldName << path,
+ str::stream() << DocumentSourceInternalUnpackBucket::kControlMaxFieldNamePrefix
+ << path,
rhs);
}
case MatchExpression::GTE: {
return std::make_unique<InternalExprGTEMatchExpression>(
- str::stream() << DocumentSourceInternalUnpackBucket::kControlMaxFieldName << path,
+ str::stream() << DocumentSourceInternalUnpackBucket::kControlMaxFieldNamePrefix
+ << path,
rhs);
}
case MatchExpression::LT: {
auto controlPred = std::make_unique<InternalExprLTMatchExpression>(
- str::stream() << DocumentSourceInternalUnpackBucket::kControlMinFieldName << path,
+ str::stream() << DocumentSourceInternalUnpackBucket::kControlMinFieldNamePrefix
+ << path,
rhs);
if (path == bucketSpec.timeField) {
auto andMatchExpr = std::make_unique<AndMatchExpression>();
@@ -517,7 +522,8 @@ std::unique_ptr<MatchExpression> createComparisonPredicate(
}
case MatchExpression::LTE: {
auto controlPred = std::make_unique<InternalExprLTEMatchExpression>(
- str::stream() << DocumentSourceInternalUnpackBucket::kControlMinFieldName << path,
+ str::stream() << DocumentSourceInternalUnpackBucket::kControlMinFieldNamePrefix
+ << path,
rhs);
if (path == bucketSpec.timeField) {
auto andMatchExpr = std::make_unique<AndMatchExpression>();
diff --git a/src/mongo/db/pipeline/document_source_internal_unpack_bucket.h b/src/mongo/db/pipeline/document_source_internal_unpack_bucket.h
index 69988aa5549..50a1513cc51 100644
--- a/src/mongo/db/pipeline/document_source_internal_unpack_bucket.h
+++ b/src/mongo/db/pipeline/document_source_internal_unpack_bucket.h
@@ -140,8 +140,8 @@ public:
static constexpr StringData kExclude = "exclude"_sd;
static constexpr StringData kTimeFieldName = "timeField"_sd;
static constexpr StringData kMetaFieldName = "metaField"_sd;
- static constexpr StringData kControlMaxFieldName = "control.max."_sd;
- static constexpr StringData kControlMinFieldName = "control.min."_sd;
+ static constexpr StringData kControlMaxFieldNamePrefix = "control.max."_sd;
+ static constexpr StringData kControlMinFieldNamePrefix = "control.min."_sd;
static boost::intrusive_ptr<DocumentSource> createFromBson(
BSONElement elem, const boost::intrusive_ptr<ExpressionContext>& expCtx);
diff --git a/src/mongo/db/timeseries/SConscript b/src/mongo/db/timeseries/SConscript
index 7173d429cd6..f29429a001e 100644
--- a/src/mongo/db/timeseries/SConscript
+++ b/src/mongo/db/timeseries/SConscript
@@ -28,17 +28,6 @@ env.Library(
],
)
-env.CppUnitTest(
- target='bucket_catalog_test',
- source=[
- 'bucket_catalog_test.cpp',
- ],
- LIBDEPS=[
- '$BUILD_DIR/mongo/db/catalog/catalog_test_fixture',
- 'bucket_catalog',
- ],
-)
-
env.Library(
target='timeseries_index_schema_conversion_functions',
source=[
@@ -54,3 +43,16 @@ env.Library(
'$BUILD_DIR/mongo/db/views/views',
],
)
+
+env.CppUnitTest(
+ target='db_timeseries_test',
+ source=[
+ 'bucket_catalog_test.cpp',
+ 'timeseries_index_schema_conversion_functions_test.cpp',
+ ],
+ LIBDEPS=[
+ '$BUILD_DIR/mongo/db/catalog/catalog_test_fixture',
+ 'bucket_catalog',
+ 'timeseries_index_schema_conversion_functions',
+ ],
+)
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 57841a7b83f..891806b1661 100644
--- a/src/mongo/db/timeseries/timeseries_index_schema_conversion_functions.cpp
+++ b/src/mongo/db/timeseries/timeseries_index_schema_conversion_functions.cpp
@@ -81,11 +81,23 @@ StatusWith<BSONObj> convertTimeseriesIndexSpecToBucketsIndexSpec(
// The time-series index on the 'timeField' is converted into a compound time index on
// the buckets collection for more efficient querying of buckets.
if (elem.number() >= 0) {
- builder.appendAs(elem, str::stream() << "control.min." << timeField);
- builder.appendAs(elem, str::stream() << "control.max." << timeField);
+ builder.appendAs(
+ elem,
+ str::stream() << DocumentSourceInternalUnpackBucket::kControlMinFieldNamePrefix
+ << timeField);
+ builder.appendAs(
+ elem,
+ str::stream() << DocumentSourceInternalUnpackBucket::kControlMaxFieldNamePrefix
+ << timeField);
} else {
- builder.appendAs(elem, str::stream() << "control.max." << timeField);
- builder.appendAs(elem, str::stream() << "control.min." << timeField);
+ builder.appendAs(
+ elem,
+ str::stream() << DocumentSourceInternalUnpackBucket::kControlMaxFieldNamePrefix
+ << timeField);
+ builder.appendAs(
+ elem,
+ str::stream() << DocumentSourceInternalUnpackBucket::kControlMinFieldNamePrefix
+ << timeField);
}
continue;
}
@@ -128,8 +140,10 @@ BSONObj convertBucketsIndexSpecToTimeseriesIndexSpec(const TimeseriesOptions& ti
auto timeField = timeseriesOptions.getTimeField();
auto metaField = timeseriesOptions.getMetaField();
- const std::string controlMinTimeField = str::stream() << "control.min." << timeField;
- const std::string controlMaxTimeField = str::stream() << "control.max." << timeField;
+ const std::string controlMinTimeField = str::stream()
+ << DocumentSourceInternalUnpackBucket::kControlMinFieldNamePrefix << timeField;
+ const std::string controlMaxTimeField = str::stream()
+ << DocumentSourceInternalUnpackBucket::kControlMaxFieldNamePrefix << timeField;
BSONObjBuilder builder;
for (const auto& elem : bucketsIndexSpecBSON) {
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
new file mode 100644
index 00000000000..24c83796b11
--- /dev/null
+++ b/src/mongo/db/timeseries/timeseries_index_schema_conversion_functions_test.cpp
@@ -0,0 +1,259 @@
+/**
+ * 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/db/timeseries/timeseries_index_schema_conversion_functions.h"
+
+#include "mongo/bson/bsonobj.h"
+#include "mongo/db/pipeline/document_source_internal_unpack_bucket.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo {
+namespace {
+
+const std::string kTimeseriesTimeFieldName("tm");
+const std::string kTimeseriesMetaFieldName("mm");
+const std::string kSubField1Name(".subfield1");
+const std::string kSubField2Name(".subfield2");
+const std::string kControlMinTimeFieldName(
+ DocumentSourceInternalUnpackBucket::kControlMinFieldNamePrefix + kTimeseriesTimeFieldName);
+const std::string kControlMaxTimeFieldName(
+ DocumentSourceInternalUnpackBucket::kControlMaxFieldNamePrefix + kTimeseriesTimeFieldName);
+const std::string kTimeseriesSomeDataFieldName("somedatafield");
+const std::string kBucketsSomeDataFieldName(BucketUnpacker::kBucketDataFieldName + "." +
+ kTimeseriesSomeDataFieldName);
+
+/**
+ * Constructs a TimeseriesOptions object for testing.
+ */
+TimeseriesOptions makeTimeseriesOptions() {
+ TimeseriesOptions options(kTimeseriesTimeFieldName);
+ options.setMetaField(StringData(kTimeseriesMetaFieldName));
+
+ return options;
+}
+
+/**
+ * Uses 'timeseriesOptions' to convert 'timeseriesIndexSpec' to 'bucketsIndexSpec' and vice versa.
+ * If 'testShouldSucceed' is false, pivots to testing that conversion attempts fail.
+ */
+void testBothWaysIndexSpecConversion(const TimeseriesOptions& timeseriesOptions,
+ const BSONObj& timeseriesIndexSpec,
+ const BSONObj& bucketsIndexSpec,
+ bool testShouldSucceed = true) {
+ // Test time-series => buckets schema conversion.
+
+ auto swBucketsIndexSpecs = timeseries::convertTimeseriesIndexSpecToBucketsIndexSpec(
+ timeseriesOptions, timeseriesIndexSpec);
+
+ if (testShouldSucceed) {
+ ASSERT_OK(swBucketsIndexSpecs);
+ ASSERT_BSONOBJ_EQ(bucketsIndexSpec, swBucketsIndexSpecs.getValue());
+ } else {
+ ASSERT_NOT_OK(swBucketsIndexSpecs);
+ }
+
+ // Test buckets => time-series schema conversion.
+
+ auto timeseriesIndexSpecResult = timeseries::convertBucketsIndexSpecToTimeseriesIndexSpec(
+ timeseriesOptions, bucketsIndexSpec);
+
+ if (testShouldSucceed) {
+ ASSERT_BSONOBJ_EQ(timeseriesIndexSpec, timeseriesIndexSpecResult);
+ } else {
+ // A buckets collection index spec that does not conform to the supported time-series index
+ // spec schema should be converted to an empty time-series index spec result.
+ ASSERT(timeseriesIndexSpecResult.isEmpty());
+ }
+}
+
+// {} <=> {}
+TEST(TimeseriesIndexSchemaConversionTest, EmptyTimeseriesIndexSpecDoesNothing) {
+ TimeseriesOptions timeseriesOptions = makeTimeseriesOptions();
+ BSONObj emptyIndexSpec = {};
+
+ testBothWaysIndexSpecConversion(timeseriesOptions, emptyIndexSpec, emptyIndexSpec);
+}
+
+// {tm: 1} <=> {control.min.tm: 1, control.max.tm: 1}
+TEST(TimeseriesIndexSchemaConversionTest, AscendingTimeIndexSpecConversion) {
+ TimeseriesOptions timeseriesOptions = makeTimeseriesOptions();
+ BSONObj timeseriesIndexSpec = BSON(kTimeseriesTimeFieldName << 1);
+ BSONObj bucketsIndexSpec = BSON(kControlMinTimeFieldName << 1 << kControlMaxTimeFieldName << 1);
+
+ testBothWaysIndexSpecConversion(timeseriesOptions, timeseriesIndexSpec, bucketsIndexSpec);
+}
+
+// {tm: -1} <=> {control.max.tm: -1, control.min.tm: -1}
+TEST(TimeseriesIndexSchemaConversionTest, DescendingTimeIndexSpecConversion) {
+ TimeseriesOptions timeseriesOptions = makeTimeseriesOptions();
+ BSONObj timeseriesIndexSpec = BSON(kTimeseriesTimeFieldName << -1);
+ BSONObj bucketsIndexSpec =
+ BSON(kControlMaxTimeFieldName << -1 << kControlMinTimeFieldName << -1);
+
+ testBothWaysIndexSpecConversion(timeseriesOptions, timeseriesIndexSpec, bucketsIndexSpec);
+}
+
+// {tm.subfield1: 1} <=> {tm.subfield1: 1}
+TEST(TimeseriesIndexSchemaConversionTest, TimeSubFieldIndexSpecConversionFails) {
+ TimeseriesOptions timeseriesOptions = makeTimeseriesOptions();
+ BSONObj timeseriesIndexSpec = BSON(kTimeseriesTimeFieldName + kSubField1Name << 1);
+ BSONObj bucketsIndexSpec = BSON(kTimeseriesTimeFieldName + kSubField1Name << 1);
+
+ testBothWaysIndexSpecConversion(
+ timeseriesOptions, timeseriesIndexSpec, bucketsIndexSpec, false /* testShouldSucceed */);
+}
+
+// {mm: 1} <=> {meta: 1}
+TEST(TimeseriesIndexSchemaConversionTest, MetadataIndexSpecConversion) {
+ TimeseriesOptions timeseriesOptions = makeTimeseriesOptions();
+ BSONObj timeseriesIndexSpec = BSON(kTimeseriesMetaFieldName << 1);
+ BSONObj bucketsIndexSpec = BSON(BucketUnpacker::kBucketMetaFieldName << 1);
+
+ testBothWaysIndexSpecConversion(timeseriesOptions, timeseriesIndexSpec, bucketsIndexSpec);
+}
+
+// {mm.subfield1: 1, mm.subfield2: 1} <=> {meta.subfield1: 1, mm.subfield2: 1}
+TEST(TimeseriesIndexSchemaConversionTest, MetadataCompoundIndexSpecConversion) {
+ TimeseriesOptions timeseriesOptions = makeTimeseriesOptions();
+ BSONObj timeseriesIndexSpec = BSON(kTimeseriesMetaFieldName + kSubField1Name
+ << 1 << kTimeseriesMetaFieldName + kSubField2Name << 1);
+ BSONObj bucketsIndexSpec =
+ BSON(BucketUnpacker::kBucketMetaFieldName + kSubField1Name
+ << 1 << BucketUnpacker::kBucketMetaFieldName + kSubField2Name << 1);
+
+ testBothWaysIndexSpecConversion(timeseriesOptions, timeseriesIndexSpec, bucketsIndexSpec);
+}
+
+// {tm: 1, mm.subfield1: 1} <=> {control.min.tm: 1, control.max.tm: 1, meta.subfield1: 1}
+TEST(TimeseriesIndexSchemaConversionTest, TimeAndMetadataCompoundIndexSpecConversion) {
+ TimeseriesOptions timeseriesOptions = makeTimeseriesOptions();
+ BSONObj timeseriesIndexSpec =
+ BSON(kTimeseriesTimeFieldName << 1 << kTimeseriesMetaFieldName + kSubField1Name << 1);
+ BSONObj bucketsIndexSpec = BSON(kControlMinTimeFieldName
+ << 1 << kControlMaxTimeFieldName << 1
+ << BucketUnpacker::kBucketMetaFieldName + kSubField1Name << 1);
+
+ testBothWaysIndexSpecConversion(timeseriesOptions, timeseriesIndexSpec, bucketsIndexSpec);
+}
+
+// {mm.subfield1: 1, tm: 1} <=> {meta.subfield1: 1, control.min.tm: 1, control.max.tm: 1}
+TEST(TimeseriesIndexSchemaConversionTest, MetadataAndTimeCompoundIndexSpecConversion) {
+ TimeseriesOptions timeseriesOptions = makeTimeseriesOptions();
+ BSONObj timeseriesIndexSpec =
+ BSON(kTimeseriesMetaFieldName + kSubField1Name << 1 << kTimeseriesTimeFieldName << 1);
+ BSONObj bucketsIndexSpec =
+ BSON(BucketUnpacker::kBucketMetaFieldName + kSubField1Name
+ << 1 << kControlMinTimeFieldName << 1 << kControlMaxTimeFieldName << 1);
+
+ testBothWaysIndexSpecConversion(timeseriesOptions, timeseriesIndexSpec, bucketsIndexSpec);
+}
+
+// {somedatafield: 1} <=> {data.somedatafield: 1}
+TEST(TimeseriesIndexSchemaConversionTest, DataIndexSpecConversionFails) {
+ TimeseriesOptions timeseriesOptions = makeTimeseriesOptions();
+ BSONObj timeseriesIndexSpec = BSON(kTimeseriesSomeDataFieldName << 1);
+ BSONObj bucketsIndexSpec = BSON(kBucketsSomeDataFieldName << 1);
+
+ testBothWaysIndexSpecConversion(
+ timeseriesOptions, timeseriesIndexSpec, bucketsIndexSpec, false /* testShouldSucceed */);
+}
+
+// {tm: 1, somedatafield: 1} <=> {control.min.tm: 1, control.max.tm: 1, data.somedatafield: 1}
+TEST(TimeseriesIndexSchemaConversionTest, TimeAndDataCompoundIndexSpecConversionFails) {
+ TimeseriesOptions timeseriesOptions = makeTimeseriesOptions();
+ BSONObj timeseriesIndexSpec =
+ BSON(kTimeseriesTimeFieldName << 1 << kTimeseriesSomeDataFieldName << 1);
+ BSONObj bucketsIndexSpec = BSON(kControlMinTimeFieldName << 1 << kControlMaxTimeFieldName << 1
+ << kBucketsSomeDataFieldName << 1);
+
+ testBothWaysIndexSpecConversion(
+ timeseriesOptions, timeseriesIndexSpec, bucketsIndexSpec, false /* testShouldSucceed */);
+}
+
+// {somedatafield: 1, tm: 1} <=> {data.somedatafield: 1, control.min.tm: 1, control.max.tm: 1}
+TEST(TimeseriesIndexSchemaConversionTest, DataAndTimeCompoundIndexSpecConversionFails) {
+ TimeseriesOptions timeseriesOptions = makeTimeseriesOptions();
+ BSONObj timeseriesIndexSpec =
+ BSON(kTimeseriesSomeDataFieldName << 1 << kTimeseriesTimeFieldName << 1);
+ BSONObj bucketsIndexSpec = BSON(kBucketsSomeDataFieldName << 1 << kControlMinTimeFieldName << 1
+ << kControlMaxTimeFieldName << 1);
+
+ testBothWaysIndexSpecConversion(
+ timeseriesOptions, timeseriesIndexSpec, bucketsIndexSpec, false /* testShouldSucceed */);
+}
+
+// {mm: 1, somedatafield: 1} <=> {meta: 1, data.somedatafield: 1}
+TEST(TimeseriesIndexSchemaConversionTest, MetadataAndDataCompoundIndexSpecConversionFails) {
+ TimeseriesOptions timeseriesOptions = makeTimeseriesOptions();
+ BSONObj timeseriesIndexSpec =
+ BSON(kTimeseriesMetaFieldName << 1 << kTimeseriesSomeDataFieldName << 1);
+ BSONObj bucketsIndexSpec =
+ BSON(BucketUnpacker::kBucketMetaFieldName << 1 << kBucketsSomeDataFieldName << 1);
+
+ testBothWaysIndexSpecConversion(
+ timeseriesOptions, timeseriesIndexSpec, bucketsIndexSpec, false /* testShouldSucceed */);
+}
+
+// {somedatafield: 1, mm: 1} <=> {data.somedatafield: 1, meta: 1}
+TEST(TimeseriesIndexSchemaConversionTest, DataAndMetadataCompoundIndexSpecConversionFails) {
+ TimeseriesOptions timeseriesOptions = makeTimeseriesOptions();
+ BSONObj timeseriesIndexSpec =
+ BSON(kTimeseriesSomeDataFieldName << 1 << kTimeseriesMetaFieldName << 1);
+ BSONObj bucketsIndexSpec =
+ BSON(kBucketsSomeDataFieldName << 1 << BucketUnpacker::kBucketMetaFieldName << 1);
+
+ testBothWaysIndexSpecConversion(
+ timeseriesOptions, timeseriesIndexSpec, bucketsIndexSpec, false /* testShouldSucceed */);
+}
+
+// {mm.subfield1: 1, mm.subfield2: 1, mm.foo:1, mm.bar: 1, mm.baz: 1, tm: 1} <=>
+// {meta.subfield1: 1, meta.subfield2: 1, meta.foo: 1, meta.bar: 1, meta.baz: 1, control.min.tm: 1,
+// control.max.tm: 1}
+TEST(TimeseriesIndexSchemaConversionTest, ManyFieldCompoundIndexSpecConversion) {
+ const auto kMetaFieldName = BucketUnpacker::kBucketMetaFieldName;
+
+ TimeseriesOptions timeseriesOptions = makeTimeseriesOptions();
+ BSONObj timeseriesIndexSpec =
+ BSON(kTimeseriesMetaFieldName + kSubField1Name
+ << 1 << kTimeseriesMetaFieldName + kSubField2Name << 1
+ << kTimeseriesMetaFieldName + ".foo" << 1 << kTimeseriesMetaFieldName + ".bar" << 1
+ << kTimeseriesMetaFieldName + ".baz" << 1 << kTimeseriesTimeFieldName << 1);
+ BSONObj bucketsIndexSpec =
+ BSON(kMetaFieldName + kSubField1Name
+ << 1 << kMetaFieldName + kSubField2Name << 1 << kMetaFieldName + ".foo" << 1
+ << kMetaFieldName + ".bar" << 1 << kMetaFieldName + ".baz" << 1
+ << kControlMinTimeFieldName << 1 << kControlMaxTimeFieldName << 1);
+
+ testBothWaysIndexSpecConversion(timeseriesOptions, timeseriesIndexSpec, bucketsIndexSpec);
+}
+
+} // namespace
+} // namespace mongo