fix: 修复mysql中dateDiff、dateCeil、dateFloor的部分问题

This commit is contained in:
Pan Qiancheng 2026-01-01 11:25:06 +08:00
parent f6322dffff
commit 3a17335937
2 changed files with 313 additions and 22 deletions

View File

@ -796,10 +796,11 @@ class MySqlTranslator extends sqlTranslator_1.SqlTranslator {
case '$dayOfYear': {
return 'DAYOFYEAR(%s)';
}
case '$dateDiff': {
(0, assert_1.default)(argumentNumber === 3);
return 'DATEDIFF(%s, %s, %s)';
}
// 这个实现有问题DATEDIFF只是计算两个日期之间的天数差只接受两个参数放在translateExperession里实现
// case '$dateDiff': {
// assert(argumentNumber === 3);
// return 'DATEDIFF(%s, %s, %s)';
// }
case '$contains': {
(0, assert_1.default)(argumentNumber === 2);
return 'ST_CONTAINS(%s, %s)';
@ -816,6 +817,22 @@ class MySqlTranslator extends sqlTranslator_1.SqlTranslator {
result += ')';
return result;
}
// ========== 聚合函数 ==========
case '$$count': {
return 'COUNT(%s)';
}
case '$$sum': {
return 'SUM(%s)';
}
case '$$max': {
return 'MAX(%s)';
}
case '$$min': {
return 'MIN(%s)';
}
case '$$avg': {
return 'AVG(%s)';
}
default: {
throw new Error(`unrecoganized function ${fnName}`);
}
@ -859,10 +876,135 @@ class MySqlTranslator extends sqlTranslator_1.SqlTranslator {
}
else {
(0, assert_1.default)(k.length === 1);
if ((expr)[k[0]] instanceof Array) {
const fnName = this.translateFnName(k[0], (expr)[k[0]].length);
const fnKey = k[0];
const fnArgs = (expr)[fnKey];
// 特殊处理日期相关函数
if (fnKey === '$dateDiff') {
// $dateDiff: [date1, date2, unit]
(0, assert_1.default)(fnArgs instanceof Array && fnArgs.length === 3);
const [date1Expr, date2Expr, unit] = fnArgs;
// 转换日期表达式
const translateDateArg = (arg) => {
if (arg instanceof Date) {
return `FROM_UNIXTIME(${arg.valueOf()} / 1000)`;
}
else if (typeof arg === 'number') {
return `FROM_UNIXTIME(${arg} / 1000)`;
}
else {
return translateInner(arg);
}
};
const date1Str = translateDateArg(date1Expr);
const date2Str = translateDateArg(date2Expr);
// MySQL TIMESTAMPDIFF 单位映射
const unitMap = {
's': 'SECOND',
'm': 'MINUTE',
'h': 'HOUR',
'd': 'DAY',
'M': 'MONTH',
'y': 'YEAR'
};
const mysqlUnit = unitMap[unit];
if (!mysqlUnit) {
throw new Error(`Unsupported date diff unit: ${unit}`);
}
// TIMESTAMPDIFF(unit, date1, date2) 返回 date2 - date1
// 但类型定义是 date1 - date2所以参数顺序要反过来
result = `TIMESTAMPDIFF(${mysqlUnit}, ${date2Str}, ${date1Str})`;
}
else if (fnKey === '$dateCeil') {
// $dateCeil: [date, unit]
(0, assert_1.default)(fnArgs instanceof Array && fnArgs.length === 2);
const [dateExpr, unit] = fnArgs;
const getTimestampExpr = (arg) => {
if (arg instanceof Date) {
return `${arg.valueOf()}`;
}
else if (typeof arg === 'number') {
return `${arg}`;
}
else {
const k = Object.keys(arg);
if (k.includes('#attr')) {
return `\`${alias}\`.\`${arg['#attr']}\``;
}
else if (k.includes('#refId')) {
const refId = arg['#refId'];
const refAttr = arg['#refAttr'];
return `\`${refDict[refId][0]}\`.\`${refAttr}\``;
}
return translateInner(arg);
}
};
const tsExpr = getTimestampExpr(dateExpr);
const msPerUnit = {
's': 1000,
'm': 60000,
'h': 3600000,
'd': 86400000,
};
if (msPerUnit[unit]) {
// CEIL 向上取整
result = `CEIL(${tsExpr} / ${msPerUnit[unit]}) * ${msPerUnit[unit]}`;
}
else {
(0, assert_1.default)(typeof unit === 'string', 'unit should be string');
console.warn('暂不支持 $dateCeil 对月年单位的处理');
throw new Error(`Unsupported date ceil unit: ${unit}`);
}
}
else if (fnKey === '$dateFloor') {
// $dateFloor: [date, unit]
(0, assert_1.default)(fnArgs instanceof Array && fnArgs.length === 2);
const [dateExpr, unit] = fnArgs;
// 获取毫秒时间戳表达式
const getTimestampExpr = (arg) => {
if (arg instanceof Date) {
return `${arg.valueOf()}`;
}
else if (typeof arg === 'number') {
return `${arg}`;
}
else {
// 属性引用,直接返回属性(存储本身就是毫秒时间戳)
const k = Object.keys(arg);
if (k.includes('#attr')) {
return `\`${alias}\`.\`${arg['#attr']}\``;
}
else if (k.includes('#refId')) {
const refId = arg['#refId'];
const refAttr = arg['#refAttr'];
return `\`${refDict[refId][0]}\`.\`${refAttr}\``;
}
// 其他表达式递归处理
return translateInner(arg);
}
};
const tsExpr = getTimestampExpr(dateExpr);
// 固定间隔单位:直接用时间戳数学运算
const msPerUnit = {
's': 1000,
'm': 60000,
'h': 3600000,
'd': 86400000,
};
if (msPerUnit[unit]) {
// FLOOR(timestamp / interval) * interval
result = `FLOOR(${tsExpr} / ${msPerUnit[unit]}) * ${msPerUnit[unit]}`;
}
else {
(0, assert_1.default)(typeof unit === 'string', 'unit should be string');
console.warn('暂不支持 $dateFloor 对月年单位的处理');
throw new Error(`Unsupported date floor unit: ${unit}`);
}
}
else if (fnArgs instanceof Array) {
// 原有的数组参数处理逻辑
const fnName = this.translateFnName(fnKey, fnArgs.length);
const args = [fnName];
args.push(...(expr)[k[0]].map((ele) => {
args.push(...fnArgs.map((ele) => {
if (['string', 'number'].includes(typeof ele) || ele instanceof Date) {
return translateConstant(ele);
}
@ -873,9 +1015,10 @@ class MySqlTranslator extends sqlTranslator_1.SqlTranslator {
result = util_1.format.apply(null, args);
}
else {
const fnName = this.translateFnName(k[0], 1);
// 原有的单参数处理逻辑
const fnName = this.translateFnName(fnKey, 1);
const args = [fnName];
const arg = (expr)[k[0]];
const arg = fnArgs;
if (['string', 'number'].includes(typeof arg) || arg instanceof Date) {
args.push(translateConstant(arg));
}
@ -949,7 +1092,7 @@ class MySqlTranslator extends sqlTranslator_1.SqlTranslator {
}
// 新的remove应该包含$$deleteAt$$的值了
/* const now = Date.now();
const updateText2 = updateText ? `${updateText}, \`${alias}\`.\`$$deleteAt$$\` = '${now}'` : `\`${alias}\`.\`$$deleteAt$$\` = '${now}'`; */
(0, assert_1.default)(updateText.includes(types_1.DeleteAtAttribute));
let sql = `update ${fromText} set ${updateText}`;

View File

@ -468,7 +468,7 @@ export class MySqlTranslator<ED extends EntityDict & BaseEntityDict> extends Sql
}
}
else {
assert (typeof length === 'object');
assert(typeof length === 'object');
const op = Object.keys(length)[0];
assert(op.startsWith('$'));
if (p) {
@ -885,10 +885,13 @@ export class MySqlTranslator<ED extends EntityDict & BaseEntityDict> extends Sql
case '$dayOfYear': {
return 'DAYOFYEAR(%s)';
}
case '$dateDiff': {
assert(argumentNumber === 3);
return 'DATEDIFF(%s, %s, %s)';
}
// 这个实现有问题DATEDIFF只是计算两个日期之间的天数差只接受两个参数放在translateExperession里实现
// case '$dateDiff': {
// assert(argumentNumber === 3);
// return 'DATEDIFF(%s, %s, %s)';
// }
case '$contains': {
assert(argumentNumber === 2);
return 'ST_CONTAINS(%s, %s)';
@ -905,6 +908,24 @@ export class MySqlTranslator<ED extends EntityDict & BaseEntityDict> extends Sql
result += ')';
return result;
}
// ========== 聚合函数 ==========
case '$$count': {
return 'COUNT(%s)';
}
case '$$sum': {
return 'SUM(%s)';
}
case '$$max': {
return 'MAX(%s)';
}
case '$$min': {
return 'MIN(%s)';
}
case '$$avg': {
return 'AVG(%s)';
}
default: {
throw new Error(`unrecoganized function ${fnName}`);
}
@ -951,10 +972,136 @@ export class MySqlTranslator<ED extends EntityDict & BaseEntityDict> extends Sql
}
else {
assert(k.length === 1);
if ((expr)[k[0]] instanceof Array) {
const fnName = this.translateFnName(k[0], (expr)[k[0]].length);
const fnKey = k[0];
const fnArgs = (expr)[fnKey];
// 特殊处理日期相关函数
if (fnKey === '$dateDiff') {
// $dateDiff: [date1, date2, unit]
assert(fnArgs instanceof Array && fnArgs.length === 3);
const [date1Expr, date2Expr, unit] = fnArgs;
// 转换日期表达式
const translateDateArg = (arg: any): string => {
if (arg instanceof Date) {
return `FROM_UNIXTIME(${arg.valueOf()} / 1000)`;
} else if (typeof arg === 'number') {
return `FROM_UNIXTIME(${arg} / 1000)`;
} else {
return translateInner(arg);
}
};
const date1Str = translateDateArg(date1Expr);
const date2Str = translateDateArg(date2Expr);
// MySQL TIMESTAMPDIFF 单位映射
const unitMap: Record<string, string> = {
's': 'SECOND',
'm': 'MINUTE',
'h': 'HOUR',
'd': 'DAY',
'M': 'MONTH',
'y': 'YEAR'
};
const mysqlUnit = unitMap[unit];
if (!mysqlUnit) {
throw new Error(`Unsupported date diff unit: ${unit}`);
}
// TIMESTAMPDIFF(unit, date1, date2) 返回 date2 - date1
// 但类型定义是 date1 - date2所以参数顺序要反过来
result = `TIMESTAMPDIFF(${mysqlUnit}, ${date2Str}, ${date1Str})`;
} else if (fnKey === '$dateCeil') {
// $dateCeil: [date, unit]
assert(fnArgs instanceof Array && fnArgs.length === 2);
const [dateExpr, unit] = fnArgs;
const getTimestampExpr = (arg: any): string => {
if (arg instanceof Date) {
return `${arg.valueOf()}`;
} else if (typeof arg === 'number') {
return `${arg}`;
} else {
const k = Object.keys(arg);
if (k.includes('#attr')) {
return `\`${alias}\`.\`${arg['#attr']}\``;
} else if (k.includes('#refId')) {
const refId = arg['#refId'];
const refAttr = arg['#refAttr'];
return `\`${refDict[refId][0]}\`.\`${refAttr}\``;
}
return translateInner(arg);
}
};
const tsExpr = getTimestampExpr(dateExpr);
const msPerUnit: Record<string, number> = {
's': 1000,
'm': 60000,
'h': 3600000,
'd': 86400000,
};
if (msPerUnit[unit]) {
// CEIL 向上取整
result = `CEIL(${tsExpr} / ${msPerUnit[unit]}) * ${msPerUnit[unit]}`;
} else {
assert(typeof unit === 'string', 'unit should be string');
console.warn('暂不支持 $dateCeil 对月年单位的处理');
throw new Error(`Unsupported date ceil unit: ${unit}`);
}
} else if (fnKey === '$dateFloor') {
// $dateFloor: [date, unit]
assert(fnArgs instanceof Array && fnArgs.length === 2);
const [dateExpr, unit] = fnArgs;
// 获取毫秒时间戳表达式
const getTimestampExpr = (arg: any): string => {
if (arg instanceof Date) {
return `${arg.valueOf()}`;
} else if (typeof arg === 'number') {
return `${arg}`;
} else {
// 属性引用,直接返回属性(存储本身就是毫秒时间戳)
const k = Object.keys(arg);
if (k.includes('#attr')) {
return `\`${alias}\`.\`${arg['#attr']}\``;
} else if (k.includes('#refId')) {
const refId = arg['#refId'];
const refAttr = arg['#refAttr'];
return `\`${refDict[refId][0]}\`.\`${refAttr}\``;
}
// 其他表达式递归处理
return translateInner(arg);
}
};
const tsExpr = getTimestampExpr(dateExpr);
// 固定间隔单位:直接用时间戳数学运算
const msPerUnit: Record<string, number> = {
's': 1000,
'm': 60000,
'h': 3600000,
'd': 86400000,
};
if (msPerUnit[unit]) {
// FLOOR(timestamp / interval) * interval
result = `FLOOR(${tsExpr} / ${msPerUnit[unit]}) * ${msPerUnit[unit]}`;
} else {
assert(typeof unit === 'string', 'unit should be string');
console.warn('暂不支持 $dateFloor 对月年单位的处理');
throw new Error(`Unsupported date floor unit: ${unit}`);
}
} else if (fnArgs instanceof Array) {
// 原有的数组参数处理逻辑
const fnName = this.translateFnName(fnKey, fnArgs.length);
const args = [fnName];
args.push(...(expr)[k[0]].map(
args.push(...fnArgs.map(
(ele: any) => {
if (['string', 'number'].includes(typeof ele) || ele instanceof Date) {
return translateConstant(ele);
@ -968,9 +1115,10 @@ export class MySqlTranslator<ED extends EntityDict & BaseEntityDict> extends Sql
result = format.apply(null, args);
}
else {
const fnName = this.translateFnName(k[0], 1);
// 原有的单参数处理逻辑
const fnName = this.translateFnName(fnKey, 1);
const args = [fnName];
const arg = (expr)[k[0]];
const arg = fnArgs;
if (['string', 'number'].includes(typeof arg) || arg instanceof Date) {
args.push(translateConstant(arg));
}
@ -1060,7 +1208,7 @@ export class MySqlTranslator<ED extends EntityDict & BaseEntityDict> extends Sql
// 新的remove应该包含$$deleteAt$$的值了
/* const now = Date.now();
const updateText2 = updateText ? `${updateText}, \`${alias}\`.\`$$deleteAt$$\` = '${now}'` : `\`${alias}\`.\`$$deleteAt$$\` = '${now}'`; */
assert(updateText.includes(DeleteAtAttribute));
let sql = `update ${fromText} set ${updateText}`;
@ -1077,4 +1225,4 @@ export class MySqlTranslator<ED extends EntityDict & BaseEntityDict> extends Sql
return sql;
}
}
}