Skip to content

Commit 895bc32

Browse files
authored
Merge pull request #13614 from Automattic/vkarpov15/gh-13191-2
perf: speed up mapOfSubdocs benchmark by 4x by avoiding unnecessary O(n^2) loop in getPathsToValidate()
2 parents e9eb8ab + b8ebe80 commit 895bc32

File tree

4 files changed

+37
-38
lines changed

4 files changed

+37
-38
lines changed

lib/document.js

+30-31
Original file line numberDiff line numberDiff line change
@@ -1112,9 +1112,11 @@ Document.prototype.$set = function $set(path, val, type, options) {
11121112
return this;
11131113
}
11141114

1115+
options = Object.assign({}, options, { _skipMinimizeTopLevel: false });
1116+
11151117
for (let i = 0; i < len; ++i) {
11161118
key = keys[i];
1117-
const pathName = prefix + key;
1119+
const pathName = prefix ? prefix + key : key;
11181120
pathtype = this.$__schema.pathType(pathName);
11191121
const valForKey = path[key];
11201122

@@ -1126,20 +1128,15 @@ Document.prototype.$set = function $set(path, val, type, options) {
11261128
pathtype === 'nested' &&
11271129
this._doc[key] != null) {
11281130
delete this._doc[key];
1129-
// Make sure we set `{}` back even if we minimize re: gh-8565
1130-
options = Object.assign({}, options, { _skipMinimizeTopLevel: true });
1131-
} else {
1132-
// Make sure we set `{_skipMinimizeTopLevel: false}` if don't have overwrite: gh-10441
1133-
options = Object.assign({}, options, { _skipMinimizeTopLevel: false });
11341131
}
11351132

11361133
if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') {
1137-
this.$set(prefix + key, path[key], constructing, Object.assign({}, options, { _skipMarkModified: true }));
1138-
$applyDefaultsToNested(this.$get(prefix + key), prefix + key, this);
1134+
this.$set(pathName, valForKey, constructing, Object.assign({}, options, { _skipMarkModified: true }));
1135+
$applyDefaultsToNested(this.$get(pathName), pathName, this);
11391136
continue;
11401137
} else if (strict) {
11411138
// Don't overwrite defaults with undefined keys (gh-3981) (gh-9039)
1142-
if (constructing && path[key] === void 0 &&
1139+
if (constructing && valForKey === void 0 &&
11431140
this.$get(pathName) !== void 0) {
11441141
continue;
11451142
}
@@ -1149,20 +1146,19 @@ Document.prototype.$set = function $set(path, val, type, options) {
11491146
}
11501147

11511148
if (pathtype === 'real' || pathtype === 'virtual') {
1152-
const p = path[key];
1153-
this.$set(prefix + key, p, constructing, options);
1154-
} else if (pathtype === 'nested' && path[key] instanceof Document) {
1155-
this.$set(prefix + key,
1156-
path[key].toObject({ transform: false }), constructing, options);
1149+
this.$set(pathName, valForKey, constructing, options);
1150+
} else if (pathtype === 'nested' && valForKey instanceof Document) {
1151+
this.$set(pathName,
1152+
valForKey.toObject({ transform: false }), constructing, options);
11571153
} else if (strict === 'throw') {
11581154
if (pathtype === 'nested') {
1159-
throw new ObjectExpectedError(key, path[key]);
1155+
throw new ObjectExpectedError(key, valForKey);
11601156
} else {
11611157
throw new StrictModeError(key);
11621158
}
11631159
}
1164-
} else if (path[key] !== void 0) {
1165-
this.$set(prefix + key, path[key], constructing, options);
1160+
} else if (valForKey !== void 0) {
1161+
this.$set(pathName, valForKey, constructing, options);
11661162
}
11671163
}
11681164

@@ -2225,16 +2221,17 @@ Document.prototype[documentModifiedPaths] = Document.prototype.modifiedPaths;
22252221

