2019-03-13 19:27:18 +01:00
|
|
|
const _ = require('lodash');
|
2019-03-28 12:13:32 +01:00
|
|
|
const pluralize = require('pluralize');
|
2019-03-13 19:27:18 +01:00
|
|
|
|
|
|
|
const buildQuery = ({ model, filters }) => qb => {
|
|
|
|
if (_.has(filters, 'where') && Array.isArray(filters.where)) {
|
2019-03-22 12:16:09 +01:00
|
|
|
// build path with aliases and return a mapping of the paths with there aliases;
|
|
|
|
|
2019-03-13 19:27:18 +01:00
|
|
|
// build joins
|
2019-03-22 12:16:09 +01:00
|
|
|
buildQueryJoins(qb, { model, where: filters.where });
|
2019-03-13 19:27:18 +01:00
|
|
|
|
|
|
|
// apply filters
|
|
|
|
filters.where.forEach(whereClause => {
|
|
|
|
const { association, model: fieldModel, attributeKey } = getAssociationFromFieldKey(
|
|
|
|
model,
|
|
|
|
whereClause.field
|
|
|
|
);
|
|
|
|
|
|
|
|
let fieldKey = `${fieldModel.collectionName}.${attributeKey}`;
|
|
|
|
|
|
|
|
if (association && attributeKey === whereClause.field) {
|
|
|
|
fieldKey = `${fieldModel.collectionName}.${fieldModel.primaryKey}`;
|
|
|
|
}
|
|
|
|
|
2019-03-22 12:16:09 +01:00
|
|
|
buildWhereClause({
|
|
|
|
qb,
|
2019-03-13 19:27:18 +01:00
|
|
|
...whereClause,
|
|
|
|
field: fieldKey,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_.has(filters, 'sort')) {
|
|
|
|
qb.orderBy(
|
|
|
|
filters.sort.map(({ field, order }) => ({
|
|
|
|
column: field,
|
|
|
|
order,
|
|
|
|
}))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_.has(filters, 'start')) {
|
|
|
|
qb.offset(filters.start);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_.has(filters, 'limit') && filters.limit >= 0) {
|
|
|
|
qb.limit(filters.limit);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-03-22 12:16:09 +01:00
|
|
|
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 }));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-03-13 19:27:18 +01:00
|
|
|
switch (operator) {
|
|
|
|
case 'eq':
|
|
|
|
return qb.where(field, value);
|
|
|
|
case 'ne':
|
|
|
|
return qb.where(field, '!=', value);
|
|
|
|
case 'lt':
|
|
|
|
return qb.where(field, '<', value);
|
|
|
|
case 'lte':
|
|
|
|
return qb.where(field, '<=', value);
|
|
|
|
case 'gt':
|
|
|
|
return qb.where(field, '>', value);
|
|
|
|
case 'gte':
|
|
|
|
return qb.where(field, '>=', value);
|
|
|
|
case 'in':
|
|
|
|
return qb.whereIn(field, Array.isArray(value) ? value : [value]);
|
|
|
|
case 'nin':
|
2019-03-22 12:16:09 +01:00
|
|
|
return qb.whereNotIn(field, Array.isArray(value) ? value : [value]);
|
2019-03-13 19:27:18 +01:00
|
|
|
case 'contains': {
|
|
|
|
return qb.whereRaw('LOWER(??) LIKE LOWER(?)', [field, `%${value}%`]);
|
|
|
|
}
|
|
|
|
case 'ncontains':
|
|
|
|
return qb.whereRaw('LOWER(??) NOT LIKE LOWER(?)', [field, `%${value}%`]);
|
|
|
|
case 'containss':
|
|
|
|
return qb.where(field, 'like', `%${value}%`);
|
|
|
|
case 'ncontainss':
|
|
|
|
return qb.whereNot(field, 'like', `%${value}%`);
|
|
|
|
|
|
|
|
default:
|
2019-03-22 12:16:09 +01:00
|
|
|
throw new Error(`Unhandled whereClause : ${field} ${operator} ${value}`);
|
2019-03-13 19:27:18 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const extractRelationsFromWhere = where => {
|
|
|
|
return where
|
|
|
|
.map(({ field }) => {
|
|
|
|
const parts = field.split('.');
|
|
|
|
return parts.length === 1 ? field : _.initial(parts).join('.');
|
|
|
|
})
|
|
|
|
.sort()
|
|
|
|
.reverse()
|
|
|
|
.reduce((acc, currentValue) => {
|
|
|
|
const alreadyPopulated = _.some(acc, item => _.startsWith(item, currentValue));
|
|
|
|
if (!alreadyPopulated) {
|
|
|
|
acc.push(currentValue);
|
|
|
|
}
|
|
|
|
return acc;
|
|
|
|
}, []);
|
|
|
|
};
|
|
|
|
|
2019-03-22 12:16:09 +01:00
|
|
|
const getAssociationFromFieldKey = (model, fieldKey) => {
|
|
|
|
let tmpModel = model;
|
2019-03-13 19:27:18 +01:00
|
|
|
let association;
|
|
|
|
let attributeKey;
|
|
|
|
|
|
|
|
const parts = fieldKey.split('.');
|
|
|
|
|
|
|
|
for (let key of parts) {
|
|
|
|
attributeKey = key;
|
2019-03-22 12:16:09 +01:00
|
|
|
association = tmpModel.associations.find(ast => ast.alias === key);
|
2019-03-13 19:27:18 +01:00
|
|
|
if (association) {
|
2019-03-22 12:16:09 +01:00
|
|
|
tmpModel = findModelByAssoc(association);
|
2019-03-13 19:27:18 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
association,
|
2019-03-22 12:16:09 +01:00
|
|
|
model: tmpModel,
|
2019-03-13 19:27:18 +01:00
|
|
|
attributeKey,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2019-03-22 12:16:09 +01:00
|
|
|
const findModelByAssoc = assoc => {
|
|
|
|
const { models } = assoc.plugin ? strapi.plugins[assoc.plugin] : strapi;
|
|
|
|
return models[assoc.collection || assoc.model];
|
|
|
|
};
|
2019-03-13 19:27:18 +01:00
|
|
|
|
2019-03-22 12:16:09 +01:00
|
|
|
const buildQueryJoins = (qb, { model, where }) => {
|
|
|
|
const relationToPopulate = extractRelationsFromWhere(where);
|
2019-03-13 19:27:18 +01:00
|
|
|
|
2019-03-22 12:16:09 +01:00
|
|
|
return relationToPopulate.forEach(path => {
|
|
|
|
const parts = path.split('.');
|
2019-03-13 19:27:18 +01:00
|
|
|
|
2019-03-22 12:16:09 +01:00
|
|
|
let tmpModel = model;
|
|
|
|
for (let part of parts) {
|
|
|
|
const association = tmpModel.associations.find(assoc => assoc.alias === part);
|
|
|
|
|
|
|
|
if (association) {
|
2019-03-22 17:58:36 +01:00
|
|
|
const assocModel = findModelByAssoc(association);
|
2019-03-22 12:16:09 +01:00
|
|
|
buildSingleJoin(qb, tmpModel, assocModel, association);
|
|
|
|
tmpModel = assocModel;
|
|
|
|
}
|
2019-03-13 19:27:18 +01:00
|
|
|
}
|
2019-03-22 12:16:09 +01:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const buildSingleJoin = (qb, strapiModel, astModel, association) => {
|
|
|
|
const relationTable = astModel.collectionName;
|
|
|
|
|
|
|
|
qb.distinct();
|
|
|
|
|
|
|
|
if (association.nature === 'manyToMany') {
|
|
|
|
// Join on both ends
|
|
|
|
qb.innerJoin(
|
|
|
|
association.tableCollectionName,
|
2019-03-28 12:13:32 +01:00
|
|
|
`${association.tableCollectionName}.${pluralize.singular(strapiModel.collectionName)}_${strapiModel.primaryKey}`,
|
2019-03-22 12:16:09 +01:00
|
|
|
`${strapiModel.collectionName}.${strapiModel.primaryKey}`
|
|
|
|
);
|
|
|
|
|
|
|
|
qb.innerJoin(
|
|
|
|
relationTable,
|
|
|
|
`${association.tableCollectionName}.${strapiModel.attributes[association.alias].attribute}_${
|
|
|
|
strapiModel.attributes[association.alias].column
|
|
|
|
}`,
|
|
|
|
`${relationTable}.${astModel.primaryKey}`
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
const externalKey =
|
|
|
|
association.type === 'collection'
|
|
|
|
? `${relationTable}.${association.via}`
|
|
|
|
: `${relationTable}.${astModel.primaryKey}`;
|
|
|
|
|
|
|
|
const internalKey =
|
|
|
|
association.type === 'collection'
|
|
|
|
? `${strapiModel.collectionName}.${strapiModel.primaryKey}`
|
|
|
|
: `${strapiModel.collectionName}.${association.alias}`;
|
|
|
|
|
|
|
|
qb.innerJoin(relationTable, externalKey, internalKey);
|
|
|
|
}
|
2019-03-13 19:27:18 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
module.exports = buildQuery;
|