summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Larkin-York <dan.larkin-york@mongodb.com>2021-07-06 20:29:04 +0000
committerDan Larkin-York <dan.larkin-york@mongodb.com>2021-07-14 16:01:26 +0000
commit3b9982be740bc360691f46ed534427065763c16f (patch)
treeac06cd2626aac50f2658cb85b47328c098130eb3
parent3e8b8bbf8fd4243d9b7e81fbb569d492da05628a (diff)
downloadmongo-3b9982be740bc360691f46ed534427065763c16f.tar.gz
SERVER-58171 Changing time-series granularity does not update view definition
-rw-r--r--jstests/core/timeseries/bucket_granularity.js118
-rw-r--r--jstests/core/timeseries/bucket_timestamp_rounding.js30
-rw-r--r--src/mongo/db/catalog/coll_mod.cpp54
-rw-r--r--src/mongo/db/catalog/create_collection.cpp17
-rw-r--r--src/mongo/db/commands/dbcommands.cpp70
-rw-r--r--src/mongo/db/timeseries/timeseries_options.cpp80
-rw-r--r--src/mongo/db/timeseries/timeseries_options.h5
7 files changed, 255 insertions, 119 deletions
diff --git a/jstests/core/timeseries/bucket_granularity.js b/jstests/core/timeseries/bucket_granularity.js
index b14f56e4e7f..62a6594e359 100644
--- a/jstests/core/timeseries/bucket_granularity.js
+++ b/jstests/core/timeseries/bucket_granularity.js
@@ -19,6 +19,17 @@
(function() {
+function verifyViewPipeline(coll) {
+ const cProps =
+ db.runCommand({listCollections: 1, filter: {name: coll.getName()}}).cursor.firstBatch[0];
+ const cSeconds = cProps.options.timeseries.bucketMaxSpanSeconds;
+ const vProps = db.system.views.find({_id: `${db.getName()}.${coll.getName()}`}).toArray()[0];
+ const vSeconds = vProps.pipeline[0].$_internalUnpackBucket.bucketMaxSpanSeconds;
+ assert.eq(cSeconds,
+ vSeconds,
+ `expected view pipeline 'bucketMaxSpanSeconds' to match timeseries options`);
+}
+
(function testSeconds() {
let coll = db.granularitySeconds;
coll.drop();
@@ -88,6 +99,7 @@
assert.commandWorked(db.createCollection(
coll.getName(), {timeseries: {timeField: 't', granularity: 'seconds'}}));
+ verifyViewPipeline(coll);
// All measurements land in the same bucket.
assert.commandWorked(coll.insert({t: ISODate("2021-04-22T20:00:00.000Z")}));
@@ -102,21 +114,64 @@
assert.commandWorked(coll.insert({t: ISODate("2021-04-22T21:00:00.000Z")}));
assert.eq(2, db.system.buckets.granularitySecondsToMinutes.find().itcount());
- // TODO SERVER-58171: Re-enable these tests:
// Now let's bump to minutes and make sure we get the expected behavior
- // assert.commandWorked(
- // db.runCommand({collMod: coll.getName(), timeseries: {granularity: 'minutes'}}));
+ assert.commandWorked(
+ db.runCommand({collMod: coll.getName(), timeseries: {granularity: 'minutes'}}));
+ verifyViewPipeline(coll);
// All measurements land in the same bucket.
- // assert.commandWorked(coll.insert({t: ISODate("2021-04-23T20:00:00.000Z")}));
- // assert.commandWorked(coll.insert({t: ISODate("2021-04-23T20:22:02.000Z")}));
- // assert.commandWorked(coll.insert({t: ISODate("2021-04-23T20:59:59.999Z")}));
- // assert.eq(2, db.system.buckets.granularitySecondsToMinutes.find().itcount());
+ assert.commandWorked(coll.insert({t: ISODate("2021-04-23T20:00:00.000Z")}));
+ assert.commandWorked(coll.insert({t: ISODate("2021-04-23T20:22:02.000Z")}));
+ assert.commandWorked(coll.insert({t: ISODate("2021-04-23T20:59:59.999Z")}));
+ assert.eq(2, db.system.buckets.granularitySecondsToMinutes.find().itcount());
// Expect bucket max span to be one day. A new measurement outside of this range should create
// a new bucket.
- // assert.commandWorked(coll.insert({t: ISODate("2021-04-23T21:00:00.000Z")}));
- // assert.eq(3, db.system.buckets.granularitySecondsToMinutes.find().itcount());
+ assert.commandWorked(coll.insert({t: ISODate("2021-04-23T21:00:00.000Z")}));
+ assert.eq(3, db.system.buckets.granularitySecondsToMinutes.find().itcount());
+
+ // Make sure when we query, we use the new bucket max span to make sure we get all matches
+ assert.eq(4, coll.find({t: {$gt: ISODate("2021-04-23T19:00:00.000Z")}}).itcount());
+})();
+
+(function testIncreasingSecondsToHours() {
+ let coll = db.granularitySecondsToHours;
+ coll.drop();
+
+ assert.commandWorked(db.createCollection(
+ coll.getName(), {timeseries: {timeField: 't', granularity: 'seconds'}}));
+ verifyViewPipeline(coll);
+
+ // All measurements land in the same bucket.
+ assert.commandWorked(coll.insert({t: ISODate("2021-04-22T20:00:00.000Z")}));
+ assert.commandWorked(coll.insert({t: ISODate("2021-04-22T20:00:03.000Z")}));
+ assert.commandWorked(coll.insert({t: ISODate("2021-04-22T20:00:59.999Z")}));
+ assert.eq(1, db.system.buckets.granularitySecondsToHours.find().itcount());
+
+ // Expect bucket max span to be one hour. A new measurement outside of this range should create
+ // a new bucket.
+ assert.commandWorked(coll.insert({t: ISODate("2021-04-22T20:59:59.999Z")}));
+ assert.eq(1, db.system.buckets.granularitySecondsToHours.find().itcount());
+ assert.commandWorked(coll.insert({t: ISODate("2021-04-22T21:00:00.000Z")}));
+ assert.eq(2, db.system.buckets.granularitySecondsToHours.find().itcount());
+
+ assert.commandWorked(
+ db.runCommand({collMod: coll.getName(), timeseries: {granularity: 'hours'}}));
+ verifyViewPipeline(coll);
+
+ // All measurements land in the same bucket.
+ assert.commandWorked(coll.insert({t: ISODate("2021-05-22T00:00:00.000Z")}));
+ assert.commandWorked(coll.insert({t: ISODate("2021-05-22T18:11:03.000Z")}));
+ assert.commandWorked(coll.insert({t: ISODate("2021-05-22T20:59:59.999Z")}));
+ assert.eq(2, db.system.buckets.granularitySecondsToHours.find().itcount());
+
+ // Expect bucket max span to be 30 days. A new measurement outside of this range should create
+ // a new bucket.
+ assert.commandWorked(coll.insert({t: ISODate("2021-05-22T21:00:00.001Z")}));
+ assert.eq(3, db.system.buckets.granularitySecondsToHours.find().itcount());
+
+ // Make sure when we query, we use the new bucket max span to make sure we get all matches
+ assert.eq(4, coll.find({t: {$gt: ISODate("2021-05-21T00:00:00.000Z")}}).itcount());
})();
(function testIncreasingMinutesToHours() {
@@ -125,6 +180,7 @@
assert.commandWorked(db.createCollection(
coll.getName(), {timeseries: {timeField: 't', granularity: 'minutes'}}));
+ verifyViewPipeline(coll);
// All measurements land in the same bucket.
assert.commandWorked(coll.insert({t: ISODate("2021-04-22T20:00:00.000Z")}));
@@ -139,43 +195,45 @@
assert.commandWorked(coll.insert({t: ISODate("2021-04-23T20:00:00.000Z")}));
assert.eq(2, db.system.buckets.granularityMinutesToHours.find().itcount());
- // TODO SERVER-58171: Re-enable these tests:
- // assert.commandWorked(
- // db.runCommand({collMod: coll.getName(), timeseries: {granularity: 'hours'}}));
+ assert.commandWorked(
+ db.runCommand({collMod: coll.getName(), timeseries: {granularity: 'hours'}}));
+ verifyViewPipeline(coll);
// All measurements land in the same bucket.
- // assert.commandWorked(coll.insert({t: ISODate("2021-05-23T00:00:00.000Z")}));
- // assert.commandWorked(coll.insert({t: ISODate("2021-05-23T18:11:03.000Z")}));
- // assert.commandWorked(coll.insert({t: ISODate("2021-05-23T19:59:59.999Z")}));
- // assert.eq(2, db.system.buckets.granularityMinutesToHours.find().itcount());
+ assert.commandWorked(coll.insert({t: ISODate("2021-05-23T00:00:00.000Z")}));
+ assert.commandWorked(coll.insert({t: ISODate("2021-05-23T18:11:03.000Z")}));
+ assert.commandWorked(coll.insert({t: ISODate("2021-05-23T19:59:59.999Z")}));
+ assert.eq(2, db.system.buckets.granularityMinutesToHours.find().itcount());
// Expect bucket max span to be 30 days. A new measurement outside of this range should create
// a new bucket.
- // assert.commandWorked(coll.insert({t: ISODate("2021-05-23T20:00:00.001Z")}));
- // assert.eq(3, db.system.buckets.granularityMinutesToHours.find().itcount());
+ assert.commandWorked(coll.insert({t: ISODate("2021-05-23T20:00:00.001Z")}));
+ assert.eq(3, db.system.buckets.granularityMinutesToHours.find().itcount());
+
+ // Make sure when we query, we use the new bucket max span to make sure we get all matches
+ assert.eq(4, coll.find({t: {$gt: ISODate("2021-05-22T00:00:00.000Z")}}).itcount());
})();
(function testReducingGranularityFails() {
- let coll = db.granularityMinutesToHours;
+ let coll = db.reducingGranularityFails;
coll.drop();
- // TODO SERVER-58171: Re-enable these tests:
- // assert.commandWorked(db.createCollection(
- // coll.getName(), {timeseries: {timeField: 't', granularity: 'minutes'}}));
+ assert.commandWorked(db.createCollection(
+ coll.getName(), {timeseries: {timeField: 't', granularity: 'minutes'}}));
// Decreasing minutes -> seconds shouldn't work.
- // assert.commandFailed(
- // db.runCommand({collMod: coll.getName(), timeseries: {granularity: 'seconds'}}));
+ assert.commandFailed(
+ db.runCommand({collMod: coll.getName(), timeseries: {granularity: 'seconds'}}));
// Increasing minutes -> hours should work fine.
- // assert.commandWorked(
- // db.runCommand({collMod: coll.getName(), timeseries: {granularity: 'hours'}}));
+ assert.commandWorked(
+ db.runCommand({collMod: coll.getName(), timeseries: {granularity: 'hours'}}));
// Decreasing hours -> minutes shouldn't work.
- // assert.commandFailed(
- // db.runCommand({collMod: coll.getName(), timeseries: {granularity: 'minutes'}}));
+ assert.commandFailed(
+ db.runCommand({collMod: coll.getName(), timeseries: {granularity: 'minutes'}}));
// Decreasing hours -> seconds shouldn't work either.
- // assert.commandFailed(
- // db.runCommand({collMod: coll.getName(), timeseries: {granularity: 'seconds'}}));
+ assert.commandFailed(
+ db.runCommand({collMod: coll.getName(), timeseries: {granularity: 'seconds'}}));
})();
})();
diff --git a/jstests/core/timeseries/bucket_timestamp_rounding.js b/jstests/core/timeseries/bucket_timestamp_rounding.js
index 62d584a365b..d3bcf8ef33c 100644
--- a/jstests/core/timeseries/bucket_timestamp_rounding.js
+++ b/jstests/core/timeseries/bucket_timestamp_rounding.js
@@ -86,19 +86,18 @@
assert.eq(1, buckets.length);
assert.eq(buckets[0].control.min.t, ISODate("2021-04-22T20:10:00.000Z"));
- // TODO SERVER-58171: Re-enable these tests
// Now let's bump to minutes and make sure we get the expected behavior
- // assert.commandWorked(
- // db.runCommand({collMod: coll.getName(), timeseries: {granularity: 'minutes'}}));
+ assert.commandWorked(
+ db.runCommand({collMod: coll.getName(), timeseries: {granularity: 'minutes'}}));
// Open a new bucket and ensure min time is rounded down to nearest hour.
- // assert.commandWorked(coll.insert({t: ISODate("2021-04-24T20:10:14.134Z")}));
+ assert.commandWorked(coll.insert({t: ISODate("2021-04-24T20:10:14.134Z")}));
// And that going backwards, but after the rounding point, doesn't open another new bucket.
- // assert.commandWorked(coll.insert({t: ISODate("2021-04-24T20:05:00.000Z")}));
+ assert.commandWorked(coll.insert({t: ISODate("2021-04-24T20:05:00.000Z")}));
- // buckets = db.system.buckets.granularitySecondsToMinutes.find().toArray();
- // assert.eq(2, buckets.length);
- // assert.eq(buckets[1].control.min.t, ISODate("2021-04-24T20:00:00.000Z"));
+ buckets = db.system.buckets.granularitySecondsToMinutes.find().toArray();
+ assert.eq(2, buckets.length);
+ assert.eq(buckets[1].control.min.t, ISODate("2021-04-24T20:00:00.000Z"));
})();
(function testMinutesToHours() {
@@ -117,18 +116,17 @@
assert.eq(1, buckets.length);
assert.eq(buckets[0].control.min.t, ISODate("2021-04-22T20:00:00.000Z"));
- // TODO SERVER-58171: Re-enable these tests
// Now let's bump to minutes and make sure we get the expected behavior
- // assert.commandWorked(
- // db.runCommand({collMod: coll.getName(), timeseries: {granularity: 'hours'}}));
+ assert.commandWorked(
+ db.runCommand({collMod: coll.getName(), timeseries: {granularity: 'hours'}}));
// Open a new bucket and ensure min time is rounded down to nearest day.
- // assert.commandWorked(coll.insert({t: ISODate("2021-06-24T20:10:14.134Z")}));
+ assert.commandWorked(coll.insert({t: ISODate("2021-06-24T20:10:14.134Z")}));
// And that going backwards, but after the rounding point, doesn't open another new bucket.
- // assert.commandWorked(coll.insert({t: ISODate("2021-06-24T10:00:00.000Z")}));
+ assert.commandWorked(coll.insert({t: ISODate("2021-06-24T10:00:00.000Z")}));
- // buckets = db.system.buckets.granularityMinutesToHours.find().toArray();
- // assert.eq(2, buckets.length);
- // assert.eq(buckets[1].control.min.t, ISODate("2021-06-24T00:00:00.000Z"));
+ buckets = db.system.buckets.granularityMinutesToHours.find().toArray();
+ assert.eq(2, buckets.length);
+ assert.eq(buckets[1].control.min.t, ISODate("2021-06-24T00:00:00.000Z"));
})();
})();
diff --git a/src/mongo/db/catalog/coll_mod.cpp b/src/mongo/db/catalog/coll_mod.cpp
index 08019ffa486..04ef2969437 100644
--- a/src/mongo/db/catalog/coll_mod.cpp
+++ b/src/mongo/db/catalog/coll_mod.cpp
@@ -114,35 +114,6 @@ struct CollModRequest {
bool recordPreImages = false;
};
-bool isValidTimeseriesGranularityTransition(BucketGranularityEnum current,
- BucketGranularityEnum target) {
- bool validTransition = true;
- if (current == target) {
- return validTransition;
- }
-
- switch (current) {
- case BucketGranularityEnum::Seconds: {
- if (target != BucketGranularityEnum::Minutes) {
- validTransition = false;
- }
- break;
- }
- case BucketGranularityEnum::Minutes: {
- if (target != BucketGranularityEnum::Hours) {
- validTransition = false;
- }
- break;
- }
- case BucketGranularityEnum::Hours: {
- validTransition = false;
- break;
- }
- }
-
- return validTransition;
-}
-
StatusWith<CollModRequest> parseCollModRequest(OperationContext* opCtx,
const NamespaceString& nss,
const CollectionPtr& coll,
@@ -368,12 +339,6 @@ StatusWith<CollModRequest> parseCollModRequest(OperationContext* opCtx,
<< fieldName);
}
- if (e.Obj().hasField("granularity")) {
- // TODO: SERVER-58171 Re-enable this feature
- return Status(ErrorCodes::InvalidOptions,
- "Changing timeseries 'granularity' is not allowed");
- }
-
cmr.timeseries = e;
} else {
if (isView) {
@@ -651,21 +616,10 @@ Status _collModInternal(OperationContext* opCtx,
}
if (ts.isABSONObj()) {
- TimeseriesOptions newOptions = *oldCollOptions.timeseries;
- bool changed = false;
-
- if (ts.Obj().hasField("granularity")) {
- BSONElement granularityElem = ts.Obj().getField("granularity");
- BucketGranularityEnum target = BucketGranularity_parse(
- IDLParserErrorContext("BucketGranularity"), granularityElem.valueStringData());
- if (target != oldCollOptions.timeseries->getGranularity()) {
- newOptions.setGranularity(target);
- newOptions.setBucketMaxSpanSeconds(
- timeseries::getMaxSpanSecondsFromGranularity(target));
- changed = true;
- }
- }
-
+ auto res = timeseries::applyTimeseriesOptionsModifications(*oldCollOptions.timeseries,
+ ts.Obj());
+ uassertStatusOK(res);
+ auto [newOptions, changed] = res.getValue();
if (changed) {
coll.getWritableCollection()->setTimeseriesOptions(opCtx, newOptions);
}
diff --git a/src/mongo/db/catalog/create_collection.cpp b/src/mongo/db/catalog/create_collection.cpp
index b759d4b92f5..1cc3c7b8a0d 100644
--- a/src/mongo/db/catalog/create_collection.cpp
+++ b/src/mongo/db/catalog/create_collection.cpp
@@ -383,21 +383,8 @@ Status _createTimeseries(OperationContext* opCtx,
CollectionOptions viewOptions;
viewOptions.viewOn = bucketsNs.coll().toString();
viewOptions.collation = options.collation;
-
- if (options.timeseries->getMetaField()) {
- viewOptions.pipeline = BSON_ARRAY(BSON(
- "$_internalUnpackBucket"
- << BSON("timeField" << options.timeseries->getTimeField() << "metaField"
- << *options.timeseries->getMetaField() << "bucketMaxSpanSeconds"
- << *options.timeseries->getBucketMaxSpanSeconds() << "exclude"
- << BSONArray())));
- } else {
- viewOptions.pipeline = BSON_ARRAY(BSON(
- "$_internalUnpackBucket"
- << BSON("timeField" << options.timeseries->getTimeField() << "bucketMaxSpanSeconds"
- << *options.timeseries->getBucketMaxSpanSeconds() << "exclude"
- << BSONArray())));
- }
+ constexpr bool asArray = true;
+ viewOptions.pipeline = timeseries::generateViewPipeline(*options.timeseries, asArray);
// Create the time-series view.
auto status = db->userCreateNS(opCtx, ns, viewOptions);
diff --git a/src/mongo/db/commands/dbcommands.cpp b/src/mongo/db/commands/dbcommands.cpp
index 888ead550e0..7293ed47275 100644
--- a/src/mongo/db/commands/dbcommands.cpp
+++ b/src/mongo/db/commands/dbcommands.cpp
@@ -110,8 +110,8 @@ namespace {
* Returns a CollMod on the underlying buckets collection of the time-series collection.
* Returns null if 'origCmd' is not for a time-series collection.
*/
-std::unique_ptr<CollMod> makeTimeseriesCollModCommand(OperationContext* opCtx,
- const CollMod& origCmd) {
+std::unique_ptr<CollMod> makeTimeseriesBucketsCollModCommand(OperationContext* opCtx,
+ const CollMod& origCmd) {
const auto& origNs = origCmd.getNamespace();
auto timeseriesOptions = timeseries::getTimeseriesOptions(opCtx, origNs);
@@ -149,6 +149,43 @@ std::unique_ptr<CollMod> makeTimeseriesCollModCommand(OperationContext* opCtx,
return cmd;
}
+/**
+ * Returns a CollMod on the view definition of the time-series collection.
+ * Returns null if 'origCmd' is not for a time-series collection or if the view definition need not
+ * be changed.
+ */
+std::unique_ptr<CollMod> makeTimeseriesViewCollModCommand(OperationContext* opCtx,
+ const CollMod& origCmd) {
+ const auto& ns = origCmd.getNamespace();
+
+ auto timeseriesOptions = timeseries::getTimeseriesOptions(opCtx, ns);
+
+ // Return early with null if we are not working with a time-series collection.
+ if (!timeseriesOptions) {
+ return {};
+ }
+
+ auto& tsMod = origCmd.getTimeseries();
+ if (tsMod) {
+ auto res =
+ timeseries::applyTimeseriesOptionsModifications(*timeseriesOptions, tsMod->toBSON());
+ if (res.isOK()) {
+ auto& [newOptions, changed] = res.getValue();
+ if (changed) {
+ auto cmd = std::make_unique<CollMod>(ns);
+ constexpr bool asArray = false;
+ std::vector<BSONObj> pipeline = {
+ timeseries::generateViewPipeline(newOptions, asArray)};
+ cmd->setPipeline(std::move(pipeline));
+ return cmd;
+ }
+ }
+ }
+
+ return {};
+}
+
+
class CmdDropDatabase : public DropDatabaseCmdVersion1Gen<CmdDropDatabase> {
public:
std::string help() const final {
@@ -579,12 +616,29 @@ public:
// time-series collection, which are important for maintaining the view-bucket
// relationship.
//
- // 'timeseriesCmd' is null if the request namespace does not refer to a time-series
- // collection. Otherwise, transforms the user time-series index request to one on the
- // underlying bucket.
- auto timeseriesCmd = makeTimeseriesCollModCommand(opCtx, requestParser.request());
- if (timeseriesCmd) {
- cmd = timeseriesCmd.get();
+ // 'timeseriesBucketsCmd' is null if the request namespace does not refer to a time-series
+ // collection. Otherwise, transforms the user time-series collMod request to one on the
+ // underlying bucket collection.
+ auto timeseriesBucketsCmd =
+ makeTimeseriesBucketsCollModCommand(opCtx, requestParser.request());
+ if (timeseriesBucketsCmd) {
+ // We additionally create a special, limited collMod command for the view definition
+ // itself if the pipeline needs to be updated to reflect changed timeseries options.
+ // This operation is completed first. In the case that we get a partial update where
+ // only one of the two collMod operations fully completes (e.g. replication rollback),
+ // having the view pipeline update without updating the timeseries options on the
+ // buckets collection will result in sub-optimal performance, but correct behavior.
+ // If the timeseries options were updated without updating the view pipeline, we could
+ // end up with incorrect query behavior (namely data missing from some queries).
+ auto timeseriesViewCmd =
+ makeTimeseriesViewCollModCommand(opCtx, requestParser.request());
+ if (timeseriesViewCmd) {
+ uassertStatusOK(collMod(opCtx,
+ timeseriesViewCmd->getNamespace(),
+ timeseriesViewCmd->toBSON(BSONObj()),
+ &result));
+ }
+ cmd = timeseriesBucketsCmd.get();
}
uassertStatusOK(collMod(opCtx, cmd->getNamespace(), cmd->toBSON(BSONObj()), &result));
diff --git a/src/mongo/db/timeseries/timeseries_options.cpp b/src/mongo/db/timeseries/timeseries_options.cpp
index 6ba0a5b7480..00921c7d34d 100644
--- a/src/mongo/db/timeseries/timeseries_options.cpp
+++ b/src/mongo/db/timeseries/timeseries_options.cpp
@@ -37,6 +37,45 @@ namespace mongo {
namespace timeseries {
+namespace {
+
+BSONObj wrapInArrayIf(bool doWrap, BSONObj&& obj) {
+ if (doWrap) {
+ return (::mongo::BSONArrayBuilder() << std::move(obj)).arr();
+ }
+ return std::move(obj);
+}
+
+
+bool isValidTimeseriesGranularityTransition(BucketGranularityEnum current,
+ BucketGranularityEnum target) {
+ bool validTransition = true;
+ if (current == target) {
+ return validTransition;
+ }
+
+ switch (current) {
+ case BucketGranularityEnum::Seconds: {
+ // Both minutes and hours are allowed.
+ break;
+ }
+ case BucketGranularityEnum::Minutes: {
+ if (target != BucketGranularityEnum::Hours) {
+ validTransition = false;
+ }
+ break;
+ }
+ case BucketGranularityEnum::Hours: {
+ validTransition = false;
+ break;
+ }
+ }
+
+ return validTransition;
+}
+
+} // namespace
+
boost::optional<TimeseriesOptions> getTimeseriesOptions(OperationContext* opCtx,
const NamespaceString& nss) {
auto bucketsNs = nss.makeTimeseriesBucketsNamespace();
@@ -79,5 +118,46 @@ int getBucketRoundingSecondsFromGranularity(BucketGranularityEnum granularity) {
MONGO_UNREACHABLE;
}
+StatusWith<std::pair<TimeseriesOptions, bool>> applyTimeseriesOptionsModifications(
+ const TimeseriesOptions& currentOptions, const BSONObj& mod) {
+ TimeseriesOptions newOptions = currentOptions;
+ bool changed = false;
+
+ if (mod.hasField("granularity")) {
+ BSONElement granularityElem = mod.getField("granularity");
+ BucketGranularityEnum target = BucketGranularity_parse(
+ IDLParserErrorContext("BucketGranularity"), granularityElem.valueStringData());
+ if (target != currentOptions.getGranularity()) {
+ if (!isValidTimeseriesGranularityTransition(currentOptions.getGranularity(), target)) {
+ return Status{ErrorCodes::InvalidOptions,
+ "Invalid transition for timeseries.granularity. Can only transition "
+ "from 'seconds' to 'minutes' or 'minutes' to 'hours'."};
+ }
+ newOptions.setGranularity(target);
+ newOptions.setBucketMaxSpanSeconds(
+ timeseries::getMaxSpanSecondsFromGranularity(target));
+ changed = true;
+ }
+ }
+
+ return std::make_pair(newOptions, changed);
+}
+
+BSONObj generateViewPipeline(const TimeseriesOptions& options, bool asArray) {
+ if (options.getMetaField()) {
+ return wrapInArrayIf(
+ asArray,
+ BSON("$_internalUnpackBucket" << BSON(
+ "timeField" << options.getTimeField() << "metaField" << *options.getMetaField()
+ << "bucketMaxSpanSeconds" << *options.getBucketMaxSpanSeconds()
+ << "exclude" << BSONArray())));
+ }
+ return wrapInArrayIf(
+ asArray,
+ BSON("$_internalUnpackBucket" << BSON(
+ "timeField" << options.getTimeField() << "bucketMaxSpanSeconds"
+ << *options.getBucketMaxSpanSeconds() << "exclude" << BSONArray())));
+}
+
} // namespace timeseries
} // namespace mongo
diff --git a/src/mongo/db/timeseries/timeseries_options.h b/src/mongo/db/timeseries/timeseries_options.h
index e3a605dde73..56361cfbb93 100644
--- a/src/mongo/db/timeseries/timeseries_options.h
+++ b/src/mongo/db/timeseries/timeseries_options.h
@@ -58,5 +58,10 @@ int getMaxSpanSecondsFromGranularity(BucketGranularityEnum granularity);
*/
int getBucketRoundingSecondsFromGranularity(BucketGranularityEnum granularity);
+StatusWith<std::pair<TimeseriesOptions, bool>> applyTimeseriesOptionsModifications(
+ const TimeseriesOptions& current, const BSONObj& mod);
+
+BSONObj generateViewPipeline(const TimeseriesOptions& options, bool asArray);
+
} // namespace timeseries
} // namespace mongo