diff --git a/lib/helpers/query/castUpdate.js b/lib/helpers/query/castUpdate.js index 1ee20fb41f2..29b16c9824d 100644 --- a/lib/helpers/query/castUpdate.js +++ b/lib/helpers/query/castUpdate.js @@ -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; } @@ -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; diff --git a/lib/helpers/query/getEmbeddedDiscriminatorPath.js b/lib/helpers/query/getEmbeddedDiscriminatorPath.js index 395ea75c4bf..f4dc839b9c7 100644 --- a/lib/helpers/query/getEmbeddedDiscriminatorPath.js +++ b/lib/helpers/query/getEmbeddedDiscriminatorPath.js @@ -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('.')); @@ -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) { @@ -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; } diff --git a/lib/helpers/update/castArrayFilters.js b/lib/helpers/update/castArrayFilters.js index fef89346393..a9992f2a507 100644 --- a/lib/helpers/update/castArrayFilters.js +++ b/lib/helpers/update/castArrayFilters.js @@ -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; @@ -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) { diff --git a/lib/helpers/update/updatedPathsByArrayFilter.js b/lib/helpers/update/updatedPathsByArrayFilter.js new file mode 100644 index 00000000000..7cbae298402 --- /dev/null +++ b/lib/helpers/update/updatedPathsByArrayFilter.js @@ -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; + }, {}); +}; \ No newline at end of file diff --git a/lib/query.js b/lib/query.js index ba43a98c912..86e2e627a22 100644 --- a/lib/query.js +++ b/lib/query.js @@ -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); };