Skip to content

Commit

Permalink
fix(query): correctly cast embedded discriminator paths when discrimi…
Browse files Browse the repository at this point in the history
…nator key is specified in array filter

Fix #9977
  • Loading branch information
vkarpov15 committed Mar 16, 2021
1 parent 9fcb2db commit fba3457
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 23 deletions.
4 changes: 2 additions & 2 deletions lib/helpers/query/castUpdate.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ function walkUpdatePath(schema, obj, op, options, context, filter, pref) {
schematype = schema._getSchema(prefix + key);

if (schematype == null) {
const _res = getEmbeddedDiscriminatorPath(schema, obj, filter, prefix + key);
const _res = getEmbeddedDiscriminatorPath(schema, obj, filter, prefix + key, options);
if (_res.schematype != null) {
schematype = _res.schematype;
}
Expand Down Expand Up @@ -324,7 +324,7 @@ function walkUpdatePath(schema, obj, op, options, context, filter, pref) {
// If no schema type, check for embedded discriminators because the
// filter or update may imply an embedded discriminator type. See #8378
if (schematype == null) {
const _res = getEmbeddedDiscriminatorPath(schema, obj, filter, checkPath);
const _res = getEmbeddedDiscriminatorPath(schema, obj, filter, checkPath, options);
if (_res.schematype != null) {
schematype = _res.schematype;
pathDetails = _res.type;
Expand Down
18 changes: 17 additions & 1 deletion lib/helpers/query/getEmbeddedDiscriminatorPath.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,23 @@
const cleanPositionalOperators = require('../schema/cleanPositionalOperators');
const get = require('../get');
const getDiscriminatorByValue = require('../discriminator/getDiscriminatorByValue');
const updatedPathsByArrayFilter = require('../update/updatedPathsByArrayFilter');

/*!
* Like `schema.path()`, except with a document, because impossible to
* determine path type without knowing the embedded discriminator key.
*/

module.exports = function getEmbeddedDiscriminatorPath(schema, update, filter, path) {
module.exports = function getEmbeddedDiscriminatorPath(schema, update, filter, path, options) {
const parts = path.split('.');
let schematype = null;
let type = 'adhocOrUndefined';

filter = filter || {};
update = update || {};
const arrayFilters = options != null && Array.isArray(options.arrayFilters) ?
options.arrayFilters : [];
const updatedPathsByFilter = updatedPathsByArrayFilter(update);

for (let i = 0; i < parts.length; ++i) {
const subpath = cleanPositionalOperators(parts.slice(0, i + 1).join('.'));
Expand All @@ -39,6 +43,7 @@ module.exports = function getEmbeddedDiscriminatorPath(schema, update, filter, p
if (discriminatorFilterPath in filter) {
discriminatorKey = filter[discriminatorFilterPath];
}

const wrapperPath = subpath.replace(/\.\d+$/, '');
if (schematype.$isMongooseDocumentArrayElement &&
get(filter[wrapperPath], '$elemMatch.' + key) != null) {
Expand All @@ -49,6 +54,17 @@ module.exports = function getEmbeddedDiscriminatorPath(schema, update, filter, p
discriminatorKey = update[discriminatorValuePath];
}

for (const filterKey of Object.keys(updatedPathsByFilter)) {
const schemaKey = updatedPathsByFilter[filterKey] + '.' + key;
const arrayFilterKey = filterKey + '.' + key;
if (schemaKey === discriminatorFilterPath) {
const filter = arrayFilters.find(filter => filter.hasOwnProperty(arrayFilterKey));
if (filter != null) {
discriminatorKey = filter[arrayFilterKey];
}
}
}

if (discriminatorKey == null) {
continue;
}
Expand Down
21 changes: 2 additions & 19 deletions lib/helpers/update/castArrayFilters.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
const castFilterPath = require('../query/castFilterPath');
const cleanPositionalOperators = require('../schema/cleanPositionalOperators');
const getPath = require('../schema/getPath');
const modifiedPaths = require('./modifiedPaths');
const updatedPathsByArrayFilter = require('./updatedPathsByArrayFilter');

module.exports = function castArrayFilters(query) {
const arrayFilters = query.options.arrayFilters;
Expand All @@ -15,24 +15,7 @@ module.exports = function castArrayFilters(query) {
const schema = query.schema;
const strictQuery = schema.options.strictQuery;

const updatedPaths = modifiedPaths(update);

const updatedPathsByFilter = Object.keys(updatedPaths).reduce((cur, path) => {
const matches = path.match(/\$\[[^\]]+\]/g);
if (matches == null) {
return cur;
}
for (const match of matches) {
const firstMatch = path.indexOf(match);
if (firstMatch !== path.lastIndexOf(match)) {
throw new Error(`Path '${path}' contains the same array filter multiple times`);
}
cur[match.substring(2, match.length - 1)] = path.
substr(0, firstMatch - 1).
replace(/\$\[[^\]]+\]/g, '0');
}
return cur;
}, {});
const updatedPathsByFilter = updatedPathsByArrayFilter(update);

for (const filter of arrayFilters) {
if (filter == null) {
Expand Down
24 changes: 24 additions & 0 deletions lib/helpers/update/updatedPathsByArrayFilter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use strict';

const modifiedPaths = require('./modifiedPaths');

module.exports = function updatedPathsByArrayFilter(update) {
const updatedPaths = modifiedPaths(update);

return Object.keys(updatedPaths).reduce((cur, path) => {
const matches = path.match(/\$\[[^\]]+\]/g);
if (matches == null) {
return cur;
}
for (const match of matches) {
const firstMatch = path.indexOf(match);
if (firstMatch !== path.lastIndexOf(match)) {
throw new Error(`Path '${path}' contains the same array filter multiple times`);
}
cur[match.substring(2, match.length - 1)] = path.
substr(0, firstMatch - 1).
replace(/\$\[[^\]]+\]/g, '0');
}
return cur;
}, {});
};
3 changes: 2 additions & 1 deletion lib/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -4586,7 +4586,8 @@ Query.prototype._castUpdate = function _castUpdate(obj, overwrite) {
strict: strict,
omitUndefined,
useNestedStrict: useNestedStrict,
upsert: upsert
upsert: upsert,
arrayFilters: this.options.arrayFilters
}, this, this._conditions);
};

Expand Down

0 comments on commit fba3457

Please sign in to comment.