summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Larkin-York <dan.larkin-york@mongodb.com>2022-07-30 18:40:02 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2022-08-29 22:00:54 +0000
commit64e49af85eedd2c95b89cc07fbcc2a7475c5b4fa (patch)
tree2b7e0c3c2664eecd404ebe3b40f8feaf2b311239
parent79cfcdd83eb6f64e164a588d0daf9bb873328b45 (diff)
downloadmongo-64e49af85eedd2c95b89cc07fbcc2a7475c5b4fa.tar.gz
SERVER-66794 SERVER-67814 SERVER-69052 Detect time-series collections that require extended range support
(cherry picked from commit 800d394a09d52b4fd4b4a2491d01ec9c9b7fad62) (cherry picked from commit 65002ae2d4bacf9413383faa4b38d3480a2a8328) (cherry picked from commit 97989ceff09af0a2ed6a8c38fad42337d10fe1ec) (cherry picked from commit f374f58e555a189e2a945fe5812a434cb3889ee3) (cherry picked from commit 8e4a1a6d60a38daa6a0598d52cae76e7b34eda49)
-rw-r--r--jstests/noPassthrough/timeseries_extended_range_rollback.js76
-rw-r--r--jstests/noPassthrough/timeseries_extended_range_startup.js65
-rw-r--r--src/mongo/db/SConscript8
-rw-r--r--src/mongo/db/catalog/SConscript3
-rw-r--r--src/mongo/db/catalog/catalog_control.cpp44
-rw-r--r--src/mongo/db/catalog/catalog_control.h9
-rw-r--r--src/mongo/db/catalog/catalog_control_test.cpp4
-rw-r--r--src/mongo/db/catalog/catalog_stats.cpp14
-rw-r--r--src/mongo/db/catalog/catalog_stats.h38
-rw-r--r--src/mongo/db/catalog/collection.h20
-rw-r--r--src/mongo/db/catalog/collection_impl.cpp25
-rw-r--r--src/mongo/db/catalog/collection_impl.h15
-rw-r--r--src/mongo/db/catalog/collection_mock.h8
-rw-r--r--src/mongo/db/op_observer/SConscript122
-rw-r--r--src/mongo/db/op_observer_impl.cpp22
-rw-r--r--src/mongo/db/startup_recovery.cpp6
-rw-r--r--src/mongo/db/timeseries/SConscript15
-rw-r--r--src/mongo/db/timeseries/timeseries_extended_range.cpp115
-rw-r--r--src/mongo/db/timeseries/timeseries_extended_range.h67
-rw-r--r--src/mongo/db/timeseries/timeseries_extended_range_test.cpp98
20 files changed, 754 insertions, 20 deletions
diff --git a/jstests/noPassthrough/timeseries_extended_range_rollback.js b/jstests/noPassthrough/timeseries_extended_range_rollback.js
new file mode 100644
index 00000000000..5224863143c
--- /dev/null
+++ b/jstests/noPassthrough/timeseries_extended_range_rollback.js
@@ -0,0 +1,76 @@
+/**
+ * Tests that time-series collection that require extended range support are properly recognized
+ * after rollback.
+ * @tags: [
+ * requires_replication,
+ * ]
+ */
+(function() {
+'use strict';
+
+load('jstests/replsets/libs/rollback_test.js');
+
+const getExtendedRangeCount = (db) => {
+ return assert.commandWorked(db.adminCommand({serverStatus: 1}))
+ .catalogStats.timeseriesExtendedRange;
+};
+
+const collName = "test.standard";
+
+// Operations that will be present on both nodes, before the common point.
+let CommonOps = (node) => {
+ const coll = node.getCollection(collName);
+ const db = coll.getDB("test");
+
+ assert.commandWorked(db.createCollection("standard", {timeseries: {timeField: "time"}}));
+ assert.commandWorked(db.createCollection("extended", {timeseries: {timeField: "time"}}));
+ assert.commandWorked(db.standard.insert({time: ISODate("1980-01-01T00:00:00.000Z")}, {w: 2}));
+ assert.commandWorked(db.extended.insert({time: ISODate("2040-01-01T00:00:00.000Z")}, {w: 2}));
+};
+
+// Operations that will be performed on the rollback node past the common point.
+let RollbackOps = (node) => {
+ const coll = node.getCollection(collName);
+ const db = coll.getDB("test");
+
+ assert.commandWorked(db.createCollection("extra", {timeseries: {timeField: "time"}}));
+};
+
+// Set up Rollback Test.
+const rollbackTest = new RollbackTest();
+const primary = rollbackTest.getPrimary();
+const secondary = rollbackTest.getSecondary();
+assert.eq(undefined, getExtendedRangeCount(primary));
+assert.eq(undefined, getExtendedRangeCount(secondary));
+CommonOps(primary);
+
+// Make sure the collections got flagged properly during the initial write.
+assert(checkLog.checkContainsWithCountJson(
+ primary, 6679402, {"nss": "test.standard", "timeField": "time"}, 0));
+assert(checkLog.checkContainsWithCountJson(
+ secondary, 6679402, {"nss": "test.standard", "timeField": "time"}, 0));
+assert(checkLog.checkContainsWithCountJson(
+ primary, 6679402, {"nss": "test.extended", "timeField": "time"}, 1));
+assert(checkLog.checkContainsWithCountJson(
+ secondary, 6679402, {"nss": "test.extended", "timeField": "time"}, 1));
+
+assert.eq(1, getExtendedRangeCount(primary));
+assert.eq(1, getExtendedRangeCount(secondary));
+
+const rollbackNode = rollbackTest.transitionToRollbackOperations();
+RollbackOps(rollbackNode);
+
+rollbackTest.transitionToSyncSourceOperationsBeforeRollback();
+rollbackTest.transitionToSyncSourceOperationsDuringRollback();
+rollbackTest.transitionToSteadyStateOperations();
+
+// Make sure the collections get flagged properly again during rollback.
+assert(checkLog.checkContainsWithCountJson(
+ rollbackNode, 6679402, {"nss": "test.standard", "timeField": "time"}, 0));
+assert(checkLog.checkContainsWithCountJson(
+ rollbackNode, 6679402, {"nss": "test.extended", "timeField": "time"}, 2));
+
+assert.eq(1, getExtendedRangeCount(rollbackNode));
+
+rollbackTest.stop();
+})();
diff --git a/jstests/noPassthrough/timeseries_extended_range_startup.js b/jstests/noPassthrough/timeseries_extended_range_startup.js
new file mode 100644
index 00000000000..21aa3046f84
--- /dev/null
+++ b/jstests/noPassthrough/timeseries_extended_range_startup.js
@@ -0,0 +1,65 @@
+/**
+ * Tests that time-series collection that require extended range support are properly recognized
+ * during startup recovery.
+ * @tags: [
+ * requires_replication,
+ * # The primary is restarted and must retain its data.
+ * requires_persistence,
+ * ]
+ */
+(function() {
+'use strict';
+
+const getExtendedRangeCount = (db) => {
+ return assert.commandWorked(db.adminCommand({serverStatus: 1}))
+ .catalogStats.timeseriesExtendedRange;
+};
+
+const rst = new ReplSetTest({name: jsTest.name(), nodes: 2});
+rst.startSet();
+rst.initiateWithHighElectionTimeout();
+
+const primary = rst.getPrimary();
+const secondary = rst.getSecondary();
+const dbName = "testDB";
+const primaryDB = primary.getDB(dbName);
+
+assert.eq(undefined, getExtendedRangeCount(primary));
+assert.eq(undefined, getExtendedRangeCount(secondary));
+
+assert.commandWorked(primaryDB.createCollection("standard", {timeseries: {timeField: "time"}}));
+assert.commandWorked(primaryDB.createCollection("extended", {timeseries: {timeField: "time"}}));
+assert.commandWorked(
+ primaryDB.standard.insert({time: ISODate("1980-01-01T00:00:00.000Z")}, {w: 2}));
+assert.commandWorked(
+ primaryDB.extended.insert({time: ISODate("2040-01-01T00:00:00.000Z")}, {w: 2}));
+
+// Make sure the collections got flagged properly during the initial write.
+assert(checkLog.checkContainsWithCountJson(
+ primary, 6679402, {"nss": "testDB.standard", "timeField": "time"}, 0));
+assert(checkLog.checkContainsWithCountJson(
+ secondary, 6679402, {"nss": "testDB.standard", "timeField": "time"}, 0));
+assert(checkLog.checkContainsWithCountJson(
+ primary, 6679402, {"nss": "testDB.extended", "timeField": "time"}, 1));
+assert(checkLog.checkContainsWithCountJson(
+ secondary, 6679402, {"nss": "testDB.extended", "timeField": "time"}, 1));
+
+assert.eq(1, getExtendedRangeCount(primary));
+assert.eq(1, getExtendedRangeCount(secondary));
+
+rst.restart(primary);
+rst.waitForState(primary, ReplSetTest.State.SECONDARY);
+
+assert.eq(1, primaryDB.standard.count());
+assert.eq(1, primaryDB.extended.count());
+
+// Make sure the collections get flagged properly again after startup.
+assert(checkLog.checkContainsWithCountJson(
+ primary, 6679402, {"nss": "testDB.standard", "timeField": "time"}, 0));
+assert(checkLog.checkContainsWithCountJson(
+ primary, 6679402, {"nss": "testDB.extended", "timeField": "time"}, 1));
+
+assert.eq(1, getExtendedRangeCount(primary));
+
+rst.stopSet();
+})(); \ No newline at end of file
diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript
index a528dd36b6f..a9b568bb861 100644
--- a/src/mongo/db/SConscript
+++ b/src/mongo/db/SConscript
@@ -827,6 +827,7 @@ env.Library(
'$BUILD_DIR/mongo/db/catalog/collection_catalog',
'$BUILD_DIR/mongo/db/repl/tenant_migration_access_blocker',
'$BUILD_DIR/mongo/db/timeseries/bucket_catalog',
+ '$BUILD_DIR/mongo/db/timeseries/timeseries_extended_range',
'$BUILD_DIR/mongo/s/coreshard',
'$BUILD_DIR/mongo/s/grid',
'catalog/collection_options',
@@ -1320,9 +1321,10 @@ env.Library(
"startup_recovery.cpp",
],
LIBDEPS_PRIVATE=[
- 'catalog/catalog_helpers',
- 'catalog/database_holder',
- 'commands/mongod_fcv',
+ '$BUILD_DIR/mongo/db/catalog/catalog_helpers',
+ '$BUILD_DIR/mongo/db/catalog/database_holder',
+ '$BUILD_DIR/mongo/db/commands/mongod_fcv',
+ '$BUILD_DIR/mongo/db/timeseries/timeseries_extended_range',
'dbdirectclient',
'dbhelpers',
'rebuild_indexes',
diff --git a/src/mongo/db/catalog/SConscript b/src/mongo/db/catalog/SConscript
index b7a80ad0047..d397b8dc0dd 100644
--- a/src/mongo/db/catalog/SConscript
+++ b/src/mongo/db/catalog/SConscript
@@ -322,6 +322,8 @@ env.Library(
'$BUILD_DIR/mongo/db/index_builds_coordinator_interface',
'$BUILD_DIR/mongo/db/rebuild_indexes',
'$BUILD_DIR/mongo/db/service_context',
+ '$BUILD_DIR/mongo/db/timeseries/timeseries_extended_range',
+ 'catalog_stats',
'collection',
'collection_catalog',
'database_holder',
@@ -374,6 +376,7 @@ env.Library(
'$BUILD_DIR/mongo/db/storage/key_string',
'$BUILD_DIR/mongo/db/storage/storage_engine_impl',
'$BUILD_DIR/mongo/db/system_index',
+ '$BUILD_DIR/mongo/db/timeseries/timeseries_extended_range',
'$BUILD_DIR/mongo/db/views/views_mongod',
'catalog_stats',
'collection',
diff --git a/src/mongo/db/catalog/catalog_control.cpp b/src/mongo/db/catalog/catalog_control.cpp
index ec5adb1550e..6fe83f1befa 100644
--- a/src/mongo/db/catalog/catalog_control.cpp
+++ b/src/mongo/db/catalog/catalog_control.cpp
@@ -35,6 +35,7 @@
#include "mongo/db/catalog/catalog_control.h"
+#include "mongo/db/catalog/catalog_stats.h"
#include "mongo/db/catalog/collection.h"
#include "mongo/db/catalog/collection_catalog.h"
#include "mongo/db/catalog/database.h"
@@ -43,17 +44,18 @@
#include "mongo/db/index_builds_coordinator.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/rebuild_indexes.h"
+#include "mongo/db/timeseries/timeseries_extended_range.h"
#include "mongo/logv2/log.h"
namespace mongo {
namespace catalog {
-MinVisibleTimestampMap closeCatalog(OperationContext* opCtx) {
+PreviousCatalogState closeCatalog(OperationContext* opCtx) {
invariant(opCtx->lockState()->isW());
IndexBuildsCoordinator::get(opCtx)->assertNoIndexBuildInProgress();
- MinVisibleTimestampMap minVisibleTimestampMap;
+ PreviousCatalogState previousCatalogState;
std::vector<std::string> allDbs =
opCtx->getServiceContext()->getStorageEngine()->listDatabases();
@@ -76,7 +78,12 @@ MinVisibleTimestampMap closeCatalog(OperationContext* opCtx) {
"coll_ns"_attr = coll->ns(),
"uuid"_attr = coll->uuid(),
"minVisible"_attr = minVisible);
- minVisibleTimestampMap[coll->uuid()] = *minVisible;
+ previousCatalogState.minVisibleTimestampMap[coll->uuid()] = *minVisible;
+ }
+
+ if (coll->getTimeseriesOptions()) {
+ previousCatalogState.requiresTimestampExtendedRangeSupportMap[coll->uuid()] =
+ coll->getRequiresTimeseriesExtendedRangeSupport();
}
}
}
@@ -103,12 +110,16 @@ MinVisibleTimestampMap closeCatalog(OperationContext* opCtx) {
LOGV2(20272, "closeCatalog: closing storage engine catalog");
opCtx->getServiceContext()->getStorageEngine()->closeCatalog(opCtx);
+ // Reset the stats counter for extended range time-series collections. This is maintained
+ // outside the catalog itself.
+ catalog_stats::requiresTimeseriesExtendedRangeSupport.store(0);
+
reopenOnFailure.dismiss();
- return minVisibleTimestampMap;
+ return previousCatalogState;
}
void openCatalog(OperationContext* opCtx,
- const MinVisibleTimestampMap& minVisibleTimestampMap,
+ const PreviousCatalogState& previousCatalogState,
Timestamp stableTimestamp) {
invariant(opCtx->lockState()->isW());
@@ -198,7 +209,7 @@ void openCatalog(OperationContext* opCtx,
str::stream()
<< "failed to get valid collection pointer for namespace " << collNss);
- if (minVisibleTimestampMap.count(collection->uuid()) > 0) {
+ if (previousCatalogState.minVisibleTimestampMap.count(collection->uuid()) > 0) {
// After rolling back to a stable timestamp T, the minimum visible timestamp for
// each collection must be reset to (at least) its value at T. Additionally, there
// cannot exist a minimum visible timestamp greater than lastApplied. This allows us
@@ -208,11 +219,28 @@ void openCatalog(OperationContext* opCtx,
// bound the minimum visible timestamp (where necessary) to the stable timestamp.
// The benefit of fine grained tracking is assumed to be low-value compared to the
// cost/effort.
- auto minVisible = std::min(stableTimestamp,
- minVisibleTimestampMap.find(collection->uuid())->second);
+ auto minVisible = std::min(
+ stableTimestamp,
+ previousCatalogState.minVisibleTimestampMap.find(collection->uuid())->second);
collection->setMinimumVisibleSnapshot(minVisible);
}
+ if (collection->getTimeseriesOptions()) {
+ bool extendedRangeSetting;
+ if (auto it = previousCatalogState.requiresTimestampExtendedRangeSupportMap.find(
+ collection->uuid());
+ it != previousCatalogState.requiresTimestampExtendedRangeSupportMap.end()) {
+ extendedRangeSetting = it->second;
+ } else {
+ extendedRangeSetting =
+ timeseries::collectionMayRequireExtendedRangeSupport(opCtx, collection);
+ }
+
+ if (extendedRangeSetting) {
+ collection->setRequiresTimeseriesExtendedRangeSupport(opCtx);
+ }
+ }
+
// If this is the oplog collection, re-establish the replication system's cached pointer
// to the oplog.
if (collNss.isOplog()) {
diff --git a/src/mongo/db/catalog/catalog_control.h b/src/mongo/db/catalog/catalog_control.h
index 527e1725c07..5658a08b953 100644
--- a/src/mongo/db/catalog/catalog_control.h
+++ b/src/mongo/db/catalog/catalog_control.h
@@ -36,6 +36,11 @@ namespace catalog {
using MinVisibleTimestamp = Timestamp;
using MinVisibleTimestampMap = std::map<UUID, MinVisibleTimestamp>;
+using RequiresTimestampExtendedRangeSupportMap = std::map<UUID, bool>;
+struct PreviousCatalogState {
+ MinVisibleTimestampMap minVisibleTimestampMap;
+ RequiresTimestampExtendedRangeSupportMap requiresTimestampExtendedRangeSupportMap;
+};
/**
* Closes the catalog, destroying all associated in-memory data structures for all databases. After
@@ -43,7 +48,7 @@ using MinVisibleTimestampMap = std::map<UUID, MinVisibleTimestamp>;
*
* Must be called with the global lock acquired in exclusive mode.
*/
-MinVisibleTimestampMap closeCatalog(OperationContext* opCtx);
+PreviousCatalogState closeCatalog(OperationContext* opCtx);
/**
* Restores the catalog and all in-memory state after a call to closeCatalog().
@@ -51,7 +56,7 @@ MinVisibleTimestampMap closeCatalog(OperationContext* opCtx);
* Must be called with the global lock acquired in exclusive mode.
*/
void openCatalog(OperationContext* opCtx,
- const MinVisibleTimestampMap& catalogState,
+ const PreviousCatalogState& catalogState,
Timestamp stableTimestamp);
} // namespace catalog
} // namespace mongo
diff --git a/src/mongo/db/catalog/catalog_control_test.cpp b/src/mongo/db/catalog/catalog_control_test.cpp
index 4eca3df6a9a..901c69450ee 100644
--- a/src/mongo/db/catalog/catalog_control_test.cpp
+++ b/src/mongo/db/catalog/catalog_control_test.cpp
@@ -75,8 +75,8 @@ TEST_F(CatalogControlTest, CloseAndOpenCatalog) {
ServiceContext::UniqueOperationContext opCtx = cc().makeOperationContext();
Lock::GlobalLock globalLk(opCtx.get(), MODE_X);
- auto map = catalog::closeCatalog(opCtx.get());
- ASSERT_EQUALS(0U, map.size());
+ auto previousState = catalog::closeCatalog(opCtx.get());
+ ASSERT_EQUALS(0U, previousState.minVisibleTimestampMap.size());
catalog::openCatalog(opCtx.get(), {}, Timestamp());
}
diff --git a/src/mongo/db/catalog/catalog_stats.cpp b/src/mongo/db/catalog/catalog_stats.cpp
index 0b50c555c1e..b0206e52be0 100644
--- a/src/mongo/db/catalog/catalog_stats.cpp
+++ b/src/mongo/db/catalog/catalog_stats.cpp
@@ -29,7 +29,7 @@
#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kCommand
-#include "mongo/platform/basic.h"
+#include "mongo/db/catalog/catalog_stats.h"
#include "mongo/db/catalog/collection_catalog.h"
#include "mongo/db/catalog/database_holder.h"
@@ -38,7 +38,10 @@
#include "mongo/db/views/view_catalog.h"
#include "mongo/logv2/log.h"
-namespace mongo {
+namespace mongo::catalog_stats {
+
+// Number of time-series collections requiring extended range support
+AtomicWord<int> requiresTimeseriesExtendedRangeSupport;
namespace {
class CatalogStatsSSS : public ServerStatusSection {
@@ -58,6 +61,7 @@ public:
int timeseries = 0;
int internalCollections = 0;
int internalViews = 0;
+ int timeseriesExtendedRange = 0;
void toBson(BSONObjBuilder* builder) const {
builder->append("collections", collections);
@@ -66,6 +70,9 @@ public:
builder->append("views", views);
builder->append("internalCollections", internalCollections);
builder->append("internalViews", internalViews);
+ if (timeseriesExtendedRange > 0) {
+ builder->append("timeseriesExtendedRange", timeseriesExtendedRange);
+ }
}
};
@@ -78,6 +85,7 @@ public:
stats.collections = catalogStats.userCollections;
stats.capped = catalogStats.userCapped;
stats.internalCollections = catalogStats.internal;
+ stats.timeseriesExtendedRange = requiresTimeseriesExtendedRangeSupport.load();
const auto viewCatalogDbNames = catalog->getViewCatalogDbNames();
for (const auto& dbName : viewCatalogDbNames) {
@@ -107,4 +115,4 @@ public:
} catalogStatsSSS;
} // namespace
-} // namespace mongo
+} // namespace mongo::catalog_stats
diff --git a/src/mongo/db/catalog/catalog_stats.h b/src/mongo/db/catalog/catalog_stats.h
new file mode 100644
index 00000000000..54cc04f0bb6
--- /dev/null
+++ b/src/mongo/db/catalog/catalog_stats.h
@@ -0,0 +1,38 @@
+/**
+ * Copyright (C) 2022-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.
+ */
+
+#pragma once
+
+#include "mongo/platform/atomic_word.h"
+
+namespace mongo::catalog_stats {
+
+extern AtomicWord<int> requiresTimeseriesExtendedRangeSupport;
+
+} // namespace mongo::catalog_stats
diff --git a/src/mongo/db/catalog/collection.h b/src/mongo/db/catalog/collection.h
index 5a54d370ac8..3e7267fce06 100644
--- a/src/mongo/db/catalog/collection.h
+++ b/src/mongo/db/catalog/collection.h
@@ -524,8 +524,24 @@ public:
virtual bool isTemporary() const = 0;
/**
- * Returns true if this collection is clustered on _id values. That is, its RecordIds are _id
- * values and has no separate _id index.
+ * Returns true if the time-series collection may have dates outside the standard range (roughly
+ * 1970-2038). The value may be updated in the background by another thread between calls, even
+ * if the caller holds a lock on the collection. The value may only transition from false to
+ * true.
+ */
+ virtual bool getRequiresTimeseriesExtendedRangeSupport() const = 0;
+
+ /**
+ * Sets the in-memory flag for this collection. This value can be retrieved by
+ * 'getRequiresTimeseriesExtendedRangeSupport'.
+ *
+ * Throws if this is not a time-series collection.
+ */
+ virtual void setRequiresTimeseriesExtendedRangeSupport(OperationContext* opCtx) const = 0;
+
+ /*
+ * Returns true if this collection is clustered. That is, its RecordIds store the value of the
+ * cluster key. If the collection is clustered on _id, there is no separate _id index.
*/
virtual bool isClustered() const = 0;
diff --git a/src/mongo/db/catalog/collection_impl.cpp b/src/mongo/db/catalog/collection_impl.cpp
index bcfe8cc156f..01e0282d150 100644
--- a/src/mongo/db/catalog/collection_impl.cpp
+++ b/src/mongo/db/catalog/collection_impl.cpp
@@ -39,6 +39,7 @@
#include "mongo/bson/ordering.h"
#include "mongo/bson/simple_bsonelement_comparator.h"
#include "mongo/bson/simple_bsonobj_comparator.h"
+#include "mongo/db/catalog/catalog_stats.h"
#include "mongo/db/catalog/collection_catalog.h"
#include "mongo/db/catalog/collection_options.h"
#include "mongo/db/catalog/document_validation.h"
@@ -75,6 +76,7 @@
#include "mongo/db/storage/durable_catalog.h"
#include "mongo/db/storage/key_string.h"
#include "mongo/db/storage/record_store.h"
+#include "mongo/db/timeseries/timeseries_extended_range.h"
#include "mongo/db/transaction_participant.h"
#include "mongo/db/ttl_collection_cache.h"
#include "mongo/db/update/update_driver.h"
@@ -1397,6 +1399,29 @@ bool CollectionImpl::isTemporary() const {
return _metadata->options.temp;
}
+bool CollectionImpl::getRequiresTimeseriesExtendedRangeSupport() const {
+ return _shared->_requiresTimeseriesExtendedRangeSupport.load();
+}
+
+void CollectionImpl::setRequiresTimeseriesExtendedRangeSupport(OperationContext* opCtx) const {
+ uassert(6679401, "This is not a time-series collection", _metadata->options.timeseries);
+
+ bool expected = false;
+ bool set = _shared->_requiresTimeseriesExtendedRangeSupport.compareAndSwap(&expected, true);
+ if (set) {
+ catalog_stats::requiresTimeseriesExtendedRangeSupport.fetchAndAdd(1);
+ if (!timeseries::collectionHasTimeIndex(opCtx, *this)) {
+ LOGV2_WARNING(
+ 6679402,
+ "Time-series collection contains dates outside the standard range. Some query "
+ "optimizations may be disabled. Please consider building an index on timeField to "
+ "re-enable them.",
+ "nss"_attr = ns().getTimeseriesViewNamespace(),
+ "timeField"_attr = _metadata->options.timeseries->getTimeField());
+ }
+ }
+}
+
bool CollectionImpl::isClustered() const {
return _clustered;
}
diff --git a/src/mongo/db/catalog/collection_impl.h b/src/mongo/db/catalog/collection_impl.h
index 5abeac1d6e5..8828e9611d9 100644
--- a/src/mongo/db/catalog/collection_impl.h
+++ b/src/mongo/db/catalog/collection_impl.h
@@ -317,6 +317,9 @@ public:
bool isTemporary() const final;
+ bool getRequiresTimeseriesExtendedRangeSupport() const final;
+ void setRequiresTimeseriesExtendedRangeSupport(OperationContext* opCtx) const final;
+
bool isClustered() const final;
void updateClusteredIndexTTLSetting(OperationContext* opCtx,
boost::optional<int64_t> expireAfterSeconds) final;
@@ -547,6 +550,18 @@ private:
AtomicWord<bool> _committed{true};
+ // Time-series collections are allowed to contain measurements with arbitrary dates;
+ // however, many of our query optimizations only work properly with dates that can be stored
+ // as an offset in seconds from the Unix epoch within 31 bits (roughly 1970-2038). When this
+ // flag is set to true, these optimizations will be disabled. It must be set to true if the
+ // collection contains any measurements with dates outside this normal range.
+ //
+ // This is set from the write path where we only hold an IX lock, so we want to be able to
+ // set it from a const method on the Collection. In order to do this, we need to make it
+ // mutable. Given that the value may only transition from false to true, but never back
+ // again, and that we store and retrieve it atomically, this should be safe.
+ mutable AtomicWord<bool> _requiresTimeseriesExtendedRangeSupport{false};
+
// Capped information.
const bool _isCapped;
const long long _cappedMaxDocs;
diff --git a/src/mongo/db/catalog/collection_mock.h b/src/mongo/db/catalog/collection_mock.h
index 862d75012d5..a44edb479d5 100644
--- a/src/mongo/db/catalog/collection_mock.h
+++ b/src/mongo/db/catalog/collection_mock.h
@@ -243,6 +243,14 @@ public:
std::abort();
}
+ bool getRequiresTimeseriesExtendedRangeSupport() const {
+ MONGO_UNREACHABLE;
+ }
+
+ void setRequiresTimeseriesExtendedRangeSupport(OperationContext* opCtx) const {
+ MONGO_UNREACHABLE;
+ }
+
bool isClustered() const {
std::abort();
}
diff --git a/src/mongo/db/op_observer/SConscript b/src/mongo/db/op_observer/SConscript
new file mode 100644
index 00000000000..36cb605336f
--- /dev/null
+++ b/src/mongo/db/op_observer/SConscript
@@ -0,0 +1,122 @@
+# -*- mode: python -*-
+
+Import("env")
+
+env = env.Clone()
+
+env.Library(
+ target="op_observer",
+ source=[
+ "op_observer.cpp",
+ ],
+ LIBDEPS=[
+ '$BUILD_DIR/mongo/base',
+ ],
+)
+
+env.Library(
+ target="op_observer_util",
+ source=[
+ "op_observer_util.cpp",
+ ],
+ LIBDEPS=[
+ '$BUILD_DIR/mongo/base',
+ ],
+ LIBDEPS_PRIVATE=[
+ '$BUILD_DIR/mongo/db/bson/dotted_path_support',
+ '$BUILD_DIR/mongo/db/catalog/collection_options',
+ '$BUILD_DIR/mongo/db/index/index_descriptor',
+ '$BUILD_DIR/mongo/db/s/sharding_api_d',
+ ],
+)
+
+env.Library(
+ target='oplog_writer_impl',
+ source=[
+ 'oplog_writer_impl.cpp',
+ ],
+ LIBDEPS_PRIVATE=[
+ '$BUILD_DIR/mongo/base',
+ '$BUILD_DIR/mongo/db/repl/oplog',
+ ],
+)
+
+env.Library(
+ target='oplog_writer_transaction_proxy',
+ source=[
+ 'oplog_writer_transaction_proxy.cpp',
+ ],
+ LIBDEPS_PRIVATE=[
+ '$BUILD_DIR/mongo/base',
+ ],
+)
+
+env.Library(
+ target="op_observer_impl",
+ source=[
+ "op_observer_impl.cpp",
+ ],
+ LIBDEPS=[
+ '$BUILD_DIR/mongo/db/change_stream_pre_images_collection_manager',
+ ],
+ LIBDEPS_PRIVATE=[
+ '$BUILD_DIR/mongo/base',
+ '$BUILD_DIR/mongo/db/batched_write_context',
+ '$BUILD_DIR/mongo/db/catalog/collection_catalog',
+ '$BUILD_DIR/mongo/db/catalog/collection_options',
+ '$BUILD_DIR/mongo/db/catalog/commit_quorum_options',
+ '$BUILD_DIR/mongo/db/catalog/database_holder',
+ '$BUILD_DIR/mongo/db/catalog/import_collection_oplog_entry',
+ '$BUILD_DIR/mongo/db/commands/txn_cmd_request',
+ '$BUILD_DIR/mongo/db/concurrency/exception_util',
+ '$BUILD_DIR/mongo/db/dbhelpers',
+ '$BUILD_DIR/mongo/db/internal_transactions_feature_flag',
+ '$BUILD_DIR/mongo/db/multitenancy',
+ '$BUILD_DIR/mongo/db/pipeline/change_stream_preimage',
+ '$BUILD_DIR/mongo/db/read_write_concern_defaults',
+ '$BUILD_DIR/mongo/db/repl/image_collection_entry',
+ '$BUILD_DIR/mongo/db/repl/repl_server_parameters',
+ '$BUILD_DIR/mongo/db/repl/tenant_migration_access_blocker',
+ '$BUILD_DIR/mongo/db/s/sharding_api_d',
+ '$BUILD_DIR/mongo/db/server_feature_flags',
+ '$BUILD_DIR/mongo/db/session/session_catalog',
+ '$BUILD_DIR/mongo/db/timeseries/bucket_catalog',
+ '$BUILD_DIR/mongo/db/timeseries/timeseries_extended_range',
+ '$BUILD_DIR/mongo/db/transaction/transaction',
+ '$BUILD_DIR/mongo/db/views/views_mongod',
+ '$BUILD_DIR/mongo/s/coreshard',
+ '$BUILD_DIR/mongo/s/grid',
+ 'op_observer',
+ 'op_observer_util',
+ ],
+)
+
+env.Library(
+ target="fcv_op_observer",
+ source=[
+ "fcv_op_observer.cpp",
+ ],
+ LIBDEPS_PRIVATE=[
+ '$BUILD_DIR/mongo/base',
+ '$BUILD_DIR/mongo/db/commands/mongod_fcv',
+ '$BUILD_DIR/mongo/db/session/kill_sessions_local',
+ '$BUILD_DIR/mongo/executor/egress_tag_closer_manager',
+ 'op_observer',
+ 'op_observer_util',
+ ],
+)
+
+env.Library(
+ target="user_write_block_mode_op_observer",
+ source=[
+ "user_write_block_mode_op_observer.cpp",
+ ],
+ LIBDEPS=[
+ '$BUILD_DIR/mongo/base',
+ 'op_observer',
+ ],
+ LIBDEPS_PRIVATE=[
+ '$BUILD_DIR/mongo/db/s/sharding_api_d',
+ '$BUILD_DIR/mongo/db/s/user_writes_recoverable_critical_section',
+ ],
+)
diff --git a/src/mongo/db/op_observer_impl.cpp b/src/mongo/db/op_observer_impl.cpp
index 0a92202a170..0e9bc43b7d5 100644
--- a/src/mongo/db/op_observer_impl.cpp
+++ b/src/mongo/db/op_observer_impl.cpp
@@ -65,6 +65,7 @@
#include "mongo/db/server_options.h"
#include "mongo/db/session_catalog_mongod.h"
#include "mongo/db/timeseries/bucket_catalog.h"
+#include "mongo/db/timeseries/timeseries_extended_range.h"
#include "mongo/db/transaction_participant.h"
#include "mongo/db/transaction_participant_gen.h"
#include "mongo/db/views/durable_view_catalog.h"
@@ -610,6 +611,27 @@ void OpObserverImpl::onInserts(OperationContext* opCtx,
}
});
}
+ } else if (nss.isTimeseriesBucketsCollection()) {
+ // Check if the bucket _id is sourced from a date outside the standard range. If our writes
+ // end up erroring out or getting rolled back, then this flag will stay set. This is okay
+ // though, as it only disables some query optimizations and won't result in any correctness
+ // issues if the flag is set when it doesn't need to be (as opposed to NOT being set when it
+ // DOES need to be -- that will cause correctness issues). Additionally, if the user tried
+ // to insert measurements with dates outside the standard range, chances are they will do so
+ // again, and we will have only set the flag a little early.
+ invariant(opCtx->lockState()->isCollectionLockedForMode(nss, MODE_IX));
+ auto bucketsColl =
+ CollectionCatalog::get(opCtx)->lookupCollectionByNamespaceForRead(opCtx, nss);
+ tassert(6905201, "Could not find collection for write", bucketsColl);
+ auto timeSeriesOptions = bucketsColl->getTimeseriesOptions();
+ if (timeSeriesOptions.has_value()) {
+ if (auto currentSetting = bucketsColl->getRequiresTimeseriesExtendedRangeSupport();
+ !currentSetting &&
+ timeseries::bucketsHaveDateOutsideStandardRange(
+ timeSeriesOptions.value(), first, last)) {
+ bucketsColl->setRequiresTimeseriesExtendedRangeSupport(opCtx);
+ }
+ }
}
}
diff --git a/src/mongo/db/startup_recovery.cpp b/src/mongo/db/startup_recovery.cpp
index 76af488f216..553306986ba 100644
--- a/src/mongo/db/startup_recovery.cpp
+++ b/src/mongo/db/startup_recovery.cpp
@@ -54,6 +54,7 @@
#include "mongo/db/repl_set_member_in_standalone_mode.h"
#include "mongo/db/server_options.h"
#include "mongo/db/storage/storage_repair_observer.h"
+#include "mongo/db/timeseries/timeseries_extended_range.h"
#include "mongo/logv2/log.h"
#include "mongo/util/exit.h"
#include "mongo/util/fail_point.h"
@@ -248,6 +249,11 @@ Status ensureCollectionProperties(OperationContext* opCtx,
return downgradeError;
}
}
+
+ if (coll->getTimeseriesOptions() &&
+ timeseries::collectionMayRequireExtendedRangeSupport(opCtx, coll)) {
+ coll->setRequiresTimeseriesExtendedRangeSupport(opCtx);
+ }
}
return Status::OK();
}
diff --git a/src/mongo/db/timeseries/SConscript b/src/mongo/db/timeseries/SConscript
index 65762c7a0c8..02107f1926a 100644
--- a/src/mongo/db/timeseries/SConscript
+++ b/src/mongo/db/timeseries/SConscript
@@ -101,11 +101,24 @@ env.Library(
],
)
+env.Library(
+ target='timeseries_extended_range',
+ source=[
+ 'timeseries_extended_range.cpp',
+ ],
+ LIBDEPS_PRIVATE=[
+ '$BUILD_DIR/mongo/base',
+ '$BUILD_DIR/mongo/db/catalog/index_catalog',
+ 'timeseries_options',
+ ],
+)
+
env.CppUnitTest(
target='db_timeseries_test',
source=[
'bucket_catalog_test.cpp',
'minmax_test.cpp',
+ 'timeseries_extended_range_test.cpp',
'timeseries_index_schema_conversion_functions_test.cpp',
'timeseries_update_delete_util_test.cpp',
'timeseries_options_test.cpp'
@@ -113,6 +126,8 @@ env.CppUnitTest(
LIBDEPS=[
'$BUILD_DIR/mongo/db/catalog/catalog_test_fixture',
'bucket_catalog',
+ 'bucket_compression',
+ 'timeseries_extended_range',
'timeseries_index_schema_conversion_functions',
'timeseries_options',
'timeseries_update_delete_util',
diff --git a/src/mongo/db/timeseries/timeseries_extended_range.cpp b/src/mongo/db/timeseries/timeseries_extended_range.cpp
new file mode 100644
index 00000000000..b6ceb6e8158
--- /dev/null
+++ b/src/mongo/db/timeseries/timeseries_extended_range.cpp
@@ -0,0 +1,115 @@
+/**
+ * Copyright (C) 2022-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/db/timeseries/timeseries_extended_range.h"
+
+#include "mongo/db/timeseries/timeseries_constants.h"
+
+namespace mongo::timeseries {
+
+bool dateOutsideStandardRange(Date_t date) {
+ constexpr long long kMaxNormalRangeTimestamp = ((1LL << 31) - 1);
+ long long timeSeconds = durationCount<Seconds>(date.toDurationSinceEpoch());
+ return timeSeconds < 0 || timeSeconds > kMaxNormalRangeTimestamp;
+}
+
+bool bucketsHaveDateOutsideStandardRange(const TimeseriesOptions& options,
+ std::vector<InsertStatement>::const_iterator first,
+ std::vector<InsertStatement>::const_iterator last) {
+ return std::any_of(first, last, [&](const InsertStatement& stmt) -> bool {
+ auto controlElem = stmt.doc.getField(timeseries::kBucketControlFieldName);
+ uassert(6781400,
+ "Time series bucket document is missing 'control' field",
+ controlElem.isABSONObj());
+ auto minElem = controlElem.Obj().getField(timeseries::kBucketControlMinFieldName);
+ uassert(6781401,
+ "Time series bucket document is missing 'control.min' field",
+ minElem.isABSONObj());
+ auto timeElem = minElem.Obj().getField(options.getTimeField());
+ uassert(6781402,
+ "Time series bucket document does not have a valid min time element",
+ timeElem && BSONType::Date == timeElem.type());
+
+ auto date = timeElem.Date();
+ return dateOutsideStandardRange(date);
+ });
+}
+
+bool collectionMayRequireExtendedRangeSupport(OperationContext* opCtx,
+ const CollectionPtr& collection) {
+ bool requiresExtendedRangeSupport = false;
+
+ // We use a heuristic here to perform a check as quickly as possible and get the correct answer
+ // with high probability. The rough idea is that if a user has dates outside the standard range
+ // from 1970-2038, they most likely have some dates near either end of that range, i.e. between
+ // 1902-1969 or 2039-2106. Given this assumption, we can assume that at least one document in
+ // the collection should have the high bit of the timestamp portion of the OID set. If such a
+ // document exists, then the maximum OID will have this bit set. So we can just check the last
+ // document in the record store and test this high bit of it's _id.
+
+ auto* rs = collection->getRecordStore();
+ auto cursor = rs->getCursor(opCtx, /* forward */ false);
+ if (auto record = cursor->next()) {
+ const auto& obj = record->data.toBson();
+ OID id = obj.getField(kBucketIdFieldName).OID();
+
+ uint8_t highDateBits = id.view().read<uint8_t>(0);
+ if (highDateBits & 0x80) {
+ requiresExtendedRangeSupport = true;
+ }
+ }
+
+ return requiresExtendedRangeSupport;
+}
+
+bool collectionHasTimeIndex(OperationContext* opCtx, const Collection& collection) {
+ auto tsOptions = collection.getTimeseriesOptions();
+ invariant(tsOptions);
+ std::string controlMinTimeField = timeseries::kControlMinFieldNamePrefix.toString();
+ controlMinTimeField.append(tsOptions->getTimeField().toString());
+ std::string controlMaxTimeField = timeseries::kControlMaxFieldNamePrefix.toString();
+ controlMaxTimeField.append(tsOptions->getTimeField().toString());
+
+ auto indexCatalog = collection.getIndexCatalog();
+ // The IndexIterator is initialized lazily, so the first call to 'next' positions it to the
+ // first entry.
+ for (auto it = indexCatalog->getIndexIterator(opCtx, false); it->more();) {
+ auto index = it->next();
+ auto desc = index->descriptor();
+ auto pattern = desc->keyPattern();
+ auto keyIt = pattern.begin();
+ StringData field = keyIt->fieldNameStringData();
+ if (field == controlMinTimeField || field == controlMaxTimeField) {
+ return true;
+ }
+ }
+ return false;
+}
+
+} // namespace mongo::timeseries
diff --git a/src/mongo/db/timeseries/timeseries_extended_range.h b/src/mongo/db/timeseries/timeseries_extended_range.h
new file mode 100644
index 00000000000..f37ac278c26
--- /dev/null
+++ b/src/mongo/db/timeseries/timeseries_extended_range.h
@@ -0,0 +1,67 @@
+/**
+ * Copyright (C) 2022-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.
+ */
+
+#pragma once
+
+#include "mongo/bson/bsonobj.h"
+#include "mongo/db/catalog/collection.h"
+#include "mongo/db/timeseries/timeseries_gen.h"
+
+namespace mongo::timeseries {
+
+/**
+ * Determines whether the given 'date' is outside the standard supported range and requires extended
+ * range support. Standard range dates can be expressed as a number of seconds since the Unix epoch
+ * in 31 unsigned bits.
+ */
+bool dateOutsideStandardRange(Date_t date);
+
+/**
+ * Determines whether any of the given buckets have control.min.timeField values that lie outside
+ * the standard range.
+ */
+bool bucketsHaveDateOutsideStandardRange(const TimeseriesOptions& options,
+ std::vector<InsertStatement>::const_iterator first,
+ std::vector<InsertStatement>::const_iterator last);
+
+/**
+ * Uses a heuristic to determine whether a given time-series collection may contain measurements
+ * with dates that fall outside the standard range.
+ */
+bool collectionMayRequireExtendedRangeSupport(OperationContext* opCtx,
+ const CollectionPtr& collection);
+
+/**
+ * Determines whether a time-series collection has an index primarily ordered by a time field. This
+ * excludes the clustered index, and is testing specifically if an index's key pattern's first field
+ * is either control.min.<timeField> or control.max.<timeField>.
+ */
+bool collectionHasTimeIndex(OperationContext* opCtx, const Collection& collection);
+
+} // namespace mongo::timeseries
diff --git a/src/mongo/db/timeseries/timeseries_extended_range_test.cpp b/src/mongo/db/timeseries/timeseries_extended_range_test.cpp
new file mode 100644
index 00000000000..a6392aabd1b
--- /dev/null
+++ b/src/mongo/db/timeseries/timeseries_extended_range_test.cpp
@@ -0,0 +1,98 @@
+/**
+ * Copyright (C) 2022-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/bson/json.h"
+#include "mongo/db/timeseries/timeseries_extended_range.h"
+#include "mongo/unittest/bson_test_util.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo {
+namespace {
+
+TEST(TimeseriesExtendedRangeSupport, DateOutsideStandardRange) {
+ Date_t minStandard = Date_t::fromDurationSinceEpoch(Seconds(0));
+ Date_t maxStandard = Date_t::fromDurationSinceEpoch(Seconds((1LL << 31) - 1));
+
+ Date_t extendedLow = Date_t::fromDurationSinceEpoch(Seconds(-1));
+ Date_t extendedHigh = Date_t::fromDurationSinceEpoch(Seconds(1LL << 31));
+
+ ASSERT_FALSE(timeseries::dateOutsideStandardRange(minStandard));
+ ASSERT_FALSE(timeseries::dateOutsideStandardRange(maxStandard));
+
+ ASSERT_TRUE(timeseries::dateOutsideStandardRange(extendedLow));
+ ASSERT_TRUE(timeseries::dateOutsideStandardRange(extendedHigh));
+}
+
+TEST(TimeseriesExtendedRangeSupport, BucketsHaveDateOutsideStandardRange) {
+ TimeseriesOptions options;
+ options.setTimeField("time"_sd);
+
+ std::vector<InsertStatement> standardRange = {
+ {0,
+ mongo::fromjson(
+ R"({"control": {"min": {"time": {"$date": "1970-01-01T00:00:00.000Z"}}}})")},
+ {1,
+ mongo::fromjson(
+ R"({"control": {"min": {"time": {"$date": "2000-01-01T00:00:00.000Z"}}}})")},
+ {2,
+ mongo::fromjson(
+ R"({"control": {"min": {"time": {"$date": "2038-01-01T00:00:00.000Z"}}}})")},
+ };
+
+ std::vector<InsertStatement> extendedRangeLow = {
+ // Dates before Unix epoch have to be hard-coded as seconds-offset rather than date strings
+ {3,
+ // -((1 << 31) + 1) seconds
+ mongo::fromjson(R"({"control": {"min": {"time": {"$date": -2147483649000}}}})")},
+ {4, mongo::fromjson(R"({"control": {"min": {"time": {"$date": -1000}}}})")},
+ };
+
+ std::vector<InsertStatement> extendedRangeHigh = {
+ {5,
+ mongo::fromjson(
+ R"({"control": {"min": {"time": {"$date": "2039-01-01T00:00:00.000Z"}}}})")},
+ {6,
+ mongo::fromjson(
+ R"({"control": {"min": {"time": {"$date": "2110-01-01T00:00:00.000Z"}}}})")},
+ };
+
+ ASSERT_FALSE(timeseries::bucketsHaveDateOutsideStandardRange(
+ options, standardRange.begin(), standardRange.end()));
+ ASSERT_TRUE(timeseries::bucketsHaveDateOutsideStandardRange(
+ options, extendedRangeLow.begin(), extendedRangeLow.end()));
+ ASSERT_TRUE(timeseries::bucketsHaveDateOutsideStandardRange(
+ options, extendedRangeHigh.begin(), extendedRangeHigh.end()));
+
+ std::vector<InsertStatement> mixed = {standardRange[0], standardRange[1], extendedRangeLow[0]};
+ ASSERT_TRUE(
+ timeseries::bucketsHaveDateOutsideStandardRange(options, mixed.begin(), mixed.end()));
+}
+
+} // namespace
+} // namespace mongo