diff --git a/lib/connection.js b/lib/connection.js index a2769614de5..a7d93526815 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -20,6 +20,7 @@ const mongodb = require('mongodb'); const pkg = require('../package.json'); const utils = require('./utils'); const processConnectionOptions = require('./helpers/processConnectionOptions'); +const CreateCollectionsError = require('./error/createCollectionsError'); const arrayAtomicsSymbol = require('./helpers/symbols').arrayAtomicsSymbol; const sessionNewDocuments = require('./helpers/symbols').sessionNewDocuments; @@ -409,6 +410,42 @@ Connection.prototype.createCollection = async function createCollection(collecti return this.db.createCollection(collection, options); }; +/** + * Calls `createCollection()` on a models in a series. + * + * @method createCollections + * @param {Boolean} continueOnError When true, will continue to create collections and create a new error class for the collections that errored. + * @returns {Promise} + * @api public + */ + +Connection.prototype.createCollections = async function createCollections(options = {}) { + const result = {}; + const errorsMap = { }; + + const { continueOnError } = options; + delete options.continueOnError; + for (const model of Object.values(this.models)) { + try { + result[model.modelName] = await model.createCollection({}); + } catch (err) { + if (!continueOnError) { + errorsMap[model.modelName] = err; + break; + } else { + result[model.modelName] = err; + } + } + } + + if (!continueOnError && Object.keys(errorsMap).length) { + const message = Object.entries(errorsMap).map(([modelName, err]) => `${modelName}: ${err.message}`).join(', '); + const createCollectionsError = new CreateCollectionsError(message, errorsMap); + throw createCollectionsError; + } + return result; +}; + /** * _Requires MongoDB >= 3.6.0._ Starts a [MongoDB session](https://www.mongodb.com/docs/manual/release-notes/3.6/#client-sessions) * for benefits like causal consistency, [retryable writes](https://www.mongodb.com/docs/manual/core/retryable-writes/), diff --git a/lib/error/createCollectionsError.js b/lib/error/createCollectionsError.js new file mode 100644 index 00000000000..4b69cae617e --- /dev/null +++ b/lib/error/createCollectionsError.js @@ -0,0 +1,26 @@ +'use strict'; + +const MongooseError = require('./mongooseError'); + +/** + * createCollections Error constructor + * + * @param {String} message + * @param {String} errorsMap + * @inherits MongooseError + * @api private + */ + +class CreateCollectionsError extends MongooseError { + constructor(message, errorsMap) { + super(message); + this.errors = errorsMap; + } +} + +Object.defineProperty(CreateCollectionsError.prototype, 'name', { + value: 'CreateCollectionsError' +}); + +module.exports = CreateCollectionsError; + diff --git a/lib/model.js b/lib/model.js index 5a09efac493..1e552398ddf 100644 --- a/lib/model.js +++ b/lib/model.js @@ -1405,11 +1405,11 @@ Model.createCollection = async function createCollection(options) { try { await this.db.createCollection(this.$__collection.collectionName, options); } catch (err) { + if (err != null && (err.name !== 'MongoServerError' || err.code !== 48)) { throw err; } } - return this.$__collection; }; diff --git a/test/connection.test.js b/test/connection.test.js index a3689516d25..a2bf0a36af3 100644 --- a/test/connection.test.js +++ b/test/connection.test.js @@ -1529,4 +1529,22 @@ describe('connections:', function() { }); assert.deepEqual(m.connections.length, 0); }); + describe('createCollections()', function() { + it('should create collections for all models on the connection with the createCollections() function (gh-13300)', async function() { + const m = new mongoose.Mongoose(); + const schema = new Schema({ name: String }); + const A = m.model('gh13300A', schema, 'gh13300A'); + const B = m.model('gh13300B', schema, 'gh13300B'); + const C = m.model('gh13300C', schema, 'gh13300C'); + await m.connect(start.uri); + await m.connection.createCollections(); + const collections = await m.connection.db.listCollections().toArray(); + assert.equal(collections.length, 3); + const collectionNames = collections.map(inner => Object.values(inner)[0]); + assert.equal(collectionNames.includes(A.modelName), true); + assert.equal(collectionNames.includes(B.modelName), true); + assert.equal(collectionNames.includes(C.modelName), true); + // currently cannot write test for continueOnError or errors in general. + }); + }); });