mirror of
https://github.com/strapi/strapi.git
synced 2025-08-20 22:59:11 +00:00
Split helpers and support populate *
This commit is contained in:
parent
d5dd34278b
commit
6a559d6db3
10
packages/core/database/lib/query/helpers/index.js
Normal file
10
packages/core/database/lib/query/helpers/index.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
...require('./search'),
|
||||||
|
...require('./order-by'),
|
||||||
|
...require('./join'),
|
||||||
|
...require('./populate'),
|
||||||
|
...require('./where'),
|
||||||
|
...require('./transform'),
|
||||||
|
};
|
98
packages/core/database/lib/query/helpers/join.js
Normal file
98
packages/core/database/lib/query/helpers/join.js
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const createPivotJoin = (qb, joinTable, alias, tragetMeta) => {
|
||||||
|
const joinAlias = qb.getAlias();
|
||||||
|
qb.join({
|
||||||
|
alias: joinAlias,
|
||||||
|
referencedTable: joinTable.name,
|
||||||
|
referencedColumn: joinTable.joinColumn.name,
|
||||||
|
rootColumn: joinTable.joinColumn.referencedColumn,
|
||||||
|
rootTable: alias,
|
||||||
|
on: joinTable.on,
|
||||||
|
});
|
||||||
|
|
||||||
|
const subAlias = qb.getAlias();
|
||||||
|
qb.join({
|
||||||
|
alias: subAlias,
|
||||||
|
referencedTable: tragetMeta.tableName,
|
||||||
|
referencedColumn: joinTable.inverseJoinColumn.referencedColumn,
|
||||||
|
rootColumn: joinTable.inverseJoinColumn.name,
|
||||||
|
rootTable: joinAlias,
|
||||||
|
});
|
||||||
|
|
||||||
|
return subAlias;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createJoin = (ctx, { alias, attributeName, attribute }) => {
|
||||||
|
const { db, qb } = ctx;
|
||||||
|
|
||||||
|
if (attribute.type !== 'relation') {
|
||||||
|
throw new Error(`Cannot join on non relational field ${attributeName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tragetMeta = db.metadata.get(attribute.target);
|
||||||
|
|
||||||
|
const joinColumn = attribute.joinColumn;
|
||||||
|
|
||||||
|
if (joinColumn) {
|
||||||
|
const subAlias = qb.getAlias();
|
||||||
|
qb.join({
|
||||||
|
alias: subAlias,
|
||||||
|
referencedTable: tragetMeta.tableName,
|
||||||
|
referencedColumn: joinColumn.referencedColumn,
|
||||||
|
rootColumn: joinColumn.name,
|
||||||
|
rootTable: alias,
|
||||||
|
});
|
||||||
|
return subAlias;
|
||||||
|
}
|
||||||
|
|
||||||
|
const joinTable = attribute.joinTable;
|
||||||
|
if (joinTable) {
|
||||||
|
return createPivotJoin(qb, joinTable, alias, tragetMeta);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: polymorphic relations
|
||||||
|
// NOTE: using the joinColumn / joinTable syntax we don't really care about the relation type here
|
||||||
|
|
||||||
|
return alias;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: allow for more conditions
|
||||||
|
const applyJoin = (qb, join) => {
|
||||||
|
const {
|
||||||
|
method = 'leftJoin',
|
||||||
|
alias,
|
||||||
|
referencedTable,
|
||||||
|
referencedColumn,
|
||||||
|
rootColumn,
|
||||||
|
rootTable = this.alias,
|
||||||
|
on,
|
||||||
|
orderBy,
|
||||||
|
} = join;
|
||||||
|
|
||||||
|
qb[method]({ [alias]: referencedTable }, inner => {
|
||||||
|
inner.on(`${rootTable}.${rootColumn}`, `${alias}.${referencedColumn}`);
|
||||||
|
|
||||||
|
if (on) {
|
||||||
|
for (const key in on) {
|
||||||
|
inner.onVal(`${alias}.${key}`, on[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (orderBy) {
|
||||||
|
Object.keys(orderBy).forEach(column => {
|
||||||
|
const direction = orderBy[column];
|
||||||
|
qb.orderBy(`${alias}.${column}`, direction);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const applyJoins = (qb, joins) => joins.forEach(join => applyJoin(qb, join));
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
createJoin,
|
||||||
|
createPivotJoin,
|
||||||
|
applyJoins,
|
||||||
|
applyJoin,
|
||||||
|
};
|
60
packages/core/database/lib/query/helpers/order-by.js
Normal file
60
packages/core/database/lib/query/helpers/order-by.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const _ = require('lodash/fp');
|
||||||
|
|
||||||
|
const types = require('../../types');
|
||||||
|
const { createJoin } = require('./join');
|
||||||
|
|
||||||
|
// TODO: convert field names to columns names
|
||||||
|
const processOrderBy = (orderBy, ctx) => {
|
||||||
|
const { db, uid, qb, alias = qb.alias } = ctx;
|
||||||
|
|
||||||
|
if (typeof orderBy === 'string') {
|
||||||
|
const attribute = db.metadata.get(uid).attributes[orderBy];
|
||||||
|
|
||||||
|
if (!attribute) {
|
||||||
|
throw new Error(`Attribute ${orderBy} not found on model ${uid}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [{ column: `${alias}.${orderBy}` }];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(orderBy)) {
|
||||||
|
return orderBy.flatMap(value => processOrderBy(value, ctx));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_.isPlainObject(orderBy)) {
|
||||||
|
return Object.entries(orderBy).flatMap(([key, direction]) => {
|
||||||
|
const value = orderBy[key];
|
||||||
|
const attribute = db.metadata.get(uid).attributes[key];
|
||||||
|
|
||||||
|
if (!attribute) {
|
||||||
|
throw new Error(`Attribute ${key} not found on model ${uid}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attribute.type === 'relation') {
|
||||||
|
// TODO: pass down some filters (e.g published at)
|
||||||
|
const subAlias = createJoin(ctx, { alias, uid, attributeName: key, attribute });
|
||||||
|
|
||||||
|
return processOrderBy(value, {
|
||||||
|
db,
|
||||||
|
qb,
|
||||||
|
alias: subAlias,
|
||||||
|
uid: attribute.target,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (types.isScalar(attribute.type)) {
|
||||||
|
return { column: `${alias}.${key}`, order: direction };
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`You cannot order on ${attribute.type} types`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Invalid orderBy syntax');
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
processOrderBy,
|
||||||
|
};
|
@ -2,457 +2,8 @@
|
|||||||
|
|
||||||
const _ = require('lodash/fp');
|
const _ = require('lodash/fp');
|
||||||
|
|
||||||
const types = require('../types');
|
const types = require('../../types');
|
||||||
const { createField } = require('../fields');
|
const { fromRow } = require('./transform');
|
||||||
|
|
||||||
const GROUP_OPERATORS = ['$and', '$or'];
|
|
||||||
const OPERATORS = [
|
|
||||||
'$not',
|
|
||||||
'$in',
|
|
||||||
'$notIn',
|
|
||||||
'$eq',
|
|
||||||
'$ne',
|
|
||||||
'$gt',
|
|
||||||
'$gte',
|
|
||||||
'$lt',
|
|
||||||
'$lte',
|
|
||||||
'$null',
|
|
||||||
'$notNull',
|
|
||||||
'$between',
|
|
||||||
// '$like',
|
|
||||||
// '$regexp',
|
|
||||||
'$startsWith',
|
|
||||||
'$endsWith',
|
|
||||||
'$contains',
|
|
||||||
'$notContains',
|
|
||||||
];
|
|
||||||
|
|
||||||
const ARRAY_OPERATORS = ['$in', '$notIn', '$between'];
|
|
||||||
|
|
||||||
const createPivotJoin = (qb, joinTable, alias, tragetMeta) => {
|
|
||||||
const joinAlias = qb.getAlias();
|
|
||||||
qb.join({
|
|
||||||
alias: joinAlias,
|
|
||||||
referencedTable: joinTable.name,
|
|
||||||
referencedColumn: joinTable.joinColumn.name,
|
|
||||||
rootColumn: joinTable.joinColumn.referencedColumn,
|
|
||||||
rootTable: alias,
|
|
||||||
on: joinTable.on,
|
|
||||||
});
|
|
||||||
|
|
||||||
const subAlias = qb.getAlias();
|
|
||||||
qb.join({
|
|
||||||
alias: subAlias,
|
|
||||||
referencedTable: tragetMeta.tableName,
|
|
||||||
referencedColumn: joinTable.inverseJoinColumn.referencedColumn,
|
|
||||||
rootColumn: joinTable.inverseJoinColumn.name,
|
|
||||||
rootTable: joinAlias,
|
|
||||||
});
|
|
||||||
|
|
||||||
return subAlias;
|
|
||||||
};
|
|
||||||
|
|
||||||
const createJoin = (ctx, { alias, attributeName, attribute }) => {
|
|
||||||
const { db, qb } = ctx;
|
|
||||||
|
|
||||||
if (attribute.type !== 'relation') {
|
|
||||||
throw new Error(`Cannot join on non relational field ${attributeName}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const tragetMeta = db.metadata.get(attribute.target);
|
|
||||||
|
|
||||||
const joinColumn = attribute.joinColumn;
|
|
||||||
|
|
||||||
if (joinColumn) {
|
|
||||||
const subAlias = qb.getAlias();
|
|
||||||
qb.join({
|
|
||||||
alias: subAlias,
|
|
||||||
referencedTable: tragetMeta.tableName,
|
|
||||||
referencedColumn: joinColumn.referencedColumn,
|
|
||||||
rootColumn: joinColumn.name,
|
|
||||||
rootTable: alias,
|
|
||||||
});
|
|
||||||
return subAlias;
|
|
||||||
}
|
|
||||||
|
|
||||||
const joinTable = attribute.joinTable;
|
|
||||||
if (joinTable) {
|
|
||||||
return createPivotJoin(qb, joinTable, alias, tragetMeta);
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: using the joinColumn / joinTable syntax we don't really care about the relation type here
|
|
||||||
switch (attribute.relation) {
|
|
||||||
case 'oneToOne': {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'oneToMany': {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'manyToOne': {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'manyToMany': {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: polymorphic relations
|
|
||||||
// TODO: components -> they are converted to relation so not needed either
|
|
||||||
}
|
|
||||||
|
|
||||||
return alias;
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: convert field names to columns names
|
|
||||||
const processOrderBy = (orderBy, ctx) => {
|
|
||||||
const { db, uid, qb, alias = qb.alias } = ctx;
|
|
||||||
|
|
||||||
if (typeof orderBy === 'string') {
|
|
||||||
const attribute = db.metadata.get(uid).attributes[orderBy];
|
|
||||||
|
|
||||||
if (!attribute) {
|
|
||||||
throw new Error(`Attribute ${orderBy} not found on model ${uid}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [{ column: `${alias}.${orderBy}` }];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(orderBy)) {
|
|
||||||
return orderBy.flatMap(value => processOrderBy(value, ctx));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_.isPlainObject(orderBy)) {
|
|
||||||
return Object.entries(orderBy).flatMap(([key, direction]) => {
|
|
||||||
const value = orderBy[key];
|
|
||||||
const attribute = db.metadata.get(uid).attributes[key];
|
|
||||||
|
|
||||||
if (!attribute) {
|
|
||||||
throw new Error(`Attribute ${key} not found on model ${uid}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attribute.type === 'relation') {
|
|
||||||
// TODO: pass down some filters (e.g published at)
|
|
||||||
const subAlias = createJoin(ctx, { alias, uid, attributeName: key, attribute });
|
|
||||||
|
|
||||||
return processOrderBy(value, {
|
|
||||||
db,
|
|
||||||
qb,
|
|
||||||
alias: subAlias,
|
|
||||||
uid: attribute.target,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (types.isScalar(attribute.type)) {
|
|
||||||
return { column: `${alias}.${key}`, order: direction };
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`You cannot order on ${attribute.type} types`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error('Invalid orderBy syntax');
|
|
||||||
};
|
|
||||||
|
|
||||||
const isOperator = key => OPERATORS.includes(key);
|
|
||||||
|
|
||||||
const processWhere = (where, ctx, depth = 0) => {
|
|
||||||
if (depth === 0 && !_.isPlainObject(where)) {
|
|
||||||
throw new Error('Where must be an object');
|
|
||||||
}
|
|
||||||
|
|
||||||
const processNested = (where, ctx) => {
|
|
||||||
if (!_.isPlainObject(where)) {
|
|
||||||
return where;
|
|
||||||
}
|
|
||||||
|
|
||||||
return processWhere(where, ctx, depth + 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
const { db, uid, qb, alias = qb.alias } = ctx;
|
|
||||||
|
|
||||||
const filters = {};
|
|
||||||
|
|
||||||
// for each key in where
|
|
||||||
for (const key in where) {
|
|
||||||
const value = where[key];
|
|
||||||
const attribute = db.metadata.get(uid).attributes[key];
|
|
||||||
|
|
||||||
// if operator $and $or then loop over them
|
|
||||||
if (GROUP_OPERATORS.includes(key)) {
|
|
||||||
filters[key] = value.map(sub => processNested(sub, ctx));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === '$not') {
|
|
||||||
filters[key] = processNested(value, ctx);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isOperator(key)) {
|
|
||||||
if (depth == 0) {
|
|
||||||
throw new Error(
|
|
||||||
`Only $and, $or and $not can by used as root level operators. Found ${key}.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
filters[key] = processNested(value, ctx);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!attribute) {
|
|
||||||
// TODO: if targeting a column name instead of an attribute
|
|
||||||
|
|
||||||
// if key as an alias don't add one
|
|
||||||
if (key.indexOf('.') >= 0) {
|
|
||||||
filters[key] = processNested(value, ctx);
|
|
||||||
} else {
|
|
||||||
filters[`${alias || qb.alias}.${key}`] = processNested(value, ctx);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// throw new Error(`Attribute ${key} not found on model ${uid}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// move to if else to check for scalar / relation / components & throw for other types
|
|
||||||
if (attribute.type === 'relation') {
|
|
||||||
// TODO: pass down some filters (e.g published at)
|
|
||||||
|
|
||||||
// attribute
|
|
||||||
const subAlias = createJoin(ctx, { alias, uid, attributeName: key, attribute });
|
|
||||||
|
|
||||||
let nestedWhere = processNested(value, {
|
|
||||||
db,
|
|
||||||
qb,
|
|
||||||
alias: subAlias,
|
|
||||||
uid: attribute.target,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!_.isPlainObject(nestedWhere) || isOperator(_.keys(nestedWhere)[0])) {
|
|
||||||
nestedWhere = { [`${subAlias}.id`]: nestedWhere };
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: use a better merge logic (push to $and when collisions)
|
|
||||||
Object.assign(filters, nestedWhere);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (types.isScalar(attribute.type)) {
|
|
||||||
// TODO: convert attribute name to column name
|
|
||||||
// TODO: cast to DB type
|
|
||||||
filters[`${alias || qb.alias}.${key}`] = processNested(value, ctx);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`You cannot filter on ${attribute.type} types`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return filters;
|
|
||||||
};
|
|
||||||
|
|
||||||
const applyOperator = (qb, column, operator, value) => {
|
|
||||||
if (Array.isArray(value) && !ARRAY_OPERATORS.includes(operator)) {
|
|
||||||
return qb.where(subQB => {
|
|
||||||
value.forEach(subValue =>
|
|
||||||
subQB.orWhere(innerQB => {
|
|
||||||
applyOperator(innerQB, column, operator, subValue);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (operator) {
|
|
||||||
case '$not': {
|
|
||||||
qb.whereNot(qb => applyWhereToColumn(qb, column, value));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case '$in': {
|
|
||||||
qb.whereIn(column, _.castArray(value));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case '$notIn': {
|
|
||||||
qb.whereNotIn(column, _.castArray(value));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case '$eq': {
|
|
||||||
if (value === null) {
|
|
||||||
qb.whereNull(column);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
qb.where(column, value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case '$ne': {
|
|
||||||
if (value === null) {
|
|
||||||
qb.whereNotNull(column);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
qb.where(column, '<>', value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case '$gt': {
|
|
||||||
qb.where(column, '>', value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case '$gte': {
|
|
||||||
qb.where(column, '>=', value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case '$lt': {
|
|
||||||
qb.where(column, '<', value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case '$lte': {
|
|
||||||
qb.where(column, '<=', value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case '$null': {
|
|
||||||
// TODO: make this better
|
|
||||||
if (value) {
|
|
||||||
qb.whereNull(column);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case '$notNull': {
|
|
||||||
if (value) {
|
|
||||||
qb.whereNotNull(column);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case '$between': {
|
|
||||||
qb.whereBetween(column, value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// case '$regexp': {
|
|
||||||
// // TODO:
|
|
||||||
//
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// // string
|
|
||||||
// // TODO: use $case to make it case insensitive
|
|
||||||
// case '$like': {
|
|
||||||
// qb.where(column, 'like', value);
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// TODO: add casting logic
|
|
||||||
case '$startsWith': {
|
|
||||||
qb.where(column, 'like', `${value}%`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case '$endsWith': {
|
|
||||||
qb.where(column, 'like', `%${value}`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case '$contains': {
|
|
||||||
// TODO: handle insensitive
|
|
||||||
|
|
||||||
qb.where(column, 'like', `%${value}%`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case '$notContains': {
|
|
||||||
// TODO: handle insensitive
|
|
||||||
qb.whereNot(column, 'like', `%${value}%`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: json operators
|
|
||||||
|
|
||||||
// TODO: relational operators every/some/exists/size ...
|
|
||||||
|
|
||||||
default: {
|
|
||||||
throw new Error(`Undefined operator ${operator}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const applyWhereToColumn = (qb, column, columnWhere) => {
|
|
||||||
if (!_.isPlainObject(columnWhere)) {
|
|
||||||
if (Array.isArray(columnWhere)) {
|
|
||||||
return qb.whereIn(column, columnWhere);
|
|
||||||
}
|
|
||||||
|
|
||||||
return qb.where(column, columnWhere);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Transform into if has($in, value) then to handle cases with two keys doing one thing (like $contains with $case)
|
|
||||||
Object.keys(columnWhere).forEach(operator => {
|
|
||||||
const value = columnWhere[operator];
|
|
||||||
|
|
||||||
applyOperator(qb, column, operator, value);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const applyWhere = (qb, where) => {
|
|
||||||
if (Array.isArray(where)) {
|
|
||||||
return qb.where(subQB => where.forEach(subWhere => applyWhere(subQB, subWhere)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_.isPlainObject(where)) {
|
|
||||||
throw new Error('Where must be an object');
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.keys(where).forEach(key => {
|
|
||||||
const value = where[key];
|
|
||||||
|
|
||||||
if (key === '$and') {
|
|
||||||
return qb.where(subQB => {
|
|
||||||
value.forEach(v => applyWhere(subQB, v));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === '$or') {
|
|
||||||
return qb.where(subQB => {
|
|
||||||
value.forEach(v => subQB.orWhere(inner => applyWhere(inner, v)));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === '$not') {
|
|
||||||
return qb.whereNot(qb => applyWhere(qb, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
applyWhereToColumn(qb, key, value);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: allow for more conditions
|
|
||||||
const applyJoin = (qb, join) => {
|
|
||||||
const {
|
|
||||||
method = 'leftJoin',
|
|
||||||
alias,
|
|
||||||
referencedTable,
|
|
||||||
referencedColumn,
|
|
||||||
rootColumn,
|
|
||||||
rootTable = this.alias,
|
|
||||||
on,
|
|
||||||
orderBy,
|
|
||||||
} = join;
|
|
||||||
|
|
||||||
qb[method]({ [alias]: referencedTable }, inner => {
|
|
||||||
inner.on(`${rootTable}.${rootColumn}`, `${alias}.${referencedColumn}`);
|
|
||||||
|
|
||||||
if (on) {
|
|
||||||
for (const key in on) {
|
|
||||||
inner.onVal(`${alias}.${key}`, on[key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (orderBy) {
|
|
||||||
Object.keys(orderBy).forEach(column => {
|
|
||||||
const direction = orderBy[column];
|
|
||||||
qb.orderBy(`${alias}.${column}`, direction);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const applyJoins = (qb, joins) => joins.forEach(join => applyJoin(qb, join));
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts and prepares the query for populate
|
* Converts and prepares the query for populate
|
||||||
@ -473,7 +24,14 @@ const processPopulate = (populate, ctx) => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(populate)) {
|
if (populate === true) {
|
||||||
|
for (const attributeName in meta.attributes) {
|
||||||
|
const attribute = meta.attributes[attributeName];
|
||||||
|
if (attribute.type === 'relation') {
|
||||||
|
populateMap[attributeName] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (Array.isArray(populate)) {
|
||||||
for (const key of populate) {
|
for (const key of populate) {
|
||||||
const [root, ...rest] = key.split('.');
|
const [root, ...rest] = key.split('.');
|
||||||
|
|
||||||
@ -1061,121 +619,7 @@ const applyPopulate = async (results, populate, ctx) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const fromRow = (metadata, row) => {
|
|
||||||
if (Array.isArray(row)) {
|
|
||||||
return row.map(singleRow => fromRow(metadata, singleRow));
|
|
||||||
}
|
|
||||||
|
|
||||||
const { attributes } = metadata;
|
|
||||||
|
|
||||||
if (_.isNil(row)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const obj = {};
|
|
||||||
|
|
||||||
for (const column in row) {
|
|
||||||
// to field Name
|
|
||||||
const attributeName = column;
|
|
||||||
|
|
||||||
if (!attributes[attributeName]) {
|
|
||||||
// ignore value that are not related to an attribute (join columns ...)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const attribute = attributes[attributeName];
|
|
||||||
|
|
||||||
if (types.isScalar(attribute.type)) {
|
|
||||||
// TODO: we convert to column name
|
|
||||||
// TODO: handle default value too
|
|
||||||
// TODO: format data & use dialect to know which type they support (json particularly)
|
|
||||||
|
|
||||||
const field = createField(attribute);
|
|
||||||
|
|
||||||
// TODO: validate data on creation
|
|
||||||
// field.validate(data[attributeName]);
|
|
||||||
const val = row[column] === null ? null : field.fromDB(row[column]);
|
|
||||||
|
|
||||||
obj[attributeName] = val;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (types.isRelation(attribute.type)) {
|
|
||||||
obj[attributeName] = row[column];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return obj;
|
|
||||||
};
|
|
||||||
|
|
||||||
const applySearch = (qb, query, ctx) => {
|
|
||||||
const { alias, uid, db } = ctx;
|
|
||||||
|
|
||||||
const { attributes } = db.metadata.get(uid);
|
|
||||||
|
|
||||||
const searchColumns = ['id'];
|
|
||||||
|
|
||||||
const stringColumns = Object.keys(attributes).filter(attributeName => {
|
|
||||||
const attribute = attributes[attributeName];
|
|
||||||
return types.isString(attribute.type) && attribute.searchable !== false;
|
|
||||||
});
|
|
||||||
|
|
||||||
searchColumns.push(...stringColumns);
|
|
||||||
|
|
||||||
if (!_.isNaN(_.toNumber(query))) {
|
|
||||||
const numberColumns = Object.keys(attributes).filter(attributeName => {
|
|
||||||
const attribute = attributes[attributeName];
|
|
||||||
return types.isNumber(attribute.type) && attribute.searchable !== false;
|
|
||||||
});
|
|
||||||
|
|
||||||
searchColumns.push(...numberColumns);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (db.dialect.client) {
|
|
||||||
case 'postgres': {
|
|
||||||
searchColumns.forEach(attr =>
|
|
||||||
qb.orWhereRaw(`"${alias}"."${attr}"::text ILIKE ?`, `%${escapeQuery(query, '*%\\')}%`)
|
|
||||||
);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'sqlite': {
|
|
||||||
searchColumns.forEach(attr =>
|
|
||||||
qb.orWhereRaw(`"${alias}"."${attr}" LIKE ? ESCAPE '\\'`, `%${escapeQuery(query, '*%\\')}%`)
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'mysql': {
|
|
||||||
searchColumns.forEach(attr =>
|
|
||||||
qb.orWhereRaw(`\`${alias}\`.\`${attr}\` LIKE ?`, `%${escapeQuery(query, '*%\\')}%`)
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const escapeQuery = (query, charsToEscape, escapeChar = '\\') => {
|
|
||||||
return query
|
|
||||||
.split('')
|
|
||||||
.reduce(
|
|
||||||
(escapedQuery, char) =>
|
|
||||||
charsToEscape.includes(char)
|
|
||||||
? `${escapedQuery}${escapeChar}${char}`
|
|
||||||
: `${escapedQuery}${char}`,
|
|
||||||
''
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
applyWhere,
|
|
||||||
processWhere,
|
|
||||||
applyJoins,
|
|
||||||
applyJoin,
|
|
||||||
processOrderBy,
|
|
||||||
processPopulate,
|
processPopulate,
|
||||||
applySearch,
|
|
||||||
applyPopulate,
|
applyPopulate,
|
||||||
fromRow,
|
|
||||||
};
|
};
|
70
packages/core/database/lib/query/helpers/search.js
Normal file
70
packages/core/database/lib/query/helpers/search.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const _ = require('lodash/fp');
|
||||||
|
|
||||||
|
const types = require('../../types');
|
||||||
|
|
||||||
|
const applySearch = (qb, query, ctx) => {
|
||||||
|
const { alias, uid, db } = ctx;
|
||||||
|
|
||||||
|
const { attributes } = db.metadata.get(uid);
|
||||||
|
|
||||||
|
const searchColumns = ['id'];
|
||||||
|
|
||||||
|
const stringColumns = Object.keys(attributes).filter(attributeName => {
|
||||||
|
const attribute = attributes[attributeName];
|
||||||
|
return types.isString(attribute.type) && attribute.searchable !== false;
|
||||||
|
});
|
||||||
|
|
||||||
|
searchColumns.push(...stringColumns);
|
||||||
|
|
||||||
|
if (!_.isNaN(_.toNumber(query))) {
|
||||||
|
const numberColumns = Object.keys(attributes).filter(attributeName => {
|
||||||
|
const attribute = attributes[attributeName];
|
||||||
|
return types.isNumber(attribute.type) && attribute.searchable !== false;
|
||||||
|
});
|
||||||
|
|
||||||
|
searchColumns.push(...numberColumns);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (db.dialect.client) {
|
||||||
|
case 'postgres': {
|
||||||
|
searchColumns.forEach(attr =>
|
||||||
|
qb.orWhereRaw(`"${alias}"."${attr}"::text ILIKE ?`, `%${escapeQuery(query, '*%\\')}%`)
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'sqlite': {
|
||||||
|
searchColumns.forEach(attr =>
|
||||||
|
qb.orWhereRaw(`"${alias}"."${attr}" LIKE ? ESCAPE '\\'`, `%${escapeQuery(query, '*%\\')}%`)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'mysql': {
|
||||||
|
searchColumns.forEach(attr =>
|
||||||
|
qb.orWhereRaw(`\`${alias}\`.\`${attr}\` LIKE ?`, `%${escapeQuery(query, '*%\\')}%`)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const escapeQuery = (query, charsToEscape, escapeChar = '\\') => {
|
||||||
|
return query
|
||||||
|
.split('')
|
||||||
|
.reduce(
|
||||||
|
(escapedQuery, char) =>
|
||||||
|
charsToEscape.includes(char)
|
||||||
|
? `${escapedQuery}${escapeChar}${char}`
|
||||||
|
: `${escapedQuery}${char}`,
|
||||||
|
''
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
applySearch,
|
||||||
|
};
|
56
packages/core/database/lib/query/helpers/transform.js
Normal file
56
packages/core/database/lib/query/helpers/transform.js
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const _ = require('lodash/fp');
|
||||||
|
|
||||||
|
const types = require('../../types');
|
||||||
|
const { createField } = require('../../fields');
|
||||||
|
|
||||||
|
const fromRow = (metadata, row) => {
|
||||||
|
if (Array.isArray(row)) {
|
||||||
|
return row.map(singleRow => fromRow(metadata, singleRow));
|
||||||
|
}
|
||||||
|
|
||||||
|
const { attributes } = metadata;
|
||||||
|
|
||||||
|
if (_.isNil(row)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const obj = {};
|
||||||
|
|
||||||
|
for (const column in row) {
|
||||||
|
// to field Name
|
||||||
|
const attributeName = column;
|
||||||
|
|
||||||
|
if (!attributes[attributeName]) {
|
||||||
|
// ignore value that are not related to an attribute (join columns ...)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const attribute = attributes[attributeName];
|
||||||
|
|
||||||
|
if (types.isScalar(attribute.type)) {
|
||||||
|
// TODO: we convert to column name
|
||||||
|
// TODO: handle default value too
|
||||||
|
// TODO: format data & use dialect to know which type they support (json particularly)
|
||||||
|
|
||||||
|
const field = createField(attribute);
|
||||||
|
|
||||||
|
// TODO: validate data on creation
|
||||||
|
// field.validate(data[attributeName]);
|
||||||
|
const val = row[column] === null ? null : field.fromDB(row[column]);
|
||||||
|
|
||||||
|
obj[attributeName] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (types.isRelation(attribute.type)) {
|
||||||
|
obj[attributeName] = row[column];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
fromRow,
|
||||||
|
};
|
311
packages/core/database/lib/query/helpers/where.js
Normal file
311
packages/core/database/lib/query/helpers/where.js
Normal file
@ -0,0 +1,311 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const _ = require('lodash/fp');
|
||||||
|
|
||||||
|
const types = require('../../types');
|
||||||
|
const { createJoin } = require('./join');
|
||||||
|
|
||||||
|
const GROUP_OPERATORS = ['$and', '$or'];
|
||||||
|
const OPERATORS = [
|
||||||
|
'$not',
|
||||||
|
'$in',
|
||||||
|
'$notIn',
|
||||||
|
'$eq',
|
||||||
|
'$ne',
|
||||||
|
'$gt',
|
||||||
|
'$gte',
|
||||||
|
'$lt',
|
||||||
|
'$lte',
|
||||||
|
'$null',
|
||||||
|
'$notNull',
|
||||||
|
'$between',
|
||||||
|
// '$like',
|
||||||
|
// '$regexp',
|
||||||
|
'$startsWith',
|
||||||
|
'$endsWith',
|
||||||
|
'$contains',
|
||||||
|
'$notContains',
|
||||||
|
];
|
||||||
|
|
||||||
|
const ARRAY_OPERATORS = ['$in', '$notIn', '$between'];
|
||||||
|
|
||||||
|
const isOperator = key => OPERATORS.includes(key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process where parameter
|
||||||
|
* @param {Object} where
|
||||||
|
* @param {Object} ctx
|
||||||
|
* @param {number} depth
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
const processWhere = (where, ctx, depth = 0) => {
|
||||||
|
if (depth === 0 && !_.isPlainObject(where)) {
|
||||||
|
throw new Error('Where must be an object');
|
||||||
|
}
|
||||||
|
|
||||||
|
const processNested = (where, ctx) => {
|
||||||
|
if (!_.isPlainObject(where)) {
|
||||||
|
return where;
|
||||||
|
}
|
||||||
|
|
||||||
|
return processWhere(where, ctx, depth + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const { db, uid, qb, alias = qb.alias } = ctx;
|
||||||
|
|
||||||
|
const filters = {};
|
||||||
|
|
||||||
|
// for each key in where
|
||||||
|
for (const key in where) {
|
||||||
|
const value = where[key];
|
||||||
|
const attribute = db.metadata.get(uid).attributes[key];
|
||||||
|
|
||||||
|
// if operator $and $or then loop over them
|
||||||
|
if (GROUP_OPERATORS.includes(key)) {
|
||||||
|
filters[key] = value.map(sub => processNested(sub, ctx));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === '$not') {
|
||||||
|
filters[key] = processNested(value, ctx);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOperator(key)) {
|
||||||
|
if (depth == 0) {
|
||||||
|
throw new Error(
|
||||||
|
`Only $and, $or and $not can by used as root level operators. Found ${key}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
filters[key] = processNested(value, ctx);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!attribute) {
|
||||||
|
// TODO: if targeting a column name instead of an attribute
|
||||||
|
|
||||||
|
// if key as an alias don't add one
|
||||||
|
if (key.indexOf('.') >= 0) {
|
||||||
|
filters[key] = processNested(value, ctx);
|
||||||
|
} else {
|
||||||
|
filters[`${alias || qb.alias}.${key}`] = processNested(value, ctx);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// throw new Error(`Attribute ${key} not found on model ${uid}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// move to if else to check for scalar / relation / components & throw for other types
|
||||||
|
if (attribute.type === 'relation') {
|
||||||
|
// TODO: pass down some filters (e.g published at)
|
||||||
|
|
||||||
|
// attribute
|
||||||
|
const subAlias = createJoin(ctx, { alias, uid, attributeName: key, attribute });
|
||||||
|
|
||||||
|
let nestedWhere = processNested(value, {
|
||||||
|
db,
|
||||||
|
qb,
|
||||||
|
alias: subAlias,
|
||||||
|
uid: attribute.target,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!_.isPlainObject(nestedWhere) || isOperator(_.keys(nestedWhere)[0])) {
|
||||||
|
nestedWhere = { [`${subAlias}.id`]: nestedWhere };
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: use a better merge logic (push to $and when collisions)
|
||||||
|
Object.assign(filters, nestedWhere);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (types.isScalar(attribute.type)) {
|
||||||
|
// TODO: convert attribute name to column name
|
||||||
|
// TODO: cast to DB type
|
||||||
|
filters[`${alias || qb.alias}.${key}`] = processNested(value, ctx);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`You cannot filter on ${attribute.type} types`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return filters;
|
||||||
|
};
|
||||||
|
|
||||||
|
const applyOperator = (qb, column, operator, value) => {
|
||||||
|
if (Array.isArray(value) && !ARRAY_OPERATORS.includes(operator)) {
|
||||||
|
return qb.where(subQB => {
|
||||||
|
value.forEach(subValue =>
|
||||||
|
subQB.orWhere(innerQB => {
|
||||||
|
applyOperator(innerQB, column, operator, subValue);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (operator) {
|
||||||
|
case '$not': {
|
||||||
|
qb.whereNot(qb => applyWhereToColumn(qb, column, value));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case '$in': {
|
||||||
|
qb.whereIn(column, _.castArray(value));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case '$notIn': {
|
||||||
|
qb.whereNotIn(column, _.castArray(value));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case '$eq': {
|
||||||
|
if (value === null) {
|
||||||
|
qb.whereNull(column);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
qb.where(column, value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case '$ne': {
|
||||||
|
if (value === null) {
|
||||||
|
qb.whereNotNull(column);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
qb.where(column, '<>', value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case '$gt': {
|
||||||
|
qb.where(column, '>', value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case '$gte': {
|
||||||
|
qb.where(column, '>=', value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case '$lt': {
|
||||||
|
qb.where(column, '<', value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case '$lte': {
|
||||||
|
qb.where(column, '<=', value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case '$null': {
|
||||||
|
// TODO: make this better
|
||||||
|
if (value) {
|
||||||
|
qb.whereNull(column);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case '$notNull': {
|
||||||
|
if (value) {
|
||||||
|
qb.whereNotNull(column);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case '$between': {
|
||||||
|
qb.whereBetween(column, value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// case '$regexp': {
|
||||||
|
// // TODO:
|
||||||
|
//
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// // string
|
||||||
|
// // TODO: use $case to make it case insensitive
|
||||||
|
// case '$like': {
|
||||||
|
// qb.where(column, 'like', value);
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// TODO: add casting logic
|
||||||
|
case '$startsWith': {
|
||||||
|
qb.where(column, 'like', `${value}%`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case '$endsWith': {
|
||||||
|
qb.where(column, 'like', `%${value}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case '$contains': {
|
||||||
|
// TODO: handle insensitive
|
||||||
|
|
||||||
|
qb.where(column, 'like', `%${value}%`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case '$notContains': {
|
||||||
|
// TODO: handle insensitive
|
||||||
|
qb.whereNot(column, 'like', `%${value}%`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: json operators
|
||||||
|
|
||||||
|
// TODO: relational operators every/some/exists/size ...
|
||||||
|
|
||||||
|
default: {
|
||||||
|
throw new Error(`Undefined operator ${operator}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const applyWhereToColumn = (qb, column, columnWhere) => {
|
||||||
|
if (!_.isPlainObject(columnWhere)) {
|
||||||
|
if (Array.isArray(columnWhere)) {
|
||||||
|
return qb.whereIn(column, columnWhere);
|
||||||
|
}
|
||||||
|
|
||||||
|
return qb.where(column, columnWhere);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Transform into if has($in, value) then to handle cases with two keys doing one thing (like $contains with $case)
|
||||||
|
Object.keys(columnWhere).forEach(operator => {
|
||||||
|
const value = columnWhere[operator];
|
||||||
|
|
||||||
|
applyOperator(qb, column, operator, value);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const applyWhere = (qb, where) => {
|
||||||
|
if (Array.isArray(where)) {
|
||||||
|
return qb.where(subQB => where.forEach(subWhere => applyWhere(subQB, subWhere)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_.isPlainObject(where)) {
|
||||||
|
throw new Error('Where must be an object');
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(where).forEach(key => {
|
||||||
|
const value = where[key];
|
||||||
|
|
||||||
|
if (key === '$and') {
|
||||||
|
return qb.where(subQB => {
|
||||||
|
value.forEach(v => applyWhere(subQB, v));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === '$or') {
|
||||||
|
return qb.where(subQB => {
|
||||||
|
value.forEach(v => subQB.orWhere(inner => applyWhere(inner, v)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === '$not') {
|
||||||
|
return qb.whereNot(qb => applyWhere(qb, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
applyWhereToColumn(qb, key, value);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
applyWhere,
|
||||||
|
processWhere,
|
||||||
|
};
|
@ -155,6 +155,7 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
|
|||||||
});
|
});
|
||||||
|
|
||||||
// TODO: upload the files then set the links in the entity like with compo to avoid making too many queries
|
// TODO: upload the files then set the links in the entity like with compo to avoid making too many queries
|
||||||
|
// FIXME: upload in components
|
||||||
if (files && Object.keys(files).length > 0) {
|
if (files && Object.keys(files).length > 0) {
|
||||||
await this.uploadFiles(uid, entity, files);
|
await this.uploadFiles(uid, entity, files);
|
||||||
entity = await this.findOne(uid, entity.id, { params });
|
entity = await this.findOne(uid, entity.id, { params });
|
||||||
@ -194,6 +195,7 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
|
|||||||
});
|
});
|
||||||
|
|
||||||
// TODO: upload the files then set the links in the entity like with compo to avoid making too many queries
|
// TODO: upload the files then set the links in the entity like with compo to avoid making too many queries
|
||||||
|
// FIXME: upload in components
|
||||||
if (files && Object.keys(files).length > 0) {
|
if (files && Object.keys(files).length > 0) {
|
||||||
await this.uploadFiles(uid, entity, files);
|
await this.uploadFiles(uid, entity, files);
|
||||||
entity = await this.findOne(uid, entity.id, { params });
|
entity = await this.findOne(uid, entity.id, { params });
|
||||||
|
@ -77,7 +77,14 @@ const transformParamsToQuery = (uid, params = {}) => {
|
|||||||
if (populate) {
|
if (populate) {
|
||||||
// TODO: handle *.* syntax
|
// TODO: handle *.* syntax
|
||||||
const { populate } = params;
|
const { populate } = params;
|
||||||
query.populate = typeof populate === 'object' ? populate : _.castArray(populate);
|
|
||||||
|
if (populate === '*') {
|
||||||
|
query.populate = true;
|
||||||
|
} else if (typeof populate === 'object') {
|
||||||
|
query.populate = populate;
|
||||||
|
} else {
|
||||||
|
query.populate = _.castArray(populate);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: move to layer above ?
|
// TODO: move to layer above ?
|
||||||
|
@ -154,6 +154,8 @@ describe('Publication State', () => {
|
|||||||
strapi = await createStrapiInstance();
|
strapi = await createStrapiInstance();
|
||||||
rq = await createAuthRequest({ strapi });
|
rq = await createAuthRequest({ strapi });
|
||||||
|
|
||||||
|
console.log(JSON.stringify(builder.sanitizedFixtures(strapi), null, 2));
|
||||||
|
|
||||||
Object.assign(data, builder.sanitizedFixtures(strapi));
|
Object.assign(data, builder.sanitizedFixtures(strapi));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user