diff --git a/lib/MySQL/translator.js b/lib/MySQL/translator.js index 32f3071..74f65c8 100644 --- a/lib/MySQL/translator.js +++ b/lib/MySQL/translator.js @@ -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}`; diff --git a/src/MySQL/translator.ts b/src/MySQL/translator.ts index f937963..2a62b90 100644 --- a/src/MySQL/translator.ts +++ b/src/MySQL/translator.ts @@ -468,7 +468,7 @@ export class MySqlTranslator 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 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 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 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 = { + '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 = { + '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 = { + '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 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 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 extends Sql return sql; } -} \ No newline at end of file +} \ No newline at end of file