Skip to content

Commit

Permalink
perf(document): avoid creating unnecessary empty objects when creatin…
Browse files Browse the repository at this point in the history
…g a state machine

Re: #11541
  • Loading branch information
vkarpov15 committed Jun 25, 2022
1 parent 3a4ba27 commit 4a2e65e
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 35 deletions.
36 changes: 18 additions & 18 deletions lib/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -1588,7 +1588,7 @@ Document.prototype.$__shouldModify = function(pathToMark, path, options, constru
return true;
}

if (val === void 0 && path in this.$__.activePaths.states.default) {
if (val === void 0 && path in this.$__.activePaths.getStatePaths('default')) {
// we're just unsetting the default value which was never saved
return false;
}
Expand All @@ -1608,7 +1608,7 @@ Document.prototype.$__shouldModify = function(pathToMark, path, options, constru
if (!constructing &&
val !== null &&
val !== undefined &&
path in this.$__.activePaths.states.default &&
path in this.$__.activePaths.getStatePaths('default') &&
deepEqual(val, schema.getDefault(this, constructing))) {
// a path with a default was $unset on the server
// and the user is setting it to the same value again
Expand Down Expand Up @@ -1906,7 +1906,7 @@ Document.prototype.$ignore = function(path) {
*/

Document.prototype.directModifiedPaths = function() {
return Object.keys(this.$__.activePaths.states.modify);
return Object.keys(this.$__.activePaths.getStatePaths('modify'));
};

/**
Expand Down Expand Up @@ -1984,7 +1984,7 @@ function _isEmpty(v) {
Document.prototype.modifiedPaths = function(options) {
options = options || {};

const directModifiedPaths = Object.keys(this.$__.activePaths.states.modify);
const directModifiedPaths = Object.keys(this.$__.activePaths.getStatePaths('modify'));
const result = new Set();

let i = 0;
Expand Down Expand Up @@ -2063,7 +2063,7 @@ Document.prototype[documentModifiedPaths] = Document.prototype.modifiedPaths;

Document.prototype.isModified = function(paths, modifiedPaths) {
if (paths) {
const directModifiedPaths = Object.keys(this.$__.activePaths.states.modify);
const directModifiedPaths = Object.keys(this.$__.activePaths.getStatePaths('modify'));
if (directModifiedPaths.length === 0) {
return false;
}
Expand Down Expand Up @@ -2113,15 +2113,15 @@ Document.prototype.$isDefault = function(path) {
}

if (typeof path === 'string' && path.indexOf(' ') === -1) {
return this.$__.activePaths.states.default.hasOwnProperty(path);
return this.$__.activePaths.getStatePaths('default').hasOwnProperty(path);
}

let paths = path;
if (!Array.isArray(paths)) {
paths = paths.split(' ');
}

return paths.some(path => this.$__.activePaths.states.default.hasOwnProperty(path));
return paths.some(path => this.$__.activePaths.getStatePaths('default').hasOwnProperty(path));
};

/**
Expand Down Expand Up @@ -2174,15 +2174,15 @@ Document.prototype.isDirectModified = function(path) {
}

if (typeof path === 'string' && path.indexOf(' ') === -1) {
return this.$__.activePaths.states.modify.hasOwnProperty(path);
return this.$__.activePaths.getStatePaths('modify').hasOwnProperty(path);
}

let paths = path;
if (!Array.isArray(paths)) {
paths = paths.split(' ');
}

return paths.some(path => this.$__.activePaths.states.modify.hasOwnProperty(path));
return paths.some(path => this.$__.activePaths.getStatePaths('modify').hasOwnProperty(path));
};

/**
Expand All @@ -2199,15 +2199,15 @@ Document.prototype.isInit = function(path) {
}

if (typeof path === 'string' && path.indexOf(' ') === -1) {
return this.$__.activePaths.states.init.hasOwnProperty(path);
return this.$__.activePaths.getStatePaths('init').hasOwnProperty(path);
}

let paths = path;
if (!Array.isArray(paths)) {
paths = paths.split(' ');
}

return paths.some(path => this.$__.activePaths.states.init.hasOwnProperty(path));
return paths.some(path => this.$__.activePaths.getStatePaths('init').hasOwnProperty(path));
};

/**
Expand Down Expand Up @@ -2435,7 +2435,7 @@ Document.prototype.$validate = Document.prototype.validate;
*/

function _evaluateRequiredFunctions(doc) {
const requiredFields = Object.keys(doc.$__.activePaths.states.require);
const requiredFields = Object.keys(doc.$__.activePaths.getStatePaths('require'));
let i = 0;
const len = requiredFields.length;
for (i = 0; i < len; ++i) {
Expand Down Expand Up @@ -2463,7 +2463,7 @@ function _getPathsToValidate(doc) {

_evaluateRequiredFunctions(doc);
// only validate required fields when necessary
let paths = new Set(Object.keys(doc.$__.activePaths.states.require).filter(function(path) {
let paths = new Set(Object.keys(doc.$__.activePaths.getStatePaths('require')).filter(function(path) {
if (!doc.$__isSelected(path) && !doc.$isModified(path)) {
return false;
}
Expand All @@ -2473,9 +2473,9 @@ function _getPathsToValidate(doc) {
return true;
}));

Object.keys(doc.$__.activePaths.states.init).forEach(addToPaths);
Object.keys(doc.$__.activePaths.states.modify).forEach(addToPaths);
Object.keys(doc.$__.activePaths.states.default).forEach(addToPaths);
Object.keys(doc.$__.activePaths.getStatePaths('init')).forEach(addToPaths);
Object.keys(doc.$__.activePaths.getStatePaths('modify')).forEach(addToPaths);
Object.keys(doc.$__.activePaths.getStatePaths('default')).forEach(addToPaths);
function addToPaths(p) { paths.add(p); }

const subdocs = doc.$getAllSubdocs();
Expand Down Expand Up @@ -3197,8 +3197,8 @@ Document.prototype.$__reset = function reset() {

this.$__.backup = {};
this.$__.backup.activePaths = {
modify: Object.assign({}, this.$__.activePaths.states.modify),
default: Object.assign({}, this.$__.activePaths.states.default)
modify: Object.assign({}, this.$__.activePaths.getStatePaths('modify')),
default: Object.assign({}, this.$__.activePaths.getStatePaths('default'))
};
this.$__.backup.validationError = this.$__.validationError;
this.$__.backup.errors = this.$errors;
Expand Down
6 changes: 3 additions & 3 deletions lib/helpers/document/cleanModifiedSubpaths.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,21 @@ module.exports = function cleanModifiedSubpaths(doc, path, options) {
return deleted;
}

for (const modifiedPath of Object.keys(doc.$__.activePaths.states.modify)) {
for (const modifiedPath of Object.keys(doc.$__.activePaths.getStatePaths('modify'))) {
if (skipDocArrays) {
const schemaType = doc.$__schema.path(modifiedPath);
if (schemaType && schemaType.$isMongooseDocumentArray) {
continue;
}
}
if (modifiedPath.startsWith(path + '.')) {
delete doc.$__.activePaths.states.modify[modifiedPath];
doc.$__.activePaths.clearPath(modifiedPath);
++deleted;

if (doc.$isSubdocument) {
const owner = doc.ownerDocument();
const fullPath = doc.$__fullPath(modifiedPath);
delete owner.$__.activePaths.states.modify[fullPath];
owner.$__.activePaths.clearPath(fullPath);
}
}
}
Expand Down
13 changes: 7 additions & 6 deletions lib/helpers/document/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ const isPOJO = utils.isPOJO;
exports.compile = compile;
exports.defineKey = defineKey;

const _isEmptyOptions = Object.freeze({
minimize: true,
virtuals: false,
getters: false,
transform: false
});

/*!
* Compiles schemas.
*/
Expand Down Expand Up @@ -130,12 +137,6 @@ function defineKey({ prop, subprops, prototype, prefix, options }) {
value: true
});

const _isEmptyOptions = Object.freeze({
minimize: true,
virtuals: false,
getters: false,
transform: false
});
Object.defineProperty(nested, '$isEmpty', {
enumerable: false,
configurable: true,
Expand Down
29 changes: 21 additions & 8 deletions lib/statemachine.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,6 @@ StateMachine.ctor = function() {
this.paths = {};
this.states = {};
this.stateNames = states;

let i = states.length,
state;

while (i--) {
state = states[i];
this.states[state] = {};
}
};

ctor.prototype = new StateMachine();
Expand Down Expand Up @@ -76,6 +68,7 @@ StateMachine.prototype._changeState = function _changeState(path, nextState) {
if (prevBucket) delete prevBucket[path];

this.paths[path] = nextState;
this.states[nextState] = this.states[nextState] || {};
this.states[nextState][path] = true;
};

Expand All @@ -84,6 +77,9 @@ StateMachine.prototype._changeState = function _changeState(path, nextState) {
*/

StateMachine.prototype.clear = function clear(state) {
if (this.states[state] == null) {
return;
}
const keys = Object.keys(this.states[state]);
let i = keys.length;
let path;
Expand All @@ -108,6 +104,17 @@ StateMachine.prototype.clearPath = function clearPath(path) {
delete this.states[state][path];
};

/*!
* Gets the paths for the given state, or empty object `{}` if none.
*/

StateMachine.prototype.getStatePaths = function getStatePaths(state) {
if (this.states[state] != null) {
return this.states[state];
}
return {};
};

/*!
* Checks to see if at least one path is in the states passed in via `arguments`
* e.g., this.some('required', 'inited')
Expand All @@ -120,6 +127,9 @@ StateMachine.prototype.some = function some() {
const _this = this;
const what = arguments.length ? arguments : this.stateNames;
return Array.prototype.some.call(what, function(state) {
if (_this.states[state] == null) {
return false;
}
return Object.keys(_this.states[state]).length;
});
};
Expand All @@ -143,6 +153,9 @@ StateMachine.prototype._iter = function _iter(iterMethod) {
const _this = this;

const paths = states.reduce(function(paths, state) {
if (_this.states[state] == null) {
return paths;
}
return paths.concat(Object.keys(_this.states[state]));
}, []);

Expand Down

0 comments on commit 4a2e65e

Please sign in to comment.