From 8aa50cd80c8317389aed694054be42e272e26431 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20No=C3=ABl?= Date: Wed, 18 Mar 2020 17:15:24 +0100 Subject: [PATCH] Fix deep filtering for manyWay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Pierre Noël --- .../lib/buildQuery.js | 69 +++++---- .../__tests__/deepFiltering.test.e2e.js | 140 ++++++++++++++++++ 2 files changed, 180 insertions(+), 29 deletions(-) create mode 100644 packages/strapi/__tests__/deepFiltering.test.e2e.js diff --git a/packages/strapi-connector-bookshelf/lib/buildQuery.js b/packages/strapi-connector-bookshelf/lib/buildQuery.js index 115693b70a..b7c8c6e986 100644 --- a/packages/strapi-connector-bookshelf/lib/buildQuery.js +++ b/packages/strapi-connector-bookshelf/lib/buildQuery.js @@ -84,38 +84,52 @@ const buildJoinsAndFilter = (qb, model, whereClauses) => { qb.leftJoin( `${originInfo.model.databaseName}.${assoc.tableCollectionName} AS ${joinTableAlias}`, - `${joinTableAlias}.${singular( - destinationInfo.model.attributes[assoc.via].attribute - )}_${destinationInfo.model.attributes[assoc.via].column}`, + `${joinTableAlias}.${singular(destinationInfo.model.attributes[assoc.via].attribute)}_${ + destinationInfo.model.attributes[assoc.via].column + }`, `${originInfo.alias}.${originInfo.model.primaryKey}` ); qb.leftJoin( `${destinationInfo.model.databaseName}.${destinationInfo.model.collectionName} AS ${destinationInfo.alias}`, - `${joinTableAlias}.${singular( - originInfo.model.attributes[assoc.alias].attribute - )}_${originInfo.model.attributes[assoc.alias].column}`, + `${joinTableAlias}.${singular(originInfo.model.attributes[assoc.alias].attribute)}_${ + originInfo.model.attributes[assoc.alias].column + }`, `${destinationInfo.alias}.${destinationInfo.model.primaryKey}` ); - return; + } else if (assoc.nature === 'manyWay') { + const joinTableAlias = generateAlias(assoc.tableCollectionName); + + qb.leftJoin( + `${originInfo.model.databaseName}.${assoc.tableCollectionName} AS ${joinTableAlias}`, + `${joinTableAlias}.${singular(originInfo.alias)}_id`, + `${originInfo.alias}.${originInfo.model.primaryKey}` + ); + + qb.leftJoin( + `${destinationInfo.model.databaseName}.${destinationInfo.model.collectionName} AS ${destinationInfo.alias}`, + `${joinTableAlias}.${singular(originInfo.model.attributes[assoc.alias].attribute)}_${ + originInfo.model.attributes[assoc.alias].column + }`, + `${destinationInfo.alias}.${destinationInfo.model.primaryKey}` + ); + } else { + const externalKey = + assoc.type === 'collection' + ? `${destinationInfo.alias}.${assoc.via || destinationInfo.model.primaryKey}` + : `${destinationInfo.alias}.${destinationInfo.model.primaryKey}`; + + const internalKey = + assoc.type === 'collection' + ? `${originInfo.alias}.${originInfo.model.primaryKey}` + : `${originInfo.alias}.${assoc.alias}`; + + qb.leftJoin( + `${destinationInfo.model.databaseName}.${destinationInfo.model.collectionName} AS ${destinationInfo.alias}`, + externalKey, + internalKey + ); } - - const externalKey = - assoc.type === 'collection' - ? `${destinationInfo.alias}.${assoc.via || - destinationInfo.model.primaryKey}` - : `${destinationInfo.alias}.${destinationInfo.model.primaryKey}`; - - const internalKey = - assoc.type === 'collection' - ? `${originInfo.alias}.${originInfo.model.primaryKey}` - : `${originInfo.alias}.${assoc.alias}`; - - qb.leftJoin( - `${destinationInfo.model.databaseName}.${destinationInfo.model.collectionName} AS ${destinationInfo.alias}`, - externalKey, - internalKey - ); }; /** @@ -208,9 +222,7 @@ const buildWhereClause = ({ qb, field, operator, value }) => { if (Array.isArray(value) && !['in', 'nin'].includes(operator)) { return qb.where(subQb => { for (let val of value) { - subQb.orWhere(q => - buildWhereClause({ qb: q, field, operator, value: val }) - ); + subQb.orWhere(q => buildWhereClause({ qb: q, field, operator, value: val })); } }); } @@ -258,7 +270,6 @@ const findModelByAssoc = assoc => { return models[assoc.collection || assoc.model]; }; -const findAssoc = (model, key) => - model.associations.find(assoc => assoc.alias === key); +const findAssoc = (model, key) => model.associations.find(assoc => assoc.alias === key); module.exports = buildQuery; diff --git a/packages/strapi/__tests__/deepFiltering.test.e2e.js b/packages/strapi/__tests__/deepFiltering.test.e2e.js new file mode 100644 index 0000000000..e320f761b0 --- /dev/null +++ b/packages/strapi/__tests__/deepFiltering.test.e2e.js @@ -0,0 +1,140 @@ +// Test an API with all the possible filed types and simple filterings (no deep filtering, no relations) + +const { registerAndLogin } = require('../../../test/helpers/auth'); +const createModelsUtils = require('../../../test/helpers/models'); +const { createAuthRequest } = require('../../../test/helpers/request'); + +let rq; +let modelsUtils; +let data = { + paniniCards: [], + collectors: [], +}; + +const paniniCard = { + name: 'paniniCard', + kind: 'collectionType', + attributes: { + name: { + type: 'string', + }, + }, +}; + +const collector = { + name: 'collector', + kind: 'collectionType', + attributes: { + name: { + type: 'string', + }, + panini_cards: { + nature: 'manyWay', + target: 'application::panini-card.panini-card', + unique: false, + }, + }, +}; + +const paniniCardFixtures = [ + { + name: 'Hugo LLORIS', + }, + { + name: 'Samuel UMTITI', + }, +]; + +async function createFixtures() { + for (let paniniCard of paniniCardFixtures) { + const res = await rq({ + method: 'POST', + url: '/panini-cards', + body: paniniCard, + }); + + data.paniniCards.push(res.body); + } + + const collector1Res = await rq({ + method: 'POST', + url: '/collectors', + body: { + name: 'Bernard', + panini_cards: [data.paniniCards[0].id, data.paniniCards[1].id], + }, + }); + data.collectors.push(collector1Res.body); + + const collector2Res = await rq({ + method: 'POST', + url: '/collectors', + body: { + name: 'Isabelle', + panini_cards: [data.paniniCards[0].id], + }, + }); + data.collectors.push(collector2Res.body); +} + +async function deleteFixtures() { + for (let paniniCard of data.paniniCards) { + await rq({ + method: 'DELETE', + url: `/panini-cards/${paniniCard.id}`, + }); + } + for (let collector of data.collectors) { + await rq({ + method: 'DELETE', + url: `/collectors/${collector.id}`, + }); + } +} + +describe('Deep Filtering API', () => { + beforeAll(async () => { + const token = await registerAndLogin(); + rq = createAuthRequest(token); + + modelsUtils = createModelsUtils({ rq }); + await modelsUtils.createContentTypes([paniniCard, collector]); + await createFixtures(); + }, 60000); + + afterAll(async () => { + await deleteFixtures(); + await modelsUtils.deleteContentTypes(['collector', 'panini-card']); + }, 60000); + + describe('Filter on a manyWay relation', () => { + test('Should return 2 results', async () => { + const res = await rq({ + method: 'GET', + url: '/collectors', + qs: { + 'panini_cards.name': data.paniniCards[0].name, + }, + }); + + expect(Array.isArray(res.body)).toBe(true); + expect(res.body.length).toBe(2); + expect(res.body[0]).toMatchObject(data.collectors[0]); + expect(res.body[1]).toMatchObject(data.collectors[1]); + }); + + test('Should return 1 result', async () => { + const res = await rq({ + method: 'GET', + url: '/collectors', + qs: { + 'panini_cards.name': data.paniniCards[1].name, + }, + }); + + expect(Array.isArray(res.body)).toBe(true); + expect(res.body.length).toBe(1); + expect(res.body[0]).toMatchObject(data.collectors[0]); + }); + }); +});