summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenety Goh <benety@mongodb.com>2020-11-21 09:53:12 -0500
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-11-21 15:12:42 +0000
commit40fb4677db516968c8560b2e7db9c587b887d79a (patch)
tree83f2ddffa9192b048745f31b5263513d529bdf11
parent8a8435ba30e2ba76e09ea2992b8dfb3f64246702 (diff)
downloadmongo-40fb4677db516968c8560b2e7db9c587b887d79a.tar.gz
SERVER-52529 enable schema validation on time-series bucket collections
-rw-r--r--jstests/noPassthroughWithMongod/time_series_create.js25
-rw-r--r--src/mongo/db/catalog/collection_impl.cpp4
-rw-r--r--src/mongo/db/catalog/create_collection.cpp47
3 files changed, 66 insertions, 10 deletions
diff --git a/jstests/noPassthroughWithMongod/time_series_create.js b/jstests/noPassthroughWithMongod/time_series_create.js
index bcb98991bfb..72a202dc49c 100644
--- a/jstests/noPassthroughWithMongod/time_series_create.js
+++ b/jstests/noPassthroughWithMongod/time_series_create.js
@@ -130,19 +130,26 @@ testTimeseriesNamespaceExists((testDB, collName) => {
const bucketsColl = testDB.getCollection('system.buckets.' + coll.getName());
assert.commandWorked(bucketsColl.insert(
{control: {version: 1, min: {time: ISODate()}, max: {time: ISODate()}}, data: {}}));
- assert.commandWorked(bucketsColl.insert({
+ assert.commandFailedWithCode(bucketsColl.insert({
control: {version: 'not a number', min: {time: ISODate()}, max: {time: ISODate()}},
data: {}
- }));
- assert.commandWorked(bucketsColl.insert(
- {control: {version: 1, min: {time: 'not a date'}, max: {time: ISODate()}}, data: {}}));
- assert.commandWorked(bucketsColl.insert(
- {control: {version: 1, min: {time: ISODate()}, max: {time: 'not a date'}}, data: {}}));
- assert.commandWorked(bucketsColl.insert({
+ }),
+ ErrorCodes.DocumentValidationFailure);
+ assert.commandFailedWithCode(
+ bucketsColl.insert(
+ {control: {version: 1, min: {time: 'not a date'}, max: {time: ISODate()}}, data: {}}),
+ ErrorCodes.DocumentValidationFailure);
+ assert.commandFailedWithCode(
+ bucketsColl.insert(
+ {control: {version: 1, min: {time: ISODate()}, max: {time: 'not a date'}}, data: {}}),
+ ErrorCodes.DocumentValidationFailure);
+ assert.commandFailedWithCode(bucketsColl.insert({
control: {version: 1, min: {time: ISODate()}, max: {time: ISODate()}},
data: 'not an object'
- }));
- assert.commandWorked(bucketsColl.insert({invalid_bucket_field: 1}));
+ }),
+ ErrorCodes.DocumentValidationFailure);
+ assert.commandFailedWithCode(bucketsColl.insert({invalid_bucket_field: 1}),
+ ErrorCodes.DocumentValidationFailure);
assert.commandWorked(testDB.runCommand({drop: coll.getName(), writeConcern: {w: "majority"}}));
}
})();
diff --git a/src/mongo/db/catalog/collection_impl.cpp b/src/mongo/db/catalog/collection_impl.cpp
index 7dbb969842b..a7197aedf57 100644
--- a/src/mongo/db/catalog/collection_impl.cpp
+++ b/src/mongo/db/catalog/collection_impl.cpp
@@ -213,6 +213,10 @@ Status checkValidatorCanBeUsedOnNs(const BSONObj& validator,
return Status::OK();
}
+ if (nss.isTimeseriesBucketsCollection()) {
+ return Status::OK();
+ }
+
if (nss.isSystem() && !nss.isDropPendingNamespace()) {
return {ErrorCodes::InvalidOptions,
str::stream() << "Document validators not allowed on system collection " << nss
diff --git a/src/mongo/db/catalog/create_collection.cpp b/src/mongo/db/catalog/create_collection.cpp
index ede682382e9..a69f55633b2 100644
--- a/src/mongo/db/catalog/create_collection.cpp
+++ b/src/mongo/db/catalog/create_collection.cpp
@@ -33,7 +33,10 @@
#include "mongo/db/catalog/create_collection.h"
+#include <fmt/printf.h>
+
#include "mongo/bson/bsonobj.h"
+#include "mongo/bson/json.h"
#include "mongo/db/catalog/collection_catalog.h"
#include "mongo/db/catalog/database_holder.h"
#include "mongo/db/catalog/index_key_validate.h"
@@ -208,8 +211,50 @@ Status _createTimeseries(OperationContext* opCtx,
Top::get(serviceContext).collectionDropped(bucketsNs);
});
+ CollectionOptions bucketsOptions;
+
+ // Set the validator option to a JSON schema enforcing constraints on bucket documents.
+ // This validation is only structural to prevent accidental corruption by users and cannot
+ // cover all constraints.
+ // Leave the validationLevel and validationAction to their strict/error defaults.
+ auto timeField = options.timeseries->getTimeField();
+ bucketsOptions.validator = fromjson(fmt::sprintf(R"(
+{
+ '$jsonSchema' : {
+ bsonType: 'object',
+ required: ['_id', 'control', 'data'],
+ properties: {
+ _id: {bsonType: 'objectId'},
+ control: {
+ bsonType: 'object',
+ required: ['version', 'min', 'max'],
+ properties: {
+ version: {bsonType: 'number'},
+ min: {
+ bsonType: 'object',
+ required: ['%s'],
+ properties: {'%s': {bsonType: 'date'}}
+ },
+ max: {
+ bsonType: 'object',
+ required: ['%s'],
+ properties: {'%s': {bsonType: 'date'}}
+ }
+ }
+ },
+ data: {bsonType: 'object'},
+ meta: {}
+ },
+ additionalProperties: false
+ }
+})",
+ timeField,
+ timeField,
+ timeField,
+ timeField));
+
// Create the buckets collection that will back the view.
- auto bucketsCollection = db->createCollection(opCtx, bucketsNs);
+ auto bucketsCollection = db->createCollection(opCtx, bucketsNs, bucketsOptions);
invariant(bucketsCollection,
str::stream() << "Failed to create buckets collection " << bucketsNs
<< " for time-series collection " << ns);