diff options
-rw-r--r-- | jstests/noPassthroughWithMongod/time_series_create.js | 25 | ||||
-rw-r--r-- | src/mongo/db/catalog/collection_impl.cpp | 4 | ||||
-rw-r--r-- | src/mongo/db/catalog/create_collection.cpp | 47 |
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); |