From c145c91dfa060d819406a599e21d573782291ad4 Mon Sep 17 00:00:00 2001 From: Eric Adum Date: Thu, 8 Jul 2021 11:58:30 -0400 Subject: [PATCH] feat(NODE-3095): add timeseries options to db.createCollection (#2878) --- src/index.ts | 5 +- src/operations/create_collection.ts | 14 + .../collection_management_spec.test.js | 21 ++ .../unified-spec-runner/operations.ts | 7 +- .../timeseries-collection.json | 255 ++++++++++++++++++ .../timeseries-collection.yml | 129 +++++++++ 6 files changed, 428 insertions(+), 3 deletions(-) create mode 100644 test/functional/collection_management_spec.test.js create mode 100644 test/spec/collection-management/timeseries-collection.json create mode 100644 test/spec/collection-management/timeseries-collection.yml diff --git a/src/index.ts b/src/index.ts index 7c1970c1e8..901323e972 100644 --- a/src/index.ts +++ b/src/index.ts @@ -244,7 +244,10 @@ export type { export type { IndexInformationOptions } from './operations/common_functions'; export type { CountOptions } from './operations/count'; export type { CountDocumentsOptions } from './operations/count_documents'; -export type { CreateCollectionOptions } from './operations/create_collection'; +export type { + CreateCollectionOptions, + TimeSeriesCollectionOptions +} from './operations/create_collection'; export type { DeleteOptions, DeleteResult, DeleteStatement } from './operations/delete'; export type { DistinctOptions } from './operations/distinct'; export type { DropCollectionOptions, DropDatabaseOptions } from './operations/drop'; diff --git a/src/operations/create_collection.ts b/src/operations/create_collection.ts index e795d00949..86291c646a 100644 --- a/src/operations/create_collection.ts +++ b/src/operations/create_collection.ts @@ -30,6 +30,16 @@ const ILLEGAL_COMMAND_FIELDS = new Set([ 'ignoreUndefined' ]); +/** @public + * Configuration options for timeseries collections + * @see https://docs.mongodb.com/manual/core/timeseries-collections/ + */ +export interface TimeSeriesCollectionOptions extends Document { + timeField: string; + metaField?: string; + granularity?: string; +} + /** @public */ export interface CreateCollectionOptions extends CommandOperationOptions { /** Returns an error if the collection does not exist */ @@ -60,6 +70,10 @@ export interface CreateCollectionOptions extends CommandOperationOptions { pipeline?: Document[]; /** A primary key factory function for generation of custom _id keys. */ pkFactory?: PkFactory; + /** A document specifying configuration options for timeseries collections. */ + timeseries?: TimeSeriesCollectionOptions; + /** The number of seconds after which a document in a timeseries collection expires. */ + expireAfterSeconds?: number; } /** @internal */ diff --git a/test/functional/collection_management_spec.test.js b/test/functional/collection_management_spec.test.js new file mode 100644 index 0000000000..0587615931 --- /dev/null +++ b/test/functional/collection_management_spec.test.js @@ -0,0 +1,21 @@ +'use strict'; + +const { expect } = require('chai'); +const { loadSpecTests } = require('../spec/index'); +const { runUnifiedTest } = require('./unified-spec-runner/runner'); + +describe('Collection management unified spec tests', function () { + for (const collectionManagementTest of loadSpecTests('collection-management')) { + expect(collectionManagementTest).to.exist; + context(String(collectionManagementTest.description), function () { + for (const test of collectionManagementTest.tests) { + it(String(test.description), { + metadata: { sessions: { skipLeakTests: true } }, + test: async function () { + await runUnifiedTest(this, collectionManagementTest, test); + } + }); + } + }); + } +}); diff --git a/test/functional/unified-spec-runner/operations.ts b/test/functional/unified-spec-runner/operations.ts index b5e82c960b..26fcd8df83 100644 --- a/test/functional/unified-spec-runner/operations.ts +++ b/test/functional/unified-spec-runner/operations.ts @@ -212,8 +212,11 @@ operations.set('createChangeStream', async ({ entities, operation }) => { operations.set('createCollection', async ({ entities, operation }) => { const db = entities.getEntity('db', operation.object); - const session = entities.getEntity('session', operation.arguments.session, false); - await db.createCollection(operation.arguments.collection, { session }); + const { session, collection, ...opts } = operation.arguments; + await db.createCollection(collection, { + session: entities.getEntity('session', session, false), + ...opts + }); }); operations.set('createIndex', async ({ entities, operation }) => { diff --git a/test/spec/collection-management/timeseries-collection.json b/test/spec/collection-management/timeseries-collection.json new file mode 100644 index 0000000000..b5638fd36e --- /dev/null +++ b/test/spec/collection-management/timeseries-collection.json @@ -0,0 +1,255 @@ +{ + "description": "timeseries-collection", + "schemaVersion": "1.0", + "runOnRequirements": [ + { + "minServerVersion": "5.0" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "ts-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "ts-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "createCollection with all options", + "operations": [ + { + "name": "dropCollection", + "object": "database0", + "arguments": { + "collection": "test" + } + }, + { + "name": "createCollection", + "object": "database0", + "arguments": { + "collection": "test", + "expireAfterSeconds": 604800, + "timeseries": { + "timeField": "time", + "metaField": "meta", + "granularity": "minutes" + } + } + }, + { + "name": "assertCollectionExists", + "object": "testRunner", + "arguments": { + "databaseName": "ts-tests", + "collectionName": "test" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "drop": "test" + }, + "databaseName": "ts-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "create": "test", + "expireAfterSeconds": 604800, + "timeseries": { + "timeField": "time", + "metaField": "meta", + "granularity": "minutes" + } + }, + "databaseName": "ts-tests" + } + } + ] + } + ] + }, + { + "description": "insertMany with duplicate ids", + "operations": [ + { + "name": "dropCollection", + "object": "database0", + "arguments": { + "collection": "test" + } + }, + { + "name": "createCollection", + "object": "database0", + "arguments": { + "collection": "test", + "expireAfterSeconds": 604800, + "timeseries": { + "timeField": "time", + "metaField": "meta", + "granularity": "minutes" + } + } + }, + { + "name": "assertCollectionExists", + "object": "testRunner", + "arguments": { + "databaseName": "ts-tests", + "collectionName": "test" + } + }, + { + "name": "insertMany", + "object": "collection0", + "arguments": { + "documents": [ + { + "_id": 1, + "time": { + "$date": { + "$numberLong": "1552949630482" + } + } + }, + { + "_id": 1, + "time": { + "$date": { + "$numberLong": "1552949630483" + } + } + } + ] + } + }, + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": {}, + "sort": { + "time": 1 + } + }, + "expectResult": [ + { + "_id": 1, + "time": { + "$date": { + "$numberLong": "1552949630482" + } + } + }, + { + "_id": 1, + "time": { + "$date": { + "$numberLong": "1552949630483" + } + } + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "drop": "test" + }, + "databaseName": "ts-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "create": "test", + "expireAfterSeconds": 604800, + "timeseries": { + "timeField": "time", + "metaField": "meta", + "granularity": "minutes" + } + }, + "databaseName": "ts-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1, + "time": { + "$date": { + "$numberLong": "1552949630482" + } + } + }, + { + "_id": 1, + "time": { + "$date": { + "$numberLong": "1552949630483" + } + } + } + ] + } + } + }, + { + "commandStartedEvent": { + "command": { + "find": "test", + "filter": {}, + "sort": { + "time": 1 + } + }, + "databaseName": "ts-tests" + } + } + ] + } + ] + } + ] +} diff --git a/test/spec/collection-management/timeseries-collection.yml b/test/spec/collection-management/timeseries-collection.yml new file mode 100644 index 0000000000..cbc09b34ca --- /dev/null +++ b/test/spec/collection-management/timeseries-collection.yml @@ -0,0 +1,129 @@ +description: "timeseries-collection" + +schemaVersion: "1.0" + +runOnRequirements: + - minServerVersion: "5.0" + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name ts-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name test + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: [] + +tests: + - description: "createCollection with all options" + operations: + - name: dropCollection + object: *database0 + arguments: + collection: *collection0Name + - name: createCollection + object: *database0 + arguments: + collection: *collection0Name + # expireAfterSeconds should be an int64 (as it is stored on the server). + expireAfterSeconds: 604800 + timeseries: ×eries0 + timeField: "time" + metaField: "meta" + granularity: "minutes" + - name: assertCollectionExists + object: testRunner + arguments: + databaseName: *database0Name + collectionName: *collection0Name + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + drop: *collection0Name + databaseName: *database0Name + - commandStartedEvent: + command: + create: *collection0Name + expireAfterSeconds: 604800 + timeseries: *timeseries0 + databaseName: *database0Name + + # Unlike regular collections, time-series collections allow duplicate ids. + - description: "insertMany with duplicate ids" + operations: + - name: dropCollection + object: *database0 + arguments: + collection: *collection0Name + - name: createCollection + object: *database0 + arguments: + collection: *collection0Name + # expireAfterSeconds should be an int64 (as it is stored on the server). + expireAfterSeconds: 604800 + timeseries: *timeseries0 + - name: assertCollectionExists + object: testRunner + arguments: + databaseName: *database0Name + collectionName: *collection0Name + - name: insertMany + object: *collection0 + arguments: + documents: &docs + - { + _id: 1, + time: { + $date: { + $numberLong: "1552949630482" + } + } + } + - { + _id: 1, + time: { + $date: { + $numberLong: "1552949630483" + } + } + } + - name: find + object: *collection0 + arguments: + filter: {} + sort: { time: 1 } + expectResult: *docs + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + drop: *collection0Name + databaseName: *database0Name + - commandStartedEvent: + command: + create: *collection0Name + expireAfterSeconds: 604800 + timeseries: *timeseries0 + databaseName: *database0Name + - commandStartedEvent: + command: + insert: *collection0Name + documents: *docs + - commandStartedEvent: + command: + find: *collection0Name + filter: {} + sort: { time: 1 } + databaseName: *database0Name