Merge pull request #5536 from strapi/fix/#4487/filteringDataManyWayError500

Fix deep filtering for manyWay
This commit is contained in:
Alexandre BODIN 2020-03-23 14:03:12 +01:00 committed by GitHub
commit 3e4c9d95d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 199 additions and 30 deletions

View File

@ -79,31 +79,37 @@ const buildJoinsAndFilter = (qb, model, whereClauses) => {
* @param {Object} destinationInfo - destination with which we are making a join
*/
const buildJoin = (qb, assoc, originInfo, destinationInfo) => {
if (assoc.nature === 'manyToMany') {
if (['manyToMany', 'manyWay'].includes(assoc.nature)) {
const joinTableAlias = generateAlias(assoc.tableCollectionName);
let originColumnNameInJoinTable;
if (assoc.nature === 'manyToMany') {
originColumnNameInJoinTable = `${joinTableAlias}.${singular(
destinationInfo.model.attributes[assoc.via].attribute
)}_${destinationInfo.model.attributes[assoc.via].column}`;
} else if (assoc.nature === 'manyWay') {
originColumnNameInJoinTable = `${joinTableAlias}.${singular(
originInfo.model.collectionName
)}_${originInfo.model.primaryKey}`;
}
qb.leftJoin(
`${originInfo.model.databaseName}.${assoc.tableCollectionName} AS ${joinTableAlias}`,
`${joinTableAlias}.${singular(
destinationInfo.model.attributes[assoc.via].attribute
)}_${destinationInfo.model.attributes[assoc.via].column}`,
originColumnNameInJoinTable,
`${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 {
const externalKey =
assoc.type === 'collection'
? `${destinationInfo.alias}.${assoc.via ||
destinationInfo.model.primaryKey}`
? `${destinationInfo.alias}.${assoc.via || destinationInfo.model.primaryKey}`
: `${destinationInfo.alias}.${destinationInfo.model.primaryKey}`;
const internalKey =
@ -116,6 +122,7 @@ const buildJoinsAndFilter = (qb, model, whereClauses) => {
externalKey,
internalKey
);
}
};
/**
@ -208,9 +215,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 +263,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;

View File

@ -236,6 +236,7 @@ module.exports = ({ models, target }, ctx) => {
if (otherKey === foreignKey) {
otherKey = `related_${otherKey}`;
details.attribute = `related_${details.attribute}`;
}
loadedModel[name] = function() {

View File

@ -0,0 +1,164 @@
// 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,
},
collector_friends: {
nature: 'manyWay',
target: '__self__',
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],
collector_friends: [data.collectors[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]);
});
});
describe('Filter on a self manyWay relation', () => {
test('Should return 1 result', async () => {
const res = await rq({
method: 'GET',
url: '/collectors',
qs: {
'collector_friends.name': data.collectors[0].name,
},
});
console.log('res', JSON.stringify(res.body, null, 2));
expect(Array.isArray(res.body)).toBe(true);
expect(res.body.length).toBe(1);
expect(res.body[0]).toMatchObject(data.collectors[1]);
});
});
});