tachybase_todo/packages/database/src/op.ts

157 lines
5.0 KiB
TypeScript
Raw Normal View History

import { Op, Utils, Sequelize } from 'sequelize';
import moment, { MomentInput } from 'moment';
function toArray(value: any): any[] {
if (value == null) {
return [];
}
return Array.isArray(value) ? value : [value];
}
function stringToDate(value: string): Date {
return moment(value).toDate();
}
function getNextDay(value: MomentInput): Date {
return moment(value).add(1, 'd').toDate();
}
const op = new Map<string, typeof Op | Function>();
// Sequelize 内置
for (const key in Op) {
op.set(key, Op[key]);
const val = Utils.underscoredIf(key, true);
op.set(val, Op[key]);
op.set(val.replace(/_/g, ''), Op[key]);
}
// 通用
// 是否为空:数据库意义的 null
2021-03-28 13:34:51 +08:00
op.set('$null', (value, { fieldPath, database }) => {
2020-12-29 14:53:39 +08:00
// const field = database.getFieldByPath(fieldPath);
// console.log({field});
return { [Op.is]: null };
});
op.set('$notNull', () => ({ [Op.not]: null }));
op.set('$isTruly', () => ({
[Op.eq]: true,
}));
op.set('$isFalsy', () => ({
[Op.or]: [
{
[Op.eq]: false,
},
{
[Op.is]: null,
},
],
}));
// 字符串
// 包含:指对应字段的值包含某个子串
op.set('$includes', (value: string) => ({ [Op.iLike]: `%${value}%` }));
// 不包含:指对应字段的值不包含某个子串(慎用:性能问题)
op.set('$notIncludes', (value: string) => ({ [Op.notILike]: `%${value}%` }));
// 以之起始
op.set('$startsWith', (value: string) => ({ [Op.iLike]: `${value}%` }));
// 不以之起始
op.set('$notStartsWith', (value: string) => ({ [Op.notILike]: `${value}%` }));
// 以之结束
op.set('$endsWith', (value: string) => ({ [Op.iLike]: `%${value}` }));
// 不以之结束
op.set('$notEndsWith', (value: string) => ({ [Op.notILike]: `%${value}` }));
// 仅日期
// 在某日
op.set('$dateOn', (value: string) => ({ [Op.and]: [{ [Op.gte]: stringToDate(value) }, { [Op.lt]: getNextDay(value) }] }));
// 不在某日
op.set('$dateNotOn', (value: string) => ({ [Op.or]: [{ [Op.lt]: stringToDate(value) }, { [Op.gte]: getNextDay(value) }] }));
// 某日前
op.set('$dateBefore', (value: string) => ({ [Op.lt]: stringToDate(value) }));
// 某日后
op.set('$dateAfter', (value: string) => ({ [Op.gte]: getNextDay(value) }));
// 不早于(含当天)
op.set('$dateNotBefore', (value: string) => ({ [Op.gte]: stringToDate(value) }));
// 不晚于(含当天)
op.set('$dateNotAfter', (value: string) => ({ [Op.lt]: getNextDay(value) }));
// 在期间
op.set('$dateBetween', ([from, to]: string[]) => ({ [Op.and]: [{ [Op.gte]: stringToDate(from) }, { [Op.lt]: getNextDay(to) }] }));
// 不在期间
op.set('$dateNotBetween', ([from, to]: string[]) => ({ [Op.or]: [{ [Op.lt]: stringToDate(from) }, { [Op.gte]: getNextDay(to) }] }));
// 多选JSON类型
// 包含组中任意值(命名来源:`Array.prototype.some`
2020-12-29 14:53:39 +08:00
op.set('$anyOf', (values: any[], options) => {
if (!values) {
return Sequelize.literal('');
}
values = Array.isArray(values) ? values : [values];
if (values.length === 0) {
return Sequelize.literal('');
}
const { field, fieldPath } = options;
const column = fieldPath.split('.').map(name => `"${name}"`).join('.');
const sql = values.map(value => `(${column})::jsonb @> '${JSON.stringify(value)}'`).join(' OR ');
console.log(sql);
return Sequelize.literal(sql);
});
// 包含组中所有值
op.set('$allOf', (values: any) => ({ [Op.contains]: toArray(values) }));
// TODO(bug): 不包含组中任意值
op.set('$noneOf', (values: any[], options) => {
if (!values) {
return Sequelize.literal('');
}
values = Array.isArray(values) ? values : [values];
2020-12-29 14:53:39 +08:00
if (values.length === 0) {
return Sequelize.literal('');
}
const { field, fieldPath } = options;
const column = fieldPath.split('.').map(name => `"${name}"`).join('.');
const sql = values.map(value => `(${column})::jsonb @> '${JSON.stringify(value)}'`).join(' OR ');
console.log(sql);
return Sequelize.literal(`not (${sql})`);
});
// 与组中值匹配
2020-12-29 14:53:39 +08:00
op.set('$match', (values: any[], options) => {
const array = toArray(values);
2020-12-29 14:53:39 +08:00
if (values.length === 0) {
return Sequelize.literal('');
}
const { field, fieldPath } = options;
const column = fieldPath.split('.').map(name => `"${name}"`).join('.');
const sql = `(${column})::jsonb @> '${JSON.stringify(array)}' AND (${column})::jsonb <@ '${JSON.stringify(array)}'`
return Sequelize.literal(sql);
});
op.set('$notMatch', (values: any[], options) => {
const array = toArray(values);
if (values.length === 0) {
return Sequelize.literal('');
}
const { field, fieldPath } = options;
const column = fieldPath.split('.').map(name => `"${name}"`).join('.');
const sql = `(${column})::jsonb @> '${JSON.stringify(array)}' AND (${column})::jsonb <@ '${JSON.stringify(array)}'`
return Sequelize.literal(`not (${sql})`);
// return Sequelize.literal(`(not (${sql})) AND ${column} IS NULL`);
});
export default class Operator {
public static get(key: string) {
return op.get(key);
}
public static has(key: string) {
return op.has(key);
}
public static register(key: string, fn: Function) {
op.set(key, fn);
}
};