diff --git a/lib/helpers/update/castArrayFilters.js b/lib/helpers/update/castArrayFilters.js index 76ce94b03b3..163e33be14c 100644 --- a/lib/helpers/update/castArrayFilters.js +++ b/lib/helpers/update/castArrayFilters.js @@ -37,14 +37,36 @@ function _castArrayFilters(arrayFilters, schema, strictQuery, updatedPathsByFilt if (filter == null) { throw new Error(`Got null array filter in ${arrayFilters}`); } - for (const key of Object.keys(filter)) { - if (key === '$and' || key === '$or') { + const keys = Object.keys(filter).filter(key => filter[key] != null); + if (keys.length === 0) { + continue; + } + + const firstKey = keys[0]; + if (firstKey === '$and' || firstKey === '$or') { + for (const key of keys) { _castArrayFilters(filter[key], schema, strictQuery, updatedPathsByFilter, query); - continue; - } - if (filter[key] == null) { - continue; } + continue; + } + const dot = firstKey.indexOf('.'); + const filterWildcardPath = dot === -1 ? firstKey : firstKey.substring(0, dot); + if (updatedPathsByFilter[filterWildcardPath] == null) { + continue; + } + const baseFilterPath = cleanPositionalOperators( + updatedPathsByFilter[filterWildcardPath] + ); + + const baseSchematype = getPath(schema, baseFilterPath); + let filterBaseSchema = baseSchematype != null ? baseSchematype.schema : null; + if (filterBaseSchema != null && + filterBaseSchema.discriminators != null && + filter[filterWildcardPath + '.' + filterBaseSchema.options.discriminatorKey]) { + filterBaseSchema = filterBaseSchema.discriminators[filter[filterWildcardPath + '.' + filterBaseSchema.options.discriminatorKey]] || filterBaseSchema; + } + + for (const key of keys) { if (updatedPathsByFilter[key] === null) { continue; } @@ -52,21 +74,25 @@ function _castArrayFilters(arrayFilters, schema, strictQuery, updatedPathsByFilt continue; } const dot = key.indexOf('.'); - let filterPath = dot === -1 ? - updatedPathsByFilter[key] + '.0' : - updatedPathsByFilter[key.substring(0, dot)] + '.0' + key.substring(dot); - if (filterPath == null) { - throw new Error(`Filter path not found for ${key}`); + + let filterPathRelativeToBase = dot === -1 ? null : key.substring(dot); + let schematype; + if (filterPathRelativeToBase == null || filterBaseSchema == null) { + schematype = baseSchematype; + } else { + // If there are multiple array filters in the path being updated, make sure + // to replace them so we can get the schema path. + filterPathRelativeToBase = cleanPositionalOperators(filterPathRelativeToBase); + schematype = getPath(filterBaseSchema, filterPathRelativeToBase); } - // If there are multiple array filters in the path being updated, make sure - // to replace them so we can get the schema path. - filterPath = cleanPositionalOperators(filterPath); - const schematype = getPath(schema, filterPath); if (schematype == null) { if (!strictQuery) { return; } + const filterPath = filterPathRelativeToBase == null ? + baseFilterPath + '.0' : + baseFilterPath + '.0' + filterPathRelativeToBase; // For now, treat `strictQuery = true` and `strictQuery = 'throw'` as // equivalent for casting array filters. `strictQuery = true` doesn't // quite work in this context because we never want to silently strip out diff --git a/test/helpers/update.castArrayFilters.test.js b/test/helpers/update.castArrayFilters.test.js index 6baad3f8474..d132663b590 100644 --- a/test/helpers/update.castArrayFilters.test.js +++ b/test/helpers/update.castArrayFilters.test.js @@ -262,4 +262,53 @@ describe('castArrayFilters', function() { castArrayFilters(q); }, /Could not find path "arr\.0\.notInSchema" in schema/); }); + + it('handles embedded discriminators (gh-12565)', function() { + const elementSchema = new Schema( + { elementType: String }, + { discriminatorKey: 'elementType' } + ); + const versionSchema = new Schema( + { version: Number, elements: [elementSchema] }, + { strictQuery: 'throw' } + ); + versionSchema.path('elements').discriminator( + 'Graph', + new Schema({ + number: Number, + curves: [{ line: { label: String, type: String, number: String, latLong: [Number], controller: String } }] + }) + ); + + const testSchema = new Schema({ versions: [versionSchema] }); + + const q = new Query(); + q.schema = testSchema; + + const p = { + $push: { + 'versions.$[version].elements.$[element].curves': { + line: { + label: 'CC110_Ligne 02', + type: 'numerique', + number: '30', + latLong: [44, 8], + controller: 'CC110' + } + } + } + }; + const opts = { + arrayFilters: [ + { + 'element.elementType': 'Graph', + 'element.number': '1' + } + ] + }; + q.updateOne({}, p, opts); + castArrayFilters(q); + + assert.strictEqual(q.options.arrayFilters[0]['element.number'], 1); + }); });