mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 13:39:24 +08:00
* feat: create nocobase app with simple & quickstart option * chore: delete template file * create-nocobase-app: add env API_PORT fallback * chore: log * env default fallback * move config dir * change has yarn * chore: prettier * fix: npm running issue * database testing support sqlite * once... * chore: typo * fix: sqlite test * update readme * feat: copy .env.example to .env at create-nocobase-app * create-nocobase-app: change sqlite3 to github master * create-nocobase-app: .env template * create-nocobase-app: update .env * chore: typo * update README * chore: Application constructor * feat: sqlite demo data support * fix test * fix: application error * chore: plugin-client run sql * fix: application createCli * fix: can choose whether to register actions * chore: model compile error * fix: support sqlite * fix: demo data set index sequence on postgresql * chore: code reduce * fix: operators are compatible with sqlite * add impor demo option to init command * update env Co-authored-by: chenos <chenlinxh@gmail.com>
197 lines
5.4 KiB
TypeScript
197 lines
5.4 KiB
TypeScript
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
|
||
op.set('$null', (value, { fieldPath, database }) => {
|
||
// 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, { dialect }) => ({
|
||
[dialect === 'postgres' ? Op.iLike : Op.like]: `%${value}%`,
|
||
}));
|
||
// 不包含:指对应字段的值不包含某个子串(慎用:性能问题)
|
||
op.set('$notIncludes', (value: string, { dialect }) => ({
|
||
[dialect === 'postgres' ? Op.notILike : Op.notLike]: `%${value}%`,
|
||
}));
|
||
// 以之起始
|
||
op.set('$startsWith', (value: string, { dialect }) => ({
|
||
[dialect === 'postgres' ? Op.iLike : Op.like]: `${value}%`,
|
||
}));
|
||
// 不以之起始
|
||
op.set('$notStartsWith', (value: string, { dialect }) => ({
|
||
[dialect === 'postgres' ? Op.notILike : Op.notLike]: `${value}%`,
|
||
}));
|
||
// 以之结束
|
||
op.set('$endsWith', (value: string, { dialect }) => ({
|
||
[dialect === 'postgres' ? Op.iLike : Op.like]: `%${value}`,
|
||
}));
|
||
// 不以之结束
|
||
op.set('$notEndsWith', (value: string, { dialect }) => ({
|
||
[dialect === 'postgres' ? Op.notILike : Op.notLike]: `%${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`)
|
||
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 ');
|
||
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];
|
||
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 ');
|
||
return Sequelize.literal(`not (${sql})`);
|
||
});
|
||
// 与组中值匹配
|
||
op.set('$match', (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(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);
|
||
}
|
||
}
|