From b698c2825cc50c8cefec3a8035c8eb7dea7bb80f Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 9 Jul 2024 16:42:03 -0400 Subject: [PATCH] perf(model): skip $toObject() for insertMany with only primitive values Re: #14719 --- lib/document.js | 63 +++++++++++++++++++++++++++++-------------------- lib/model.js | 5 +++- 2 files changed, 41 insertions(+), 27 deletions(-) diff --git a/lib/document.js b/lib/document.js index 9efa9d15fa8..c67c246cd02 100644 --- a/lib/document.js +++ b/lib/document.js @@ -3811,13 +3811,7 @@ Document.prototype.$__handleReject = function handleReject(err) { Document.prototype.$toObject = function(options, json) { const defaultOptions = this.$__schema._defaultToObjectOptions(json); - const hasOnlyPrimitiveValues = !this.$__.populated && !this.$__.wasPopulated && (this._doc == null || Object.values(this._doc).every(v => { - return v == null - || typeof v !== 'object' - || (utils.isNativeObject(v) && !Array.isArray(v)) - || isBsonType(v, 'ObjectId') - || isBsonType(v, 'Decimal128'); - })); + const hasOnlyPrimitiveValues = this.$__hasOnlyPrimitiveValues(); // If options do not exist or is not an object, set it to empty object options = utils.isPOJO(options) ? { ...options } : {}; @@ -3867,27 +3861,10 @@ Document.prototype.$toObject = function(options, json) { // const originalTransform = options.transform; let ret; - if (hasOnlyPrimitiveValues) { + if (hasOnlyPrimitiveValues && !options.flattenObjectIds) { // Fast path: if we don't have any nested objects or arrays, we only need a // shallow clone. - if (this._doc == null) { - ret = {}; - } - ret = {}; - if (this._doc != null) { - for (const key of Object.keys(this._doc)) { - const value = this._doc[key]; - if (value instanceof Date) { - ret[key] = new Date(value); - } else if (value === undefined) { - delete ret[key]; - } else if (options.flattenObjectIds && isBsonType(value, 'ObjectId')) { - ret[key] = value.toJSON(); - } else { - ret[key] = value; - } - } - } + ret = this.$__toObjectInternal(); } else { ret = clone(this._doc, options) || {}; } @@ -3948,6 +3925,26 @@ Document.prototype.$toObject = function(options, json) { return ret; }; +/*! + * Internal shallow clone alternative to `$toObject()`: much faster, no options processing + */ + +Document.prototype.$__toObjectInternal = function $__toObjectInternal() { + const ret = {}; + if (this._doc != null) { + for (const key of Object.keys(this._doc)) { + const value = this._doc[key]; + if (value instanceof Date) { + ret[key] = new Date(value); + } else if (value !== undefined) { + ret[key] = value; + } + } + } + + return ret; +}; + /** * Converts this document into a plain-old JavaScript object ([POJO](https://masteringjs.io/tutorials/fundamentals/pojo)). * @@ -5328,6 +5325,20 @@ Document.prototype.$clearModifiedPaths = function $clearModifiedPaths() { return this; }; +/*! + * Check if the given document only has primitive values + */ + +Document.prototype.$__hasOnlyPrimitiveValues = function $__hasOnlyPrimitiveValues() { + return !this.$__.populated && !this.$__.wasPopulated && (this._doc == null || Object.values(this._doc).every(v => { + return v == null + || typeof v !== 'object' + || (utils.isNativeObject(v) && !Array.isArray(v)) + || isBsonType(v, 'ObjectId') + || isBsonType(v, 'Decimal128'); + })); +}; + /*! * Module exports. */ diff --git a/lib/model.js b/lib/model.js index a2cf862a676..4d2e9a3aad9 100644 --- a/lib/model.js +++ b/lib/model.js @@ -2951,7 +2951,10 @@ Model.$__insertMany = function(arr, options, callback) { } const shouldSetTimestamps = (!options || options.timestamps !== false) && doc.initializeTimestamps && (!doc.$__ || doc.$__.timestamps !== false); if (shouldSetTimestamps) { - return doc.initializeTimestamps().toObject(internalToObjectOptions); + doc.initializeTimestamps(); + } + if (doc.$__hasOnlyPrimitiveValues()) { + return doc.$__toObjectInternal(); } return doc.toObject(internalToObjectOptions); });