From 0a4bcc5c40f51beedfeab6603f4476a5b5c33d07 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Sun, 5 Jun 2022 12:52:01 -0400 Subject: [PATCH 1/5] feat(mongoose): add `setDriver()` function to allow overwriting `driver` in a more consistent way --- lib/connection.js | 2 +- lib/driver.js | 10 ------ lib/index.js | 72 ++++++++++++++++++++++++++++--------- test/types/populate.test.ts | 25 +++++++++++++ types/index.d.ts | 6 ++++ 5 files changed, 87 insertions(+), 28 deletions(-) diff --git a/lib/connection.js b/lib/connection.js index 54d98cec28e..8e720d6e313 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -1026,7 +1026,7 @@ Connection.prototype.collection = function(name, options) { }; options = Object.assign({}, defaultOptions, options ? utils.clone(options) : {}); options.$wasForceClosed = this.$wasForceClosed; - const Collection = driver.get().Collection; + const Collection = this.base && this.base.__driver ? this.base.__driver.Collection : driver.get().Collection; if (!(name in this.collections)) { this.collections[name] = new Collection(name, this, options); } diff --git a/lib/driver.js b/lib/driver.js index 38269132686..cf7ca3d7b25 100644 --- a/lib/driver.js +++ b/lib/driver.js @@ -6,20 +6,10 @@ let driver = null; -const _mongooseInstances = []; -module.exports._mongooseInstances = _mongooseInstances; - module.exports.get = function() { return driver; }; module.exports.set = function(v) { driver = v; - - for (const mongoose of _mongooseInstances) { - const Connection = driver.getConnection(); - mongoose.Connection = Connection; - mongoose.connections = [new Connection(mongoose)]; - mongoose.Collection = driver.Collection; - } }; diff --git a/lib/index.js b/lib/index.js index a6af69db233..46824141068 100644 --- a/lib/index.js +++ b/lib/index.js @@ -35,6 +35,7 @@ const shardingPlugin = require('./plugins/sharding'); const trusted = require('./helpers/query/trusted').trusted; const sanitizeFilter = require('./helpers/query/sanitizeFilter'); const isBsonType = require('./helpers/isBsonType'); +const MongooseError = require('./error/mongooseError'); const defaultMongooseSymbol = Symbol.for('mongoose:default'); @@ -62,6 +63,7 @@ function Mongoose(options) { this.connections = []; this.models = {}; this.events = new EventEmitter(); + this.__driver = driver.get(); // default global options this.options = Object.assign({ pluralization: true, @@ -70,7 +72,6 @@ function Mongoose(options) { }, options); const conn = this.createConnection(); // default connection conn.models = this.models; - driver._mongooseInstances.push(this); if (this.options.pluralization) { this._pluralize = legacyPluralize; @@ -136,6 +137,7 @@ Mongoose.prototype.ConnectionStates = STATES; * uses to communicate with the database. A driver is a Mongoose-specific interface that defines functions * like `find()`. * + * @deprecated * @memberOf Mongoose * @property driver * @api public @@ -143,6 +145,35 @@ Mongoose.prototype.ConnectionStates = STATES; Mongoose.prototype.driver = driver; +/** + * Overwrites the current driver used by this Mongoose instance. A driver is a + * Mongoose-specific interface that defines functions like `find()`. + * + * @memberOf Mongoose + * @method setDriver + * @api public + */ + +Mongoose.prototype.setDriver = function setDriver(driver) { + const _mongoose = this instanceof Mongoose ? this : mongoose; + + if (_mongoose.__driver === driver) { + return; + } + + if (_mongoose.connections && _mongoose.connections.find(conn => conn.readyState !== STATES.disconnected)) { + const msg = 'Cannot modify Mongoose driver if a connection is already open. ' + + 'Call `mongoose.disconnect()` before modifying the driver'; + throw new MongooseError(msg); + } + _mongoose.__driver = driver; + + const Connection = driver.getConnection(); + _mongoose.connections = [new Connection(_mongoose)]; + + return _mongoose; +}; + /** * Sets mongoose options * @@ -276,7 +307,7 @@ Mongoose.prototype.get = Mongoose.prototype.set; Mongoose.prototype.createConnection = function(uri, options, callback) { const _mongoose = this instanceof Mongoose ? this : mongoose; - const Connection = driver.get().getConnection(); + const Connection = _mongoose.__driver.getConnection(); const conn = new Connection(_mongoose); if (typeof options === 'function') { callback = options; @@ -689,7 +720,7 @@ Mongoose.prototype.__defineGetter__('connection', function() { }); Mongoose.prototype.__defineSetter__('connection', function(v) { - if (v instanceof Connection) { + if (v instanceof this.__driver.getConnection()) { this.connections[0] = v; this.models = v.models; } @@ -718,18 +749,6 @@ Mongoose.prototype.__defineSetter__('connection', function(v) { Mongoose.prototype.connections; -/*! - * Connection - */ - -const Connection = driver.get().getConnection(); - -/*! - * Collection - */ - -const Collection = driver.get().Collection; - /** * The Mongoose Aggregate constructor * @@ -746,7 +765,14 @@ Mongoose.prototype.Aggregate = Aggregate; * @api public */ -Mongoose.prototype.Collection = Collection; +Object.defineProperty(Mongoose.prototype, 'Collection', { + get: function() { + return this.__driver.Collection; + }, + set: function(Collection) { + this.__driver.Collection = Collection; + } +}); /** * The Mongoose [Connection](#connection_Connection) constructor @@ -757,7 +783,19 @@ Mongoose.prototype.Collection = Collection; * @api public */ -Mongoose.prototype.Connection = Connection; + +Object.defineProperty(Mongoose.prototype, 'Connection', { + get: function() { + return this.__driver.getConnection(); + }, + set: function(Connection) { + if (Connection === this.__driver.getConnection()) { + return; + } + + this.__driver.getConnection = () => Connection; + } +}); /** * The Mongoose version diff --git a/test/types/populate.test.ts b/test/types/populate.test.ts index e122cf7f47c..6c66837b99e 100644 --- a/test/types/populate.test.ts +++ b/test/types/populate.test.ts @@ -228,4 +228,29 @@ async function _11532() { if (!leanResult) return; expectType(leanResult.child.name); expectError(leanResult?.__v); +} + +async function gh11710() { + + // `Parent` represents the object as it is stored in MongoDB + interface Parent { + child?: Types.ObjectId, + name?: string + } + interface Child { + name: string; + } + interface PopulatedParent { + child: Child | null; + } + const ParentModel = model('Parent', new Schema({ + child: { type: Schema.Types.ObjectId, ref: 'Child' }, + name: String + })); + const childSchema: Schema = new Schema({ name: String }); + const ChildModel = model('Child', childSchema); + + // Populate with `Paths` generic `{ child: Child }` to override `child` path + const doc = await ParentModel.findOne({}).populate>('child').orFail(); + expectType(doc.child); } \ No newline at end of file diff --git a/types/index.d.ts b/types/index.d.ts index 3695eadcd9b..6e29e1e1c05 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -72,6 +72,12 @@ declare module 'mongoose' { /** Returns an array of model names created on this instance of Mongoose. */ export function modelNames(): Array; + /** + * Overwrites the current driver used by this Mongoose instance. A driver is a + * Mongoose-specific interface that defines functions like `find()`. + */ + export function setDriver(driver: any): Mongoose; + /** The node-mongodb-native driver Mongoose uses. */ export const mongo: typeof mongodb; From 0a53c798666ed7811afc80f7c67914895dcc7acc Mon Sep 17 00:00:00 2001 From: Hafez Date: Sun, 5 Jun 2022 22:32:25 +0200 Subject: [PATCH 2/5] refactor: use explanatory variable --- lib/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/index.js b/lib/index.js index 46824141068..d8f480c3442 100644 --- a/lib/index.js +++ b/lib/index.js @@ -161,7 +161,8 @@ Mongoose.prototype.setDriver = function setDriver(driver) { return; } - if (_mongoose.connections && _mongoose.connections.find(conn => conn.readyState !== STATES.disconnected)) { + const openConnection = _mongoose.connections && _mongoose.connections.find(conn => conn.readyState !== STATES.disconnected); + if (openConnection) { const msg = 'Cannot modify Mongoose driver if a connection is already open. ' + 'Call `mongoose.disconnect()` before modifying the driver'; throw new MongooseError(msg); From 2228df7106ff86a8c0d441d6b044f1107a30075f Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 16 Jun 2022 13:04:39 -0400 Subject: [PATCH 3/5] Update lib/index.js Co-authored-by: Uzlopak --- lib/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/index.js b/lib/index.js index d8f480c3442..e7a9d82e890 100644 --- a/lib/index.js +++ b/lib/index.js @@ -158,7 +158,7 @@ Mongoose.prototype.setDriver = function setDriver(driver) { const _mongoose = this instanceof Mongoose ? this : mongoose; if (_mongoose.__driver === driver) { - return; + return _mongoose; } const openConnection = _mongoose.connections && _mongoose.connections.find(conn => conn.readyState !== STATES.disconnected); From a9b7d4c9131ef30c053f11ee27568108a6505930 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 16 Jun 2022 13:30:06 -0400 Subject: [PATCH 4/5] Update lib/connection.js Co-authored-by: Uzlopak --- lib/connection.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/connection.js b/lib/connection.js index 8e720d6e313..5916b23cee8 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -1026,7 +1026,7 @@ Connection.prototype.collection = function(name, options) { }; options = Object.assign({}, defaultOptions, options ? utils.clone(options) : {}); options.$wasForceClosed = this.$wasForceClosed; - const Collection = this.base && this.base.__driver ? this.base.__driver.Collection : driver.get().Collection; + const Collection = this.base && this.base.__driver && this.base.__driver.Collection || driver.get().Collection; if (!(name in this.collections)) { this.collections[name] = new Collection(name, this, options); } From 567631a164d7455ee5b26233a725092bbdb96010 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 16 Jun 2022 15:17:07 -0400 Subject: [PATCH 5/5] test: add test coverage for drivers re: #11900 --- lib/index.js | 2 -- lib/query.js | 1 - lib/utils.js | 3 +++ test/index.test.js | 42 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 45 insertions(+), 3 deletions(-) diff --git a/lib/index.js b/lib/index.js index cabca5a8b11..3b4ed891ec2 100644 --- a/lib/index.js +++ b/lib/index.js @@ -507,7 +507,6 @@ Mongoose.prototype.pluralize = function(fn) { */ Mongoose.prototype.model = function(name, schema, collection, options) { - const _mongoose = this instanceof Mongoose ? this : mongoose; if (typeof schema === 'string') { @@ -787,7 +786,6 @@ Object.defineProperty(Mongoose.prototype, 'Collection', { * @api public */ - Object.defineProperty(Mongoose.prototype, 'Connection', { get: function() { return this.__driver.getConnection(); diff --git a/lib/query.js b/lib/query.js index 379bf87dd06..291c17fb727 100644 --- a/lib/query.js +++ b/lib/query.js @@ -2197,7 +2197,6 @@ function _castArrayFilters(query) { * @api private */ Query.prototype._find = wrapThunk(function(callback) { - this._castConditions(); if (this.error() != null) { diff --git a/lib/utils.js b/lib/utils.js index 7a6f129fe41..50058acdc13 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -935,6 +935,9 @@ exports.getOption = function(name) { const sources = Array.prototype.slice.call(arguments, 1); for (const source of sources) { + if (source == null) { + continue; + } if (source[name] != null) { return source[name]; } diff --git a/test/index.test.js b/test/index.test.js index c0b1cc483e6..9c80b5ae644 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -7,6 +7,7 @@ const start = require('./common'); const assert = require('assert'); const random = require('./util').random; const stream = require('stream'); +const { EventEmitter } = require('events'); const collection = 'blogposts_' + random(); @@ -1044,4 +1045,45 @@ describe('mongoose module:', function() { }); }); }); + + describe('custom drivers', function() { + it('can set custom driver (gh-11900)', async function() { + const m = new mongoose.Mongoose(); + + class Collection { + findOne(filter, options, cb) { + cb(null, { answer: 42 }); + } + } + class Connection extends EventEmitter { + constructor(base) { + super(); + this.base = base; + this.models = {}; + } + + collection() { + return new Collection(); + } + + openUri(uri, opts, callback) { + this.readyState = mongoose.ConnectionStates.connected; + callback(); + } + } + const driver = { + Collection, + getConnection: () => Connection + }; + + m.setDriver(driver); + + await m.connect(); + + const Test = m.model('Test', m.Schema({ answer: Number })); + + const res = await Test.findOne(); + assert.deepEqual(res.toObject(), { answer: 42 }); + }); + }); });