22262222
Document.prototype.isModified = function(paths, modifiedPaths) {
22272223
if (paths) {
2228-
if (!Array.isArray(paths)) {
2229-
paths = paths.indexOf(' ') === -1 ? [paths] : paths.split(' ');
2230-
}
2231-
22322224
const directModifiedPathsObj = this.$__.activePaths.states.modify;
22332225
if (directModifiedPathsObj == null) {
22342226
return false;
22352227
}
2228+
2229+
if (typeof paths === 'string') {
2230+
paths = [paths];
2231+
}
2232+
22362233
for (const path of paths) {
2237-
if (Object.prototype.hasOwnProperty.call(directModifiedPathsObj, path)) {
2234+
if (directModifiedPathsObj[path] != null) {
22382235
return true;
22392236
}
22402237
}
@@ -2668,14 +2665,14 @@ function _getPathsToValidate(doc) {
26682665
const modifiedPaths = doc.modifiedPaths();
26692666
for (const subdoc of subdocs) {
26702667
if (subdoc.$basePath) {
2671-
// Remove child paths for now, because we'll be validating the whole
2672-
// subdoc
26732668
const fullPathToSubdoc = subdoc.$__fullPathWithIndexes();
26742669

2675-
for (const p of paths) {
2676-
if (p == null || p.startsWith(fullPathToSubdoc + '.')) {
2677-
paths.delete(p);
2678-
}
2670+
// Remove child paths for now, because we'll be validating the whole
2671+
// subdoc.
2672+
// The following is a faster take on looping through every path in `paths`
2673+
// and checking if the path starts with `fullPathToSubdoc` re: gh-13191
2674+
for (const modifiedPath of subdoc.modifiedPaths()) {
2675+
paths.delete(fullPathToSubdoc + '.' + modifiedPath);
26792676
}
26802677

26812678
if (doc.$isModified(fullPathToSubdoc, modifiedPaths) &&
@@ -3348,12 +3345,13 @@ Document.prototype.$__reset = function reset() {
33483345
resetArrays.add(array);
33493346
}
33503347
} else {
3351-
if (subdoc.$parent() === this) {
3348+
const parent = subdoc.$parent();
3349+
if (parent === this) {
33523350
this.$__.activePaths.clearPath(subdoc.$basePath);
3353-
} else if (subdoc.$parent() != null && subdoc.$parent().$isSubdocument) {
3351+
} else if (parent != null && parent.$isSubdocument) {
33543352
// If map path underneath subdocument, may end up with a case where
33553353
// map path is modified but parent still needs to be reset. See gh-10295
3356-
subdoc.$parent().$__reset();
3354+
parent.$__reset();
33573355
}
33583356
}
33593357
}
@@ -4070,6 +4068,7 @@ function applyGetters(self, json, options) {
40704068
path = paths[i];
40714069

40724070
const parts = path.split('.');
4071+
40734072
const plen = parts.length;
40744073
const last = plen - 1;
40754074
let branch = json;

lib/helpers/schema/getPath.js

-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ module.exports = function getPath(schema, path) {
1313
if (schematype != null) {
1414
return schematype;
1515
}
16-
1716
const pieces = path.split('.');
1817
let cur = '';
1918
let isArray = false;

lib/schema.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -1530,9 +1530,6 @@ Schema.prototype.indexedPaths = function indexedPaths() {
15301530
*/
15311531

15321532
Schema.prototype.pathType = function(path) {
1533-
// Convert to '.$' to check subpaths re: gh-6405
1534-
const cleanPath = _pathToPositionalSyntax(path);
1535-
15361533
if (this.paths.hasOwnProperty(path)) {
15371534
return 'real';
15381535
}
@@ -1542,6 +1539,10 @@ Schema.prototype.pathType = function(path) {
15421539
if (this.nested.hasOwnProperty(path)) {
15431540
return 'nested';
15441541
}
1542+
1543+
// Convert to '.$' to check subpaths re: gh-6405
1544+
const cleanPath = _pathToPositionalSyntax(path);
1545+
15451546
if (this.subpaths.hasOwnProperty(cleanPath) || this.subpaths.hasOwnProperty(path)) {
15461547
return 'real';
15471548
}

lib/schema/SubdocumentPath.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -160,22 +160,22 @@ SubdocumentPath.prototype.cast = function(val, doc, init, priorVal, options) {
160160
let subdoc;
161161

162162
// Only pull relevant selected paths and pull out the base path
163-
const parentSelected = doc && doc.$__ && doc.$__.selected || {};
163+
const parentSelected = doc && doc.$__ && doc.$__.selected;
164164
const path = this.path;
165-
const selected = Object.keys(parentSelected).reduce((obj, key) => {
165+
const selected = parentSelected == null ? null : Object.keys(parentSelected).reduce((obj, key) => {
166166
if (key.startsWith(path + '.')) {
167167
obj = obj || {};
168168
obj[key.substring(path.length + 1)] = parentSelected[key];
169169
}
170170
return obj;
171171
}, null);
172-
options = Object.assign({}, options, { priorDoc: priorVal });
173172
if (init) {
174173
subdoc = new Constructor(void 0, selected, doc, false, { defaults: false });
175174
delete subdoc.$__.defaults;
176175
subdoc.$init(val);
177176
applyDefaults(subdoc, selected);
178177
} else {
178+
options = Object.assign({}, options, { priorDoc: priorVal });
179179
if (Object.keys(val).length === 0) {
180180
return new Constructor({}, selected, doc, undefined, options);
181181
}

0 commit comments

Comments
 (0)