mirror of
https://github.com/strapi/strapi.git
synced 2025-11-16 18:19:34 +00:00
Allow or operator and working with bookshelf
Signed-off-by: Alexandre Bodin <bodin.alex@gmail.com>
This commit is contained in:
parent
987eb9fc7e
commit
82316bbf3a
@ -38,11 +38,11 @@ const buildQuery = ({ model, filters }) => qb => {
|
|||||||
* @param {Array<Object>} whereClauses - an array of where clause
|
* @param {Array<Object>} whereClauses - an array of where clause
|
||||||
*/
|
*/
|
||||||
const buildJoinsAndFilter = (qb, model, whereClauses) => {
|
const buildJoinsAndFilter = (qb, model, whereClauses) => {
|
||||||
const aliasMap = {};
|
|
||||||
/**
|
/**
|
||||||
* Returns an alias for a name (simple incremental alias name)
|
* Returns an alias for a name (simple incremental alias name)
|
||||||
* @param {string} name - name to alias
|
* @param {string} name - name to alias
|
||||||
*/
|
*/
|
||||||
|
const aliasMap = {};
|
||||||
const generateAlias = name => {
|
const generateAlias = name => {
|
||||||
if (!aliasMap[name]) {
|
if (!aliasMap[name]) {
|
||||||
aliasMap[name] = 1;
|
aliasMap[name] = 1;
|
||||||
@ -58,17 +58,14 @@ const buildJoinsAndFilter = (qb, model, whereClauses) => {
|
|||||||
* @param {Object} qb - Knex query builder
|
* @param {Object} qb - Knex query builder
|
||||||
* @param {Object} tree - Query tree
|
* @param {Object} tree - Query tree
|
||||||
*/
|
*/
|
||||||
const buildQueryFromTree = (qb, queryTree) => {
|
const buildJoinsFromTree = (qb, queryTree) => {
|
||||||
// build joins
|
// build joins
|
||||||
Object.keys(queryTree.children).forEach(key => {
|
Object.keys(queryTree.joins).forEach(key => {
|
||||||
const subQueryTree = queryTree.children[key];
|
const subQueryTree = queryTree.joins[key];
|
||||||
buildJoin(qb, subQueryTree.assoc, queryTree, subQueryTree);
|
buildJoin(qb, subQueryTree.assoc, queryTree, subQueryTree);
|
||||||
|
|
||||||
buildQueryFromTree(qb, subQueryTree);
|
buildJoinsFromTree(qb, subQueryTree);
|
||||||
});
|
});
|
||||||
|
|
||||||
// build where clauses
|
|
||||||
queryTree.where.forEach(w => buildWhereClause({ qb, ...w }));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -135,71 +132,66 @@ const buildJoinsAndFilter = (qb, model, whereClauses) => {
|
|||||||
alias: generateAlias(model.collectionName),
|
alias: generateAlias(model.collectionName),
|
||||||
assoc,
|
assoc,
|
||||||
model,
|
model,
|
||||||
where: [],
|
joins: {},
|
||||||
children: {},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
const tree = {
|
||||||
* Builds a Strapi query tree easy
|
|
||||||
* @param {Array<Object>} whereClauses - Array of Strapi where clause
|
|
||||||
* @param {Object} model - Strapi model
|
|
||||||
* @param {Object} queryTree - queryTree
|
|
||||||
*/
|
|
||||||
const buildQueryTree = (whereClauses, model, queryTree) => {
|
|
||||||
for (let whereClause of whereClauses) {
|
|
||||||
const { field, operator, value } = whereClause;
|
|
||||||
let [key, ...parts] = field.split('.');
|
|
||||||
|
|
||||||
const assoc = findAssoc(model, key);
|
|
||||||
|
|
||||||
// if the key is an attribute add as where clause
|
|
||||||
if (!assoc) {
|
|
||||||
queryTree.where.push({
|
|
||||||
field: `${queryTree.alias}.${key}`,
|
|
||||||
operator,
|
|
||||||
value,
|
|
||||||
});
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const assocModel = strapi.db.getModelByAssoc(assoc);
|
|
||||||
|
|
||||||
// if the last part of the path is an association
|
|
||||||
// add the primary key of the model to the parts
|
|
||||||
if (parts.length === 0) {
|
|
||||||
parts = [assocModel.primaryKey];
|
|
||||||
}
|
|
||||||
|
|
||||||
// init sub query tree
|
|
||||||
if (!queryTree.children[key]) {
|
|
||||||
queryTree.children[key] = createTreeNode(assocModel, assoc);
|
|
||||||
}
|
|
||||||
|
|
||||||
buildQueryTree(
|
|
||||||
[
|
|
||||||
{
|
|
||||||
field: parts.join('.'),
|
|
||||||
operator,
|
|
||||||
value,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
assocModel,
|
|
||||||
queryTree.children[key]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return queryTree;
|
|
||||||
};
|
|
||||||
|
|
||||||
const root = buildQueryTree(whereClauses, model, {
|
|
||||||
alias: model.collectionName,
|
alias: model.collectionName,
|
||||||
assoc: null,
|
assoc: null,
|
||||||
model,
|
model,
|
||||||
where: [],
|
joins: {},
|
||||||
children: {},
|
};
|
||||||
});
|
|
||||||
return buildQueryFromTree(qb, root);
|
const generateNestedJoins = (field, tree) => {
|
||||||
|
let [key, ...parts] = field.split('.');
|
||||||
|
|
||||||
|
const assoc = findAssoc(tree.model, key);
|
||||||
|
// if the key is an attribute add as where clause
|
||||||
|
if (!assoc) {
|
||||||
|
return `${tree.alias}.${key}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const assocModel = strapi.db.getModelByAssoc(assoc);
|
||||||
|
|
||||||
|
// if the last part of the path is an association
|
||||||
|
// add the primary key of the model to the parts
|
||||||
|
if (parts.length === 0) {
|
||||||
|
parts = [assocModel.primaryKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
// init sub query tree
|
||||||
|
if (!tree.joins[key]) {
|
||||||
|
tree.joins[key] = createTreeNode(assocModel, assoc);
|
||||||
|
}
|
||||||
|
|
||||||
|
return generateNestedJoins(parts.join('.'), tree.joins[key]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildWhereClauses = (whereClauses, { model }) => {
|
||||||
|
return whereClauses.map(whereClause => {
|
||||||
|
const { field, operator, value } = whereClause;
|
||||||
|
|
||||||
|
if (operator === 'or') {
|
||||||
|
return { field, operator, value: value.map(v => buildWhereClauses(v, { model })) };
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = generateNestedJoins(field, tree);
|
||||||
|
|
||||||
|
return {
|
||||||
|
field: path,
|
||||||
|
operator,
|
||||||
|
value,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const aliasedWhereClauses = buildWhereClauses(whereClauses, { model });
|
||||||
|
|
||||||
|
buildJoinsFromTree(qb, tree);
|
||||||
|
aliasedWhereClauses.forEach(w => buildWhereClause({ qb, ...w }));
|
||||||
|
|
||||||
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -212,7 +204,7 @@ const buildJoinsAndFilter = (qb, model, whereClauses) => {
|
|||||||
* @param {Object} options.value - Filter value
|
* @param {Object} options.value - Filter value
|
||||||
*/
|
*/
|
||||||
const buildWhereClause = ({ qb, field, operator, value }) => {
|
const buildWhereClause = ({ qb, field, operator, value }) => {
|
||||||
if (Array.isArray(value) && !['in', 'nin'].includes(operator)) {
|
if (Array.isArray(value) && !['or', 'in', 'nin'].includes(operator)) {
|
||||||
return qb.where(subQb => {
|
return qb.where(subQb => {
|
||||||
for (let val of value) {
|
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 }));
|
||||||
@ -221,6 +213,20 @@ const buildWhereClause = ({ qb, field, operator, value }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (operator) {
|
switch (operator) {
|
||||||
|
case 'or':
|
||||||
|
return qb.where(orQb => {
|
||||||
|
value.forEach(orClause => {
|
||||||
|
orQb.orWhere(q => {
|
||||||
|
if (Array.isArray(orClause)) {
|
||||||
|
orClause.forEach(orClause =>
|
||||||
|
q.where(qq => buildWhereClause({ qb: qq, ...orClause }))
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
buildWhereClause({ qb: q, ...orClause });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
case 'eq':
|
case 'eq':
|
||||||
return qb.where(field, value);
|
return qb.where(field, value);
|
||||||
case 'ne':
|
case 'ne':
|
||||||
|
|||||||
@ -88,6 +88,50 @@ const normalizeFieldName = ({ model, field }) => {
|
|||||||
: fieldPath.join('.');
|
: fieldPath.join('.');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const BOOLEAN_OPERATORS = ['or'];
|
||||||
|
|
||||||
|
const hasDeepFilters = whereClauses => {
|
||||||
|
return (
|
||||||
|
whereClauses.filter(({ field, operator, value }) => {
|
||||||
|
if (BOOLEAN_OPERATORS.includes(operator)) {
|
||||||
|
return value.filter(hasDeepFilters).length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return field.split('.').length > 1;
|
||||||
|
}).length > 0
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalizeClauses = (whereClauses, { model }) => {
|
||||||
|
return whereClauses
|
||||||
|
.filter(({ value }) => !_.isNil(value))
|
||||||
|
.map(({ field, operator, value }) => {
|
||||||
|
if (BOOLEAN_OPERATORS.includes(operator)) {
|
||||||
|
return {
|
||||||
|
field,
|
||||||
|
operator,
|
||||||
|
value: value.map(clauses => normalizeClauses(clauses, { model })),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const { model: assocModel, attribute } = getAssociationFromFieldKey({
|
||||||
|
model,
|
||||||
|
field,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { type } = _.get(assocModel, ['allAttributes', attribute], {});
|
||||||
|
|
||||||
|
// cast value or array of values
|
||||||
|
const castedValue = castInput({ type, operator, value });
|
||||||
|
|
||||||
|
return {
|
||||||
|
field: normalizeFieldName({ model, field }),
|
||||||
|
operator,
|
||||||
|
value: castedValue,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {Object} options - Options
|
* @param {Object} options - Options
|
||||||
@ -98,33 +142,14 @@ const normalizeFieldName = ({ model, field }) => {
|
|||||||
const buildQuery = ({ model, filters = {}, ...rest }) => {
|
const buildQuery = ({ model, filters = {}, ...rest }) => {
|
||||||
// Validate query clauses
|
// Validate query clauses
|
||||||
if (filters.where && Array.isArray(filters.where)) {
|
if (filters.where && Array.isArray(filters.where)) {
|
||||||
const deepFilters = filters.where.filter(({ field }) => field.split('.').length > 1);
|
if (hasDeepFilters(filters.where)) {
|
||||||
if (deepFilters.length > 0) {
|
|
||||||
strapi.log.warn(
|
strapi.log.warn(
|
||||||
'Deep filtering queries should be used carefully (e.g Can cause performance issues).\nWhen possible build custom routes which will in most case be more optimised.'
|
'Deep filtering queries should be used carefully (e.g Can cause performance issues).\nWhen possible build custom routes which will in most case be more optimised.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// cast where clauses to match the inner types
|
// cast where clauses to match the inner types
|
||||||
filters.where = filters.where
|
filters.where = normalizeClauses(filters.where, { model });
|
||||||
.filter(({ value }) => !_.isNil(value))
|
|
||||||
.map(({ field, operator, value }) => {
|
|
||||||
const { model: assocModel, attribute } = getAssociationFromFieldKey({
|
|
||||||
model,
|
|
||||||
field,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { type } = _.get(assocModel, ['allAttributes', attribute], {});
|
|
||||||
|
|
||||||
// cast value or array of values
|
|
||||||
const castedValue = castInput({ type, operator, value });
|
|
||||||
|
|
||||||
return {
|
|
||||||
field: normalizeFieldName({ model, field }),
|
|
||||||
operator,
|
|
||||||
value: castedValue,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// call the orm's buildQuery implementation
|
// call the orm's buildQuery implementation
|
||||||
|
|||||||
@ -130,6 +130,8 @@ const VALID_OPERATORS = [
|
|||||||
'null',
|
'null',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const BOOLEAN_OPERATORS = ['or'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse where params
|
* Parse where params
|
||||||
*/
|
*/
|
||||||
@ -175,6 +177,10 @@ const convertWhereClause = (whereClause, value) => {
|
|||||||
const field = whereClause.substring(0, separatorIndex);
|
const field = whereClause.substring(0, separatorIndex);
|
||||||
const operator = whereClause.slice(separatorIndex + 1);
|
const operator = whereClause.slice(separatorIndex + 1);
|
||||||
|
|
||||||
|
if (BOOLEAN_OPERATORS.includes(operator) && field === '') {
|
||||||
|
return { field: null, operator, value: [].concat(value).map(convertWhereParams) };
|
||||||
|
}
|
||||||
|
|
||||||
// the field as underscores
|
// the field as underscores
|
||||||
if (!VALID_OPERATORS.includes(operator)) {
|
if (!VALID_OPERATORS.includes(operator)) {
|
||||||
return { field: whereClause, value };
|
return { field: whereClause, value };
|
||||||
|
|||||||
@ -16,7 +16,7 @@ const addQsParser = app => {
|
|||||||
get() {
|
get() {
|
||||||
const qstr = this.querystring;
|
const qstr = this.querystring;
|
||||||
const cache = (this._querycache = this._querycache || {});
|
const cache = (this._querycache = this._querycache || {});
|
||||||
return cache[qstr] || (cache[qstr] = qs.parse(qstr));
|
return cache[qstr] || (cache[qstr] = qs.parse(qstr, { depth: 20 }));
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user