Skip to content

Commit dd39133

Browse files
authored
Merge pull request #15691 from Automattic/vkarpov15/gh-15672-3
perf(setDefaultsOnInsert): avoid computing all modified paths when running setDefaultsOnInsert and update validators, only calculate if there are defaults to set
2 parents 9ddebb8 + d13be54 commit dd39133

File tree

3 files changed

+62
-34
lines changed

3 files changed

+62
-34
lines changed

lib/helpers/setDefaultsOnInsert.js

Lines changed: 59 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
'use strict';
2-
const modifiedPaths = require('./common').modifiedPaths;
32
const get = require('./get');
43

54
/**
@@ -29,21 +28,16 @@ module.exports = function(filter, schema, castedDoc, options) {
2928
const updatedKeys = {};
3029
const updatedValues = {};
3130
const numKeys = keys.length;
32-
const modified = {};
3331

3432
let hasDollarUpdate = false;
3533

3634
for (let i = 0; i < numKeys; ++i) {
37-
if (keys[i].startsWith('$')) {
38-
modifiedPaths(castedDoc[keys[i]], '', modified);
35+
if (keys[i].charAt(0) === '$') {
3936
hasDollarUpdate = true;
37+
break;
4038
}
4139
}
4240

43-
if (!hasDollarUpdate) {
44-
modifiedPaths(castedDoc, '', modified);
45-
}
46-
4741
const paths = Object.keys(filter);
4842
const numPaths = paths.length;
4943
for (let i = 0; i < numPaths; ++i) {
@@ -54,7 +48,7 @@ module.exports = function(filter, schema, castedDoc, options) {
5448
const numConditionKeys = conditionKeys.length;
5549
let hasDollarKey = false;
5650
for (let j = 0; j < numConditionKeys; ++j) {
57-
if (conditionKeys[j].startsWith('$')) {
51+
if (conditionKeys[j].charAt(0) === '$') {
5852
hasDollarKey = true;
5953
break;
6054
}
@@ -64,7 +58,6 @@ module.exports = function(filter, schema, castedDoc, options) {
6458
}
6559
}
6660
updatedKeys[path] = true;
67-
modified[path] = true;
6861
}
6962

7063
if (options && options.overwrite && !hasDollarUpdate) {
@@ -79,16 +72,17 @@ module.exports = function(filter, schema, castedDoc, options) {
7972
return;
8073
}
8174
const def = schemaType.getDefault(null, true);
82-
if (isModified(modified, path)) {
83-
return;
84-
}
8575
if (typeof def === 'undefined') {
8676
return;
8777
}
88-
if (schemaType.splitPath().includes('$*')) {
78+
const pathPieces = schemaType.splitPath();
79+
if (pathPieces.includes('$*')) {
8980
// Skip defaults underneath maps. We should never do `$setOnInsert` on a path with `$*`
9081
return;
9182
}
83+
if (isModified(castedDoc, updatedKeys, path, pathPieces, hasDollarUpdate)) {
84+
return;
85+
}
9286

9387
castedDoc = castedDoc || {};
9488
castedDoc.$setOnInsert = castedDoc.$setOnInsert || {};
@@ -101,31 +95,66 @@ module.exports = function(filter, schema, castedDoc, options) {
10195
return castedDoc;
10296
};
10397

104-
function isModified(modified, path) {
105-
if (modified[path]) {
98+
function isModified(castedDoc, updatedKeys, path, pathPieces, hasDollarUpdate) {
99+
// Check if path is in filter (updatedKeys)
100+
if (updatedKeys[path]) {
106101
return true;
107102
}
108103

109-
// Is any parent path of `path` modified?
110-
const sp = path.split('.');
111-
let cur = sp[0];
112-
for (let i = 1; i < sp.length; ++i) {
113-
if (modified[cur]) {
104+
// Check if any parent path is in filter
105+
let cur = pathPieces[0];
106+
for (let i = 1; i < pathPieces.length; ++i) {
107+
if (updatedKeys[cur]) {
114108
return true;
115109
}
116-
cur += '.' + sp[i];
110+
cur += '.' + pathPieces[i];
117111
}
118112

119-
// Is any child of `path` modified?
120-
const modifiedKeys = Object.keys(modified);
121-
if (modifiedKeys.length) {
122-
const parentPath = path + '.';
123-
124-
for (const modifiedPath of modifiedKeys) {
125-
if (modifiedPath.slice(0, path.length + 1) === parentPath) {
126-
return true;
113+
// Check if path is modified in the update
114+
if (hasDollarUpdate) {
115+
// Check each update operator
116+
for (const key in castedDoc) {
117+
if (key.charAt(0) === '$') {
118+
if (pathExistsInUpdate(castedDoc[key], path, pathPieces)) {
119+
return true;
120+
}
127121
}
128122
}
123+
} else {
124+
// No dollar operators, check the castedDoc directly
125+
if (pathExistsInUpdate(castedDoc, path, pathPieces)) {
126+
return true;
127+
}
128+
}
129+
130+
return false;
131+
}
132+
133+
function pathExistsInUpdate(update, targetPath, pathPieces) {
134+
if (update == null || typeof update !== 'object') {
135+
return false;
136+
}
137+
138+
// Check exact match
139+
if (update.hasOwnProperty(targetPath)) {
140+
return true;
141+
}
142+
143+
// Check if any parent path exists
144+
let cur = pathPieces[0];
145+
for (let i = 1; i < pathPieces.length; ++i) {
146+
if (update.hasOwnProperty(cur)) {
147+
return true;
148+
}
149+
cur += '.' + pathPieces[i];
150+
}
151+
152+
// Check if any child path exists (e.g., path is "a" and update has "a.b")
153+
const prefix = targetPath + '.';
154+
for (const key in update) {
155+
if (key.startsWith(prefix)) {
156+
return true;
157+
}
129158
}
130159

131160
return false;

lib/helpers/updateValidators.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
const ValidationError = require('../error/validation');
88
const cleanPositionalOperators = require('./schema/cleanPositionalOperators');
99
const flatten = require('./common').flatten;
10-
const modifiedPaths = require('./common').modifiedPaths;
1110

1211
/**
1312
* Applies validators and defaults to update and findOneAndUpdate operations,
@@ -29,7 +28,6 @@ module.exports = function(query, schema, castedDoc, options, callback) {
2928
const arrayAtomicUpdates = {};
3029
const numKeys = keys.length;
3130
let hasDollarUpdate = false;
32-
const modified = {};
3331
let currentUpdate;
3432
let key;
3533
let i;
@@ -51,7 +49,6 @@ module.exports = function(query, schema, castedDoc, options, callback) {
5149
}
5250
continue;
5351
}
54-
modifiedPaths(castedDoc[keys[i]], '', modified);
5552
const flat = flatten(castedDoc[keys[i]], null, null, schema);
5653
const paths = Object.keys(flat);
5754
const numPaths = paths.length;
@@ -76,7 +73,6 @@ module.exports = function(query, schema, castedDoc, options, callback) {
7673
}
7774

7875
if (!hasDollarUpdate) {
79-
modifiedPaths(castedDoc, '', modified);
8076
updatedValues = flatten(castedDoc, null, null, schema);
8177
updatedKeys = Object.keys(updatedValues);
8278
}

lib/schemaType.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1174,6 +1174,9 @@ SchemaType.prototype.ref = function(ref) {
11741174

11751175
SchemaType.prototype.getDefault = function(scope, init, options) {
11761176
let ret;
1177+
if (this.defaultValue == null) {
1178+
return this.defaultValue;
1179+
}
11771180
if (typeof this.defaultValue === 'function') {
11781181
if (
11791182
this.defaultValue === Date.now ||

0 commit comments

Comments
 (0)