move operators to utils

This commit is contained in:
Ben Irvin 2023-05-12 12:13:29 +02:00
parent 7747d1d3d1
commit 980edbbcce
6 changed files with 99 additions and 66 deletions

View File

@ -1,55 +1,14 @@
'use strict';
const _ = require('lodash/fp');
const { isArray, castArray, keys, isPlainObject } = require('lodash/fp');
const { isOperatorOfType } = require('@strapi/utils').operators;
const types = require('../../types');
const { createField } = require('../../fields');
const { createJoin } = require('./join');
const { toColumnName } = require('./transform');
const { isKnexQuery } = require('../../utils/knex');
const GROUP_OPERATORS = ['$and', '$or'];
const OPERATORS = [
'$not',
'$in',
'$notIn',
'$eq',
'$eqi',
'$ne',
'$gt',
'$gte',
'$lt',
'$lte',
'$null',
'$notNull',
'$between',
'$startsWith',
'$endsWith',
'$startsWithi',
'$endsWithi',
'$contains',
'$notContains',
'$containsi',
'$notContainsi',
];
const CAST_OPERATORS = [
'$not',
'$in',
'$notIn',
'$eq',
'$ne',
'$gt',
'$gte',
'$lt',
'$lte',
'$between',
];
const ARRAY_OPERATORS = ['$in', '$notIn', '$between'];
const isOperator = (key) => OPERATORS.includes(key);
const castValue = (value, attribute) => {
if (!attribute) {
return value;
@ -65,12 +24,12 @@ const castValue = (value, attribute) => {
};
const processAttributeWhere = (attribute, where, operator = '$eq') => {
if (_.isArray(where)) {
if (isArray(where)) {
return where.map((sub) => processAttributeWhere(attribute, sub, operator));
}
if (!_.isPlainObject(where)) {
if (CAST_OPERATORS.includes(operator)) {
if (!isPlainObject(where)) {
if (isOperatorOfType('cast', operator)) {
return castValue(where, attribute);
}
@ -82,7 +41,7 @@ const processAttributeWhere = (attribute, where, operator = '$eq') => {
for (const key of Object.keys(where)) {
const value = where[key];
if (!isOperator(key)) {
if (!isOperatorOfType('where', key)) {
throw new Error(`Undefined attribute level operator ${key}`);
}
@ -100,16 +59,16 @@ const processAttributeWhere = (attribute, where, operator = '$eq') => {
* @returns {Object}
*/
const processWhere = (where, ctx) => {
if (!_.isArray(where) && !_.isPlainObject(where)) {
if (!isArray(where) && !isPlainObject(where)) {
throw new Error('Where must be an array or an object');
}
if (_.isArray(where)) {
if (isArray(where)) {
return where.map((sub) => processWhere(sub, ctx));
}
const processNested = (where, ctx) => {
if (!_.isPlainObject(where)) {
if (!isPlainObject(where)) {
return where;
}
@ -126,7 +85,7 @@ const processWhere = (where, ctx) => {
const value = where[key];
// if operator $and $or then loop over them
if (GROUP_OPERATORS.includes(key)) {
if (isOperatorOfType('group', key)) {
filters[key] = value.map((sub) => processNested(sub, ctx));
continue;
}
@ -136,7 +95,7 @@ const processWhere = (where, ctx) => {
continue;
}
if (isOperator(key)) {
if (isOperatorOfType('where', key)) {
throw new Error(
`Only $and, $or and $not can only be used as root level operators. Found ${key}.`
);
@ -165,7 +124,7 @@ const processWhere = (where, ctx) => {
uid: attribute.target,
});
if (!_.isPlainObject(nestedWhere) || isOperator(_.keys(nestedWhere)[0])) {
if (!isPlainObject(nestedWhere) || isOperatorOfType('where', keys(nestedWhere)[0])) {
nestedWhere = { [qb.aliasColumn('id', subAlias)]: nestedWhere };
}
@ -192,7 +151,7 @@ const processWhere = (where, ctx) => {
// TODO: add type casting per operator at some point
const applyOperator = (qb, column, operator, value) => {
if (Array.isArray(value) && !ARRAY_OPERATORS.includes(operator)) {
if (Array.isArray(value) && !isOperatorOfType('array', operator)) {
return qb.where((subQB) => {
value.forEach((subValue) =>
subQB.orWhere((innerQB) => {
@ -209,12 +168,12 @@ const applyOperator = (qb, column, operator, value) => {
}
case '$in': {
qb.whereIn(column, isKnexQuery(value) ? value : _.castArray(value));
qb.whereIn(column, isKnexQuery(value) ? value : castArray(value));
break;
}
case '$notIn': {
qb.whereNotIn(column, isKnexQuery(value) ? value : _.castArray(value));
qb.whereNotIn(column, isKnexQuery(value) ? value : castArray(value));
break;
}
@ -328,7 +287,7 @@ const applyOperator = (qb, column, operator, value) => {
};
const applyWhereToColumn = (qb, column, columnWhere) => {
if (!_.isPlainObject(columnWhere)) {
if (!isPlainObject(columnWhere)) {
if (Array.isArray(columnWhere)) {
return qb.whereIn(column, columnWhere);
}
@ -344,11 +303,11 @@ const applyWhereToColumn = (qb, column, columnWhere) => {
};
const applyWhere = (qb, where) => {
if (!_.isArray(where) && !_.isPlainObject(where)) {
if (!isArray(where) && !isPlainObject(where)) {
throw new Error('Where must be an array or an object');
}
if (_.isArray(where)) {
if (isArray(where)) {
return qb.where((subQB) => where.forEach((subWhere) => applyWhere(subQB, subWhere)));
}

View File

@ -33,6 +33,7 @@
"lint": "run -T eslint ."
},
"dependencies": {
"@strapi/utils": "4.10.2",
"date-fns": "2.29.3",
"debug": "4.3.4",
"fs-extra": "10.0.0",

View File

@ -29,6 +29,7 @@ const {
isDynamicZoneAttribute,
isMorphToRelationalAttribute,
} = require('./content-types');
const { isOperator } = require('./operators');
const { PUBLISHED_AT_ATTRIBUTE } = contentTypesUtils.constants;
@ -378,7 +379,7 @@ const convertNestedPopulate = (subPopulate, schema) => {
return query;
};
// TODO: ensure field is valid in content types (will probably have to check strapi.contentTypes since it can be a string.path)
// TODO: ensure field is valid in content types (will probably have to check strapi.contentTypes since it can be a string.path)
const convertFieldsQueryParams = (fields, depth = 0) => {
if (depth === 0 && fields === '*') {
return undefined;
@ -398,11 +399,6 @@ const convertFieldsQueryParams = (fields, depth = 0) => {
throw new Error('Invalid fields parameter. Expected a string or an array of strings');
};
// TODO: this is temporary as a POC, get operators from @strapi/database (will likely require refactoring)
const isOperator = (key) => {
return key.startsWith('$');
};
const isValidSchemaAttribute = (key, schema) => {
if (key.trim().toLowerCase() === 'id') {
return true;
@ -459,7 +455,7 @@ const convertAndSanitizeFilters = (filters, schema) => {
// Here, `key` can either be an operator or an attribute name
for (const [key, value] of Object.entries(filters)) {
const attribute = get(key, schema?.attributes);
const validKey = isOperator(key) || isValidSchemaAttribute(key, schema);
const validKey = isOperator(key, true) || isValidSchemaAttribute(key, schema);
if (!validKey) {
removeOperator(key);

View File

@ -43,6 +43,7 @@ const importDefault = require('./import-default');
const template = require('./template');
const file = require('./file');
const traverse = require('./traverse');
const operators = require('./operators');
module.exports = {
yup,
@ -93,4 +94,5 @@ module.exports = {
importDefault,
file,
traverse,
operators,
};

View File

@ -0,0 +1,74 @@
'use strict';
const GROUP_OPERATORS = ['$and', '$or'];
const WHERE_OPERATORS = [
'$not',
'$in',
'$notIn',
'$eq',
'$eqi',
'$ne',
'$gt',
'$gte',
'$lt',
'$lte',
'$null',
'$notNull',
'$between',
'$startsWith',
'$endsWith',
'$startsWithi',
'$endsWithi',
'$contains',
'$notContains',
'$containsi',
'$notContainsi',
];
const CAST_OPERATORS = [
'$not',
'$in',
'$notIn',
'$eq',
'$ne',
'$gt',
'$gte',
'$lt',
'$lte',
'$between',
];
const ARRAY_OPERATORS = ['$in', '$notIn', '$between'];
const OPERATORS = {
where: WHERE_OPERATORS,
cast: CAST_OPERATORS,
group: GROUP_OPERATORS,
array: ARRAY_OPERATORS,
};
// for performance, cache all operators in lowercase
const OPERATORS_LOWERCASE = Object.fromEntries(
Object.entries(OPERATORS).map(([key, values]) => [
key,
values.map((value) => value.toLowerCase()),
])
);
const isOperatorOfType = (type, key, ignoreCase = false) => {
if (ignoreCase) {
return OPERATORS_LOWERCASE[type]?.includes(key.toLowerCase());
}
return OPERATORS[type]?.includes(key);
};
const isOperator = (key, ignoreCase = false) => {
return Object.keys(OPERATORS).some((type) => isOperatorOfType(type, key, ignoreCase));
};
module.exports = {
isOperator,
isOperatorOfType,
OPERATORS,
};

View File

@ -7872,6 +7872,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@strapi/database@workspace:packages/core/database"
dependencies:
"@strapi/utils": 4.10.2
date-fns: 2.29.3
debug: 4.3.4
fs-extra: 10.0.0