Skip to content

Commit

Permalink
Merge branch '6.4' into auto-typed-schema
Browse files Browse the repository at this point in the history
  • Loading branch information
vkarpov15 committed Jun 5, 2022
2 parents e137a6d + 32d5933 commit 6547fe2
Show file tree
Hide file tree
Showing 18 changed files with 351 additions and 36 deletions.
53 changes: 48 additions & 5 deletions lib/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
const ChangeStream = require('./cursor/ChangeStream');
const EventEmitter = require('events').EventEmitter;
const Schema = require('./schema');
const Collection = require('./driver').get().Collection;
const STATES = require('./connectionstate');
const MongooseError = require('./error/index');
const SyncIndexesError = require('./error/syncIndexes');
const PromiseProvider = require('./promise_provider');
const ServerSelectionError = require('./error/serverSelection');
const applyPlugins = require('./helpers/schema/applyPlugins');
const driver = require('./driver');
const promiseOrCallback = require('./helpers/promiseOrCallback');
const get = require('./helpers/get');
const immediate = require('./helpers/immediate');
Expand Down Expand Up @@ -697,6 +697,18 @@ Connection.prototype.openUri = function(uri, options, callback) {
typeof callback + '"');
}

if (this._destroyCalled) {
const error = 'Connection has been closed and destroyed, and cannot be used for re-opening the connection. ' +
'Please create a new connection with `mongoose.createConnection()` or `mongoose.connect()`.';
if (typeof callback === 'function') {
callback(error);
return;
}
else {
throw new MongooseError(error);
}
}

if (this.readyState === STATES.connecting || this.readyState === STATES.connected) {
if (this._connectionString !== uri) {
throw new MongooseError('Can\'t call `openUri()` on an active connection with ' +
Expand Down Expand Up @@ -901,6 +913,23 @@ function _setClient(conn, client, options, dbName) {
}
}

Connection.prototype.destroy = function(force, callback) {
if (typeof force === 'function') {
callback = force;
force = false;
}

if (force != null && typeof force === 'object') {
this.$wasForceClosed = !!force.force;
} else {
this.$wasForceClosed = !!force;
}

return promiseOrCallback(callback, cb => {
this._close(force, true, cb);
});
};

/**
* Closes the connection
*
Expand All @@ -923,27 +952,34 @@ Connection.prototype.close = function(force, callback) {
}

return promiseOrCallback(callback, cb => {
this._close(force, cb);
this._close(force, false, cb);
});
};

/**
* Handles closing the connection
*
* @param {Boolean} force
* @param {Boolean} destroy
* @param {Function} callback
* @api private
*/
Connection.prototype._close = function(force, callback) {
Connection.prototype._close = function(force, destroy, callback) {
const _this = this;
const closeCalled = this._closeCalled;
this._closeCalled = true;
this._destroyCalled = destroy;
if (this.client != null) {
this.client._closeCalled = true;
this.client._destroyCalled = destroy;
}

const conn = this;
switch (this.readyState) {
case STATES.disconnected:
if (destroy && this.base.connections.indexOf(conn) !== -1) {
this.base.connections.splice(this.base.connections.indexOf(conn), 1);
}
if (closeCalled) {
callback();
} else {
Expand All @@ -963,19 +999,25 @@ Connection.prototype._close = function(force, callback) {
if (err) {
return callback(err);
}
if (destroy && _this.base.connections.indexOf(conn) !== -1) {
_this.base.connections.splice(_this.base.connections.indexOf(conn), 1);
}
_this.onClose(force);
callback(null);
});

break;
case STATES.connecting:
this.once('open', function() {
_this.close(callback);
destroy ? _this.destroy(force, callback) : _this.close(force, callback);
});
break;

case STATES.disconnecting:
this.once('close', function() {
if (destroy && _this.base.connections.indexOf(conn) !== -1) {
_this.base.connections.splice(_this.base.connections.indexOf(conn), 1);
}
callback();
});
break;
Expand Down Expand Up @@ -1004,7 +1046,7 @@ Connection.prototype.onClose = function(force) {
this.emit('close', force);

for (const db of this.otherDbs) {
db.close({ force: force, skipCloseClient: true });
this._destroyCalled ? db.destroy({ force: force, skipCloseClient: true }) : db.close({ force: force, skipCloseClient: true });
}
};

Expand All @@ -1026,6 +1068,7 @@ Connection.prototype.collection = function(name, options) {
};
options = Object.assign({}, defaultOptions, options ? utils.clone(options) : {});
options.$wasForceClosed = this.$wasForceClosed;
const Collection = driver.get().Collection;
if (!(name in this.collections)) {
this.collections[name] = new Collection(name, this, options);
}
Expand Down
39 changes: 35 additions & 4 deletions lib/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -4251,10 +4251,10 @@ Document.prototype.$getPopulatedDocs = function $getPopulatedDocs() {
*
* #### Example:
*
* Model.findOne().populate('author').exec(function (err, doc) {
* console.log(doc.author.name) // Dr.Seuss
* console.log(doc.populated('author')) // '5144cf8050f071d979c118a7'
* })
* const doc = await Model.findOne().populate('author');
*
* console.log(doc.author.name); // Dr.Seuss
* console.log(doc.populated('author')); // '5144cf8050f071d979c118a7'
*
* If the path was not populated, returns `undefined`.
*
Expand Down Expand Up @@ -4308,6 +4308,37 @@ Document.prototype.populated = function(path, val, options) {

Document.prototype.$populated = Document.prototype.populated;

/**
* Throws an error if a given path is not populated
*
* #### Example:
*
* const doc = await Model.findOne().populate('author');
*
* doc.$assertPopulated('author'); // does not throw
* doc.$assertPopulated('other path'); // throws an error
*
*
* @param {String | Array<String>} path
* @return {Document} this
* @memberOf Document
* @instance
* @api public
*/

Document.prototype.$assertPopulated = function $assertPopulated(paths) {
if (Array.isArray(paths)) {
paths.forEach(path => this.$assertPopulated(path));
return this;
}

if (!this.$populated(paths)) {
throw new MongooseError(`Expected path "${paths}" to be populated`);
}

return this;
};

/**
* Takes a populated field and returns it to its unpopulated state.
*
Expand Down
5 changes: 4 additions & 1 deletion lib/helpers/timestamps/setupTimestamps.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,11 @@ module.exports = function setupTimestamps(schema, timestamps) {
}

if (createdAt && !schema.paths[createdAt]) {
schemaAdditions[createdAt] = { [schema.options.typeKey || 'type']: Date, immutable: true };
const baseImmutableCreatedAt = schema.base.get('timestamps.createdAt.immutable');
const immutable = baseImmutableCreatedAt != null ? baseImmutableCreatedAt : true;
schemaAdditions[createdAt] = { [schema.options.typeKey || 'type']: Date, immutable };
}

schema.add(schemaAdditions);

schema.pre('save', function(next) {
Expand Down
30 changes: 16 additions & 14 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,23 +154,24 @@ Mongoose.prototype.driver = driver;
* mongoose.set('debug', function(collectionName, methodName, ...methodArgs) {}); // use custom function to log collection methods + arguments
*
* Currently supported options are:
* - 'debug': If `true`, prints the operations mongoose sends to MongoDB to the console. If a writable stream is passed, it will log to that stream, without colorization. If a callback function is passed, it will receive the collection name, the method name, then all arugments passed to the method. For example, if you wanted to replicate the default logging, you could output from the callback `Mongoose: ${collectionName}.${methodName}(${methodArgs.join(', ')})`.
* - 'returnOriginal': If `false`, changes the default `returnOriginal` option to `findOneAndUpdate()`, `findByIdAndUpdate`, and `findOneAndReplace()` to false. This is equivalent to setting the `new` option to `true` for `findOneAndX()` calls by default. Read our [`findOneAndUpdate()` tutorial](/docs/tutorials/findoneandupdate.html) for more information.
* - 'applyPluginsToChildSchemas': `true` by default. Set to false to skip applying global plugins to child schemas
* - 'applyPluginsToDiscriminators': `false` by default. Set to true to apply global plugins to discriminator schemas. This typically isn't necessary because plugins are applied to the base schema and discriminators copy all middleware, methods, statics, and properties from the base schema.
* - 'autoCreate': Set to `true` to make Mongoose call [`Model.createCollection()`](/docs/api/model.html#model_Model.createCollection) automatically when you create a model with `mongoose.model()` or `conn.model()`. This is useful for testing transactions, change streams, and other features that require the collection to exist.
* - 'autoIndex': `true` by default. Set to false to disable automatic index creation for all models associated with this Mongoose instance.
* - 'bufferCommands': enable/disable mongoose's buffering mechanism for all connections and models
* - 'cloneSchemas': false by default. Set to `true` to `clone()` all schemas before compiling into a model.
* - 'applyPluginsToDiscriminators': false by default. Set to true to apply global plugins to discriminator schemas. This typically isn't necessary because plugins are applied to the base schema and discriminators copy all middleware, methods, statics, and properties from the base schema.
* - 'applyPluginsToChildSchemas': true by default. Set to false to skip applying global plugins to child schemas
* - 'objectIdGetter': true by default. Mongoose adds a getter to MongoDB ObjectId's called `_id` that returns `this` for convenience with populate. Set this to false to remove the getter.
* - 'runValidators': false by default. Set to true to enable [update validators](/docs/validation.html#update-validators) for all validators by default.
* - 'toObject': `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to [`toObject()`](/docs/api.html#document_Document-toObject)
* - 'toJSON': `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to [`toJSON()`](/docs/api.html#document_Document-toJSON), for determining how Mongoose documents get serialized by `JSON.stringify()`
* - 'strict': true by default, may be `false`, `true`, or `'throw'`. Sets the default strict mode for schemas.
* - 'strictQuery': same value as 'strict' by default (`true`), may be `false`, `true`, or `'throw'`. Sets the default [strictQuery](/docs/guide.html#strictQuery) mode for schemas.
* - 'selectPopulatedPaths': true by default. Set to false to opt out of Mongoose adding all fields that you `populate()` to your `select()`. The schema-level option `selectPopulatedPaths` overwrites this one.
* - 'cloneSchemas': `false` by default. Set to `true` to `clone()` all schemas before compiling into a model.
* - 'debug': If `true`, prints the operations mongoose sends to MongoDB to the console. If a writable stream is passed, it will log to that stream, without colorization. If a callback function is passed, it will receive the collection name, the method name, then all arugments passed to the method. For example, if you wanted to replicate the default logging, you could output from the callback `Mongoose: ${collectionName}.${methodName}(${methodArgs.join(', ')})`.
* - 'timestamps.createdAt.immutable': `true` by default. If `false`, it will change the `createdAt` field to be [`immutable: false`](https://mongoosejs.com/docs/api/schematype.html#schematype_SchemaType-immutable) which means you can update the `createdAt`
* - 'maxTimeMS': If set, attaches [maxTimeMS](https://docs.mongodb.com/manual/reference/operator/meta/maxTimeMS/) to every query
* - 'autoIndex': true by default. Set to false to disable automatic index creation for all models associated with this Mongoose instance.
* - 'autoCreate': Set to `true` to make Mongoose call [`Model.createCollection()`](/docs/api/model.html#model_Model.createCollection) automatically when you create a model with `mongoose.model()` or `conn.model()`. This is useful for testing transactions, change streams, and other features that require the collection to exist.
* - 'objectIdGetter': `true` by default. Mongoose adds a getter to MongoDB ObjectId's called `_id` that returns `this` for convenience with populate. Set this to false to remove the getter.
* - 'overwriteModels': Set to `true` to default to overwriting models with the same name when calling `mongoose.model()`, as opposed to throwing an `OverwriteModelError`.
* - 'returnOriginal': If `false`, changes the default `returnOriginal` option to `findOneAndUpdate()`, `findByIdAndUpdate`, and `findOneAndReplace()` to false. This is equivalent to setting the `new` option to `true` for `findOneAndX()` calls by default. Read our [`findOneAndUpdate()` tutorial](/docs/tutorials/findoneandupdate.html) for more information.
* - 'runValidators': `false` by default. Set to true to enable [update validators](/docs/validation.html#update-validators) for all validators by default.
* - 'selectPopulatedPaths': `true` by default. Set to false to opt out of Mongoose adding all fields that you `populate()` to your `select()`. The schema-level option `selectPopulatedPaths` overwrites this one.
* - 'strict': `true` by default, may be `false`, `true`, or `'throw'`. Sets the default strict mode for schemas.
* - 'strictQuery': same value as 'strict' by default (`true`), may be `false`, `true`, or `'throw'`. Sets the default [strictQuery](/docs/guide.html#strictQuery) mode for schemas.
* - 'toJSON': `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to [`toJSON()`](/docs/api.html#document_Document-toJSON), for determining how Mongoose documents get serialized by `JSON.stringify()`
* - 'toObject': `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to [`toObject()`](/docs/api.html#document_Document-toObject)
*
* @param {String} key
* @param {String|Function|Boolean} value
Expand Down Expand Up @@ -275,6 +276,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 conn = new Connection(_mongoose);
if (typeof options === 'function') {
callback = options;
Expand Down
71 changes: 65 additions & 6 deletions lib/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const immediate = require('./helpers/immediate');
const isExclusive = require('./helpers/projection/isExclusive');
const isInclusive = require('./helpers/projection/isInclusive');
const isSubpath = require('./helpers/projection/isSubpath');
const mpath = require('mpath');
const mquery = require('mquery');
const parseProjection = require('./helpers/projection/parseProjection');
const removeUnusedArrayFilters = require('./helpers/update/removeUnusedArrayFilters');
Expand Down Expand Up @@ -124,7 +125,10 @@ function Query(conditions, options, model, collection) {
}

// inherit mquery
mquery.call(this, this.mongooseCollection, options);
mquery.call(this, null, options);
if (collection) {
this.collection(collection);
}

if (conditions) {
this.find(conditions);
Expand Down Expand Up @@ -2217,7 +2221,8 @@ Query.prototype._find = wrapThunk(function(callback) {
// Separate options to pass down to `completeMany()` in case we need to
// set a session on the document
const completeManyOptions = Object.assign({}, {
session: this && this.options && this.options.session || null
session: this && this.options && this.options.session || null,
lean: mongooseOptions.lean || null
});

const cb = (err, docs) => {
Expand All @@ -2241,7 +2246,9 @@ Query.prototype._find = wrapThunk(function(callback) {
});
}
return mongooseOptions.lean ?
callback(null, docs) :
// call _completeManyLean here?
_completeManyLean(_this.model.schema, docs, null, completeManyOptions, callback) :
// callback(null, docs) :
completeMany(_this.model, docs, fields, userProvidedFields, completeManyOptions, callback);
}

Expand Down Expand Up @@ -2415,6 +2422,9 @@ Query.prototype._completeOne = function(doc, res, callback) {
const mongooseOptions = this._mongooseOptions;
// `rawResult`
const options = this.options;
if (!options.lean && mongooseOptions.lean) {
options.lean = mongooseOptions.lean;
}

if (options.explain) {
return callback(null, doc);
Expand All @@ -2428,7 +2438,7 @@ Query.prototype._completeOne = function(doc, res, callback) {
}
}
return mongooseOptions.lean ?
_completeOneLean(doc, res, options, callback) :
_completeOneLean(model.schema, doc, null, res, options, callback) :
completeOne(model, doc, res, options, projection, userProvidedFields,
null, callback);
}
Expand All @@ -2439,7 +2449,7 @@ Query.prototype._completeOne = function(doc, res, callback) {
if (err != null) {
return callback(err);
}
_completeOneLean(doc, res, options, callback);
_completeOneLean(model.schema, doc, null, res, options, callback);
});
}

Expand Down Expand Up @@ -4006,13 +4016,62 @@ Query.prototype._findAndModify = function(type, callback) {
* ignore
*/

function _completeOneLean(doc, res, opts, callback) {
function _completeOneLean(schema, doc, path, res, opts, callback) {
if (opts.lean && opts.lean.transform) {
for (let i = 0; i < schema.childSchemas.length; i++) {
const childPath = path ? path + '.' + schema.childSchemas[i].model.path : schema.childSchemas[i].model.path;
const _schema = schema.childSchemas[i].schema;
const obj = mpath.get(childPath, doc);
if (obj == null) {
continue;
}
if (Array.isArray(obj)) {
for (let i = 0; i < obj.length; i++) {
opts.lean.transform(obj[i]);
}
} else {
opts.lean.transform(obj);
}
_completeOneLean(_schema, obj, childPath, res, opts);
}
if (callback) {
return callback(null, doc);
} else {
return;
}
}
if (opts.rawResult) {
return callback(null, res);
}
return callback(null, doc);
}

/*!
* ignore
*/

function _completeManyLean(schema, docs, path, opts, callback) {
if (opts.lean && opts.lean.transform) {
for (let i = 0; i < schema.childSchemas.length; i++) {
const childPath = path ? path + '.' + schema.childSchemas[i].model.path : schema.childSchemas[i].model.path;
const _schema = schema.childSchemas[i].schema;
let doc = mpath.get(childPath, docs);
if (doc == null) {
continue;
}
doc = doc.flat();
for (let i = 0; i < doc.length; i++) {
opts.lean.transform(doc[i]);
}
_completeManyLean(_schema, doc, childPath, opts);
}
}

if (!callback) {
return;
}
return callback(null, docs);
}
/*!
* Override mquery.prototype._mergeUpdate to handle mongoose objects in
* updates.
Expand Down
1 change: 0 additions & 1 deletion lib/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -736,7 +736,6 @@ reserved['prototype'] =
// EventEmitter
reserved.emit =
reserved.listeners =
reserved.on =
reserved.removeListener =

// document properties and functions
Expand Down

0 comments on commit 6547fe2

Please sign in to comment.