190 lines
5.2 KiB
JavaScript
Raw Normal View History

2019-03-13 19:27:18 +01:00
const _ = require('lodash');
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)) {
// build path with aliases and return a mapping of the paths with there aliases;
2019-03-13 19:27:18 +01:00
// build joins
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}`;
}
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);
}
};
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':
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:
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;
}, []);
};
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;
association = tmpModel.associations.find(ast => ast.alias === key);
2019-03-13 19:27:18 +01:00
if (association) {
tmpModel = findModelByAssoc(association);
2019-03-13 19:27:18 +01:00
}
}
return {
association,
model: tmpModel,
2019-03-13 19:27:18 +01:00
attributeKey,
};
};
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
const buildQueryJoins = (qb, { model, where }) => {
const relationToPopulate = extractRelationsFromWhere(where);
2019-03-13 19:27:18 +01:00
return relationToPopulate.forEach(path => {
const parts = path.split('.');
2019-03-13 19:27:18 +01:00
let tmpModel = model;
for (let part of parts) {
const association = tmpModel.associations.find(assoc => assoc.alias === part);
if (association) {
const assocModel = findModelByAssoc(association);
buildSingleJoin(qb, tmpModel, assocModel, association);
tmpModel = assocModel;
}
2019-03-13 19:27:18 +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,
`${association.tableCollectionName}.${pluralize.singular(strapiModel.collectionName)}_${strapiModel.primaryKey}`,
`${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;