diff --git a/packages/core/database/src/__tests__/filter.test.ts b/packages/core/database/src/__tests__/filter.test.ts index 3c0a7faf8..eab102638 100644 --- a/packages/core/database/src/__tests__/filter.test.ts +++ b/packages/core/database/src/__tests__/filter.test.ts @@ -14,6 +14,60 @@ describe('filter', () => { await db.close(); }); + it('should filter by belongs to many association', async () => { + const A = db.collection({ + name: 'a', + fields: [ + { type: 'string', name: 'name' }, + { type: 'hasMany', name: 'b', target: 'b' }, + ], + }); + + const B = db.collection({ + name: 'b', + fields: [ + { type: 'string', name: 'name' }, + { type: 'belongsTo', name: 'c', target: 'c' }, + { type: 'belongsTo', name: 'd', target: 'd' }, + ], + }); + + const C = db.collection({ + name: 'c', + fields: [{ type: 'string', name: 'name' }], + }); + + const D = db.collection({ + name: 'd', + fields: [{ type: 'string', name: 'name' }], + }); + + await db.sync(); + + const findArgs = { + filter: { + $and: [{ b: { c: { name: { $eq: 'c1' } } } }, { b: { d: { name: { $eq: 'd1' } } } }], + }, + }; + + const findOptions = A.repository.buildQueryOptions(findArgs); + + const include = findOptions.include; + + const associationB = include.find((i) => i.association === 'b'); + expect(associationB).toBeDefined(); + expect(associationB.include).toHaveLength(2); + + let err; + try { + await A.repository.find({ ...findArgs }); + } catch (e) { + err = e; + } + + expect(err).toBeUndefined(); + }); + it('should filter by hasMany association field', async () => { const DeptCollection = db.collection({ name: 'depts', diff --git a/packages/core/database/src/filter-parser.ts b/packages/core/database/src/filter-parser.ts index c14048ede..34b60194c 100644 --- a/packages/core/database/src/filter-parser.ts +++ b/packages/core/database/src/filter-parser.ts @@ -164,11 +164,15 @@ export default class FilterParser { debug('associationKeys %o', associationKeys); - // set sequelize include option - _.set(include, firstKey, { - association: firstKey, - attributes: [], // out put empty fields by default - }); + const existInclude = _.get(include, firstKey); + + if (!existInclude) { + // set sequelize include option + _.set(include, firstKey, { + association: firstKey, + attributes: [], // out put empty fields by default + }); + } // association target model let target = associations[firstKey].target; @@ -192,10 +196,14 @@ export default class FilterParser { assoc.push(associationKey); }); - _.set(include, assoc, { - association: attr, - attributes: [], - }); + const existInclude = _.get(include, assoc); + if (!existInclude) { + _.set(include, assoc, { + association: attr, + attributes: [], + }); + } + target = target.associations[attr].target; } else { throw new Error(`${attr} neither ${firstKey}'s association nor ${firstKey}'s attribute`);