"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MySqlTranslator = void 0; const tslib_1 = require("tslib"); const assert_1 = tslib_1.__importDefault(require("assert")); const util_1 = require("util"); const lodash_1 = require("lodash"); const types_1 = require("oak-domain/lib/types"); const sqlTranslator_1 = require("../sqlTranslator"); const GeoTypes = [ { type: 'point', name: "Point" }, { type: 'path', name: "LineString", element: 'point', }, { name: "MultiLineString", element: "path", multiple: true, }, { type: 'polygon', name: "Polygon", element: "path" }, { name: "MultiPoint", element: "point", multiple: true, }, { name: "MultiPolygon", element: "polygon", multiple: true, } ]; function transformGeoData(data) { if (data instanceof Array) { const element = data[0]; if (element instanceof Array) { return ` GeometryCollection(${data.map(ele => transformGeoData(ele)).join(',')})`; } else { const geoType = GeoTypes.find(ele => ele.type === element.type); if (!geoType) { throw new Error(`${element.type} is not supported in MySQL`); } const multiGeoType = GeoTypes.find(ele => ele.element === geoType.type && ele.multiple); return ` ${multiGeoType.name}(${data.map(ele => transformGeoData(ele)).join(',')})`; } } else { const { type, coordinate } = data; const geoType = GeoTypes.find(ele => ele.type === type); if (!geoType) { throw new Error(`${data.type} is not supported in MySQL`); } const { element, name } = geoType; if (!element) { // Point return ` ${name}(${coordinate.join(',')})`; } // Polygon or Linestring return ` ${name}(${coordinate.map((ele) => transformGeoData({ type: element, coordinate: ele, }))})`; } } class MySqlTranslator extends sqlTranslator_1.SqlTranslator { getDefaultSelectFilter(alias, option) { if (option?.includedDeleted) { return ''; } return ` (\`${alias}\`.\`$$deleteAt$$\` is null)`; } makeUpSchema() { for (const entity in this.schema) { const { attributes, indexes } = this.schema[entity]; const geoIndexes = []; for (const attr in attributes) { if (attributes[attr].type === 'geometry') { const geoIndex = indexes?.find((idx) => idx.config?.type === 'spatial' && idx.attributes.find((attrDef) => attrDef.name === attr)); if (!geoIndex) { geoIndexes.push({ name: `${entity}_geo_${attr}`, attributes: [{ name: attr, }], config: { type: 'spatial', } }); } } } if (geoIndexes.length > 0) { if (indexes) { indexes.push(...geoIndexes); } else { (0, lodash_1.assign)(this.schema[entity], { indexes: geoIndexes, }); } } } } constructor(schema) { super(schema); // MySQL为geometry属性默认创建索引 this.makeUpSchema(); } static supportedDataTypes = [ // numeric types "bit", "int", "integer", // synonym for int "tinyint", "smallint", "mediumint", "bigint", "float", "double", "double precision", // synonym for double "real", // synonym for double "decimal", "dec", // synonym for decimal "numeric", // synonym for decimal "fixed", // synonym for decimal "bool", // synonym for tinyint "boolean", // synonym for tinyint // date and time types "date", "datetime", "timestamp", "time", "year", // string types "char", "nchar", // synonym for national char "national char", "varchar", "nvarchar", // synonym for national varchar "national varchar", "blob", "text", "tinyblob", "tinytext", "mediumblob", "mediumtext", "longblob", "longtext", "enum", "set", "binary", "varbinary", // json data type "json", // spatial data types "geometry", "point", "linestring", "polygon", "multipoint", "multilinestring", "multipolygon", "geometrycollection" ]; static spatialTypes = [ "geometry", "point", "linestring", "polygon", "multipoint", "multilinestring", "multipolygon", "geometrycollection" ]; static withLengthDataTypes = [ "char", "varchar", "nvarchar", "binary", "varbinary" ]; static withPrecisionDataTypes = [ "decimal", "dec", "numeric", "fixed", "float", "double", "double precision", "real", "time", "datetime", "timestamp" ]; static withScaleDataTypes = [ "decimal", "dec", "numeric", "fixed", "float", "double", "double precision", "real" ]; static unsignedAndZerofillTypes = [ "int", "integer", "smallint", "tinyint", "mediumint", "bigint", "decimal", "dec", "numeric", "fixed", "float", "double", "double precision", "real" ]; static withWidthDataTypes = [ 'int', ]; static dataTypeDefaults = { "varchar": { length: 255 }, "nvarchar": { length: 255 }, "national varchar": { length: 255 }, "char": { length: 1 }, "binary": { length: 1 }, "varbinary": { length: 255 }, "decimal": { precision: 10, scale: 0 }, "dec": { precision: 10, scale: 0 }, "numeric": { precision: 10, scale: 0 }, "fixed": { precision: 10, scale: 0 }, "float": { precision: 12 }, "double": { precision: 22 }, "time": { precision: 0 }, "datetime": { precision: 0 }, "timestamp": { precision: 0 }, "bit": { width: 1 }, "int": { width: 11 }, "integer": { width: 11 }, "tinyint": { width: 4 }, "smallint": { width: 6 }, "mediumint": { width: 9 }, "bigint": { width: 20 } }; maxAliasLength = 63; populateDataTypeDef(type, params, enumeration) { if (['date', 'datetime', 'time', 'sequence'].includes(type)) { return 'bigint '; } if (['object', 'array'].includes(type)) { return 'json '; } if (['image', 'function'].includes(type)) { return 'text '; } if (type === 'ref') { return 'char(36) '; } if (['bool', 'boolean'].includes(type)) { // MySQL读出来就是tinyint(1) return 'tinyint(1) '; } if (type === 'money') { return 'bigint '; } if (type === 'enum') { (0, assert_1.default)(enumeration); return `enum(${enumeration.map(ele => `'${ele}'`).join(',')}) `; } if (MySqlTranslator.withLengthDataTypes.includes(type)) { if (params) { const { length } = params; return `${type}(${length}) `; } else { const { length } = MySqlTranslator.dataTypeDefaults[type]; return `${type}(${length}) `; } } if (MySqlTranslator.withPrecisionDataTypes.includes(type)) { if (params) { const { precision, scale } = params; if (typeof scale === 'number') { return `${type}(${precision}, ${scale}) `; } return `${type}(${precision}) `; } else { const { precision, scale } = MySqlTranslator.dataTypeDefaults[type]; if (typeof scale === 'number') { return `${type}(${precision}, ${scale}) `; } return `${type}(${precision}) `; } } if (MySqlTranslator.withWidthDataTypes.includes(type)) { (0, assert_1.default)(type === 'int'); const { width } = params || { width: 4 }; switch (width) { case 1: { return 'tinyint '; } case 2: { return 'smallint '; } case 3: { return 'mediumint '; } case 4: { return 'int '; } default: { return 'bigint '; } } } return `${type} `; } translateAttrProjection(dataType, alias, attr) { switch (dataType) { case 'geometry': { return ` st_astext(\`${alias}\`.\`${attr}\`)`; } default: { return ` \`${alias}\`.\`${attr}\``; } } } translateObjectPredicate(predicate, alias, attr) { const translateInner = (o, p) => { let stmt2 = ''; if (o instanceof Array) { o.forEach((ele, idx) => { if (ele !== undefined && ele !== null) { const part = translateInner(ele, `${p}[${idx}]`); if (stmt2) { stmt2 += ' and '; } stmt2 += `${part}`; } }); } else if (typeof o === 'object') { for (const attr2 in o) { if (attr2 === '$and') { o[attr2].forEach((ele) => { const part = translateInner(ele, p); if (stmt2) { stmt2 += ' and '; } stmt2 += `${part}`; }); } else if (attr2 === '$or') { let stmtOr = ''; o[attr2].forEach((ele) => { const part = translateInner(ele, p); if (stmtOr) { stmtOr += ' or '; } stmtOr += `${part}`; }); if (stmt2) { stmt2 += ' and '; } stmt2 += `(${stmtOr})`; } else if (attr2 === '$contains') { // json_contains,多值的包含关系 const value = JSON.stringify(o[attr2]); if (stmt2) { stmt2 += ' and '; } if (p) { stmt2 += `(JSON_CONTAINS(${alias}.${attr}->>"$${p}", CAST('${value}' AS JSON)))`; } else { stmt2 += `(JSON_CONTAINS(${alias}.${attr}, CAST('${value}' AS JSON)))`; } } else if (attr2 === '$overlaps') { // json_overlaps,多值的交叉关系 const value = JSON.stringify(o[attr2]); if (stmt2) { stmt2 += ' and '; } if (p) { stmt2 += `(JSON_OVERLAPS(${alias}.${attr}->>"$${p}", CAST('${value}' AS JSON)))`; } else { stmt2 += `(JSON_OVERLAPS(${alias}.${attr}, CAST('${value}' AS JSON)))`; } } else if (attr2 === '$length') { // json length const length = o[attr2]; if (stmt2) { stmt2 += ' and '; } if (typeof length === 'number') { if (p) { stmt2 += `(JSON_LENGTH(${alias}.${attr}->>"$${p}") = ${length})`; } else { stmt2 += `(JSON_LENGTH(${alias}.${attr}) = ${length})`; } } else { (0, assert_1.default)(typeof length === 'object'); const op = Object.keys(length)[0]; (0, assert_1.default)(op.startsWith('$')); if (p) { stmt2 += `(JSON_LENGTH(${alias}.${attr}->>"$${p}") ${this.translatePredicate(op, length[op])})`; } else { stmt2 += `(JSON_LENGTH(${alias}.${attr}) ${this.translatePredicate(op, length[op])})`; } } } else if (attr2.startsWith('$')) { if (stmt2) { stmt2 += ' and '; } if (p) { stmt2 += `(${alias}.${attr}->>"$${p}" ${this.translatePredicate(attr2, o[attr2])})`; } else { stmt2 += `(${alias}.${attr} ${this.translatePredicate(attr2, o[attr2])})`; } } else { // 继续子对象解构 const attr3 = attr2.startsWith('.') ? attr2.slice(1) : attr2; const part = translateInner(o[attr2], `${p}.${attr3}`); if (stmt2) { stmt2 += ' and '; } stmt2 += `${part}`; } } } else { // 直接的属性处理 if (stmt2) { stmt2 += ' and '; } if (typeof o === 'string') { if (p) { stmt2 += `(${alias}.${attr}->>"$${p}" = '${o}')`; } else { // 对根对象的字符串比较 stmt2 += `(${alias}.${attr} = '${o}')`; } } else { (0, assert_1.default)(p); stmt2 += `(${alias}.${attr}->>"$${p}" = ${o})`; } } return stmt2; }; return translateInner(predicate, ''); } translateObjectProjection(projection, alias, attr, prefix) { let stmt = ''; const translateInner = (o, p) => { if (o instanceof Array) { o.forEach((item, idx) => { const p2 = `${p}[${idx}]`; if (typeof item === 'number') { if (stmt) { stmt += ', '; } stmt += `${alias}.${attr}->>"$${p2}"`; stmt += prefix ? ` as \`${prefix}.${attr}${p2}\`` : ` as \`${attr}${p2}\``; } else if (typeof item === 'object') { translateInner(item, p2); } }); } else { for (const key in o) { const p2 = `${p}.${key}`; if (typeof o[key] === 'number') { if (stmt) { stmt += ', '; } stmt += `${alias}.${attr}->>"$${p2}"`; stmt += prefix ? ` as \`${prefix}.${attr}${p2}\`` : ` as \`${attr}${p2}\``; } else { translateInner(o[key], p2); } } } }; translateInner(projection, ''); return stmt; } translateAttrValue(dataType, value) { if (value === null || value === undefined) { return 'null'; } switch (dataType) { case 'geometry': { return transformGeoData(value); } case 'datetime': case 'time': case 'date': { if (value instanceof Date) { return `${value.valueOf()}`; } else if (typeof value === 'number') { return `${value}`; } return `'${(new Date(value)).valueOf()}'`; } case 'object': case 'array': { return this.escapeStringValue(JSON.stringify(value)); } /* case 'function': { return `'${Buffer.from(value.toString()).toString('base64')}'`; } */ default: { if (typeof value === 'string') { return this.escapeStringValue(value); } return value; } } } translateFullTextSearch(value, entity, alias) { const { $search } = value; const { indexes } = this.schema[entity]; const ftIndex = indexes && indexes.find((ele) => { const { config } = ele; return config && config.type === 'fulltext'; }); (0, assert_1.default)(ftIndex); const { attributes } = ftIndex; const columns2 = attributes.map(({ name }) => `${alias}.${name}`); return ` match(${columns2.join(',')}) against ('${$search}' in natural language mode)`; } translateAttributeDef(attr, attrDef) { let sql = `\`${attr}\` `; const { type, params, default: defaultValue, unique, notNull, sequenceStart, enumeration, } = attrDef; sql += this.populateDataTypeDef(type, params, enumeration); if (notNull || type === 'geometry') { sql += ' not null '; } if (unique) { sql += ' unique '; } if (typeof sequenceStart === 'number') { sql += ' auto_increment unique '; } if (defaultValue !== undefined) { (0, assert_1.default)(type !== 'ref'); sql += ` default ${this.translateAttrValue(type, defaultValue)}`; } if (attr === types_1.PrimaryKeyAttribute) { sql += ' primary key'; } return sql; } translateCreateEntity(entity, options) { const ifExists = options?.ifExists || 'drop'; const { schema } = this; const entityDef = schema[entity]; const { storageName, attributes, indexes, view, static: _static } = entityDef; let hasSequence = false; // todo view暂还不支持 const entityType = view ? 'view' : 'table'; let sql = `create ${entityType} `; if (ifExists === 'omit' || (_static && ifExists === 'dropIfNotStatic')) { sql += ' if not exists'; } if (storageName) { sql += `\`${storageName}\` `; } else { sql += `\`${entity}\` `; } if (view) { throw new Error(' view unsupported yet'); } else { sql += '('; // 翻译所有的属性 Object.keys(attributes).forEach((attr, idx) => { const attrSql = this.translateAttributeDef(attr, attributes[attr]); if (idx !== 0) { sql += ', '; } sql += attrSql; if (typeof attributes[attr].sequenceStart === 'number') { (0, assert_1.default)(hasSequence === false, 'Entity can only have one auto increment attribute.'); hasSequence = attributes[attr].sequenceStart; } }); // 翻译索引信息 if (indexes) { sql += ',\n'; indexes.forEach(({ name, attributes, config }, idx) => { const { unique, type, parser } = config || {}; // 因为有deleteAt的存在,这里的unique没意义,只能框架自己去建立checker来处理 /* if (unique) { sql += ' unique '; } else */ if (type === 'fulltext') { sql += ' fulltext '; } else if (type === 'spatial') { sql += ' spatial '; } sql += `index \`${name}\` `; if (type === 'hash') { sql += ` using hash `; } sql += '('; attributes.forEach(({ name, size, direction }, idx2) => { sql += `\`${name}\``; if (size) { sql += ` (${size})`; } if (direction) { sql += ` ${direction}`; } if (idx2 < attributes.length - 1) { sql += ', '; } }); sql += ')'; if (parser) { sql += ` with parser ${parser}`; } if (idx < indexes.length - 1) { sql += ',\n'; } }); } } sql += ')'; if (typeof hasSequence === 'number') { sql += `auto_increment = ${hasSequence}`; } if (ifExists === 'drop' || (!_static && ifExists === 'dropIfNotStatic')) { return [`drop ${entityType} if exists \`${storageName || entity}\`;`, sql]; } return [sql]; } translateFnName(fnName, argumentNumber) { switch (fnName) { case '$add': { let result = '%s'; while (--argumentNumber > 0) { result += ' + %s'; } return result; } case '$subtract': { (0, assert_1.default)(argumentNumber === 2); return '%s - %s'; } case '$multiply': { let result = '%s'; while (--argumentNumber > 0) { result += ' * %s'; } return result; } case '$divide': { (0, assert_1.default)(argumentNumber === 2); return '%s / %s'; } case '$abs': { return 'ABS(%s)'; } case '$round': { (0, assert_1.default)(argumentNumber === 2); return 'ROUND(%s, %s)'; } case '$ceil': { return 'CEIL(%s)'; } case '$floor': { return 'FLOOR(%s)'; } case '$mod': { return 'MOD(%s, %s)'; } case '$pow': { (0, assert_1.default)(argumentNumber === 2); return 'POW(%s, %s)'; } case '$gt': { (0, assert_1.default)(argumentNumber === 2); return '%s > %s'; } case '$gte': { (0, assert_1.default)(argumentNumber === 2); return '%s >= %s'; } case '$lt': { (0, assert_1.default)(argumentNumber === 2); return '%s < %s'; } case '$lte': { return '%s <= %s'; } case '$eq': { (0, assert_1.default)(argumentNumber === 2); return '%s = %s'; } case '$ne': { (0, assert_1.default)(argumentNumber === 2); return '%s <> %s'; } case '$startsWith': { (0, assert_1.default)(argumentNumber === 2); return '%s like CONCAT(%s, \'%\')'; } case '$endsWith': { (0, assert_1.default)(argumentNumber === 2); return '%s like CONCAT(\'%\', %s)'; } case '$includes': { (0, assert_1.default)(argumentNumber === 2); return '%s like CONCAT(\'%\', %s, \'%\')'; } case '$true': { return '%s = true'; } case '$false': { return '%s = false'; } case '$and': { let result = ''; for (let iter = 0; iter < argumentNumber; iter++) { result += '%s'; if (iter < argumentNumber - 1) { result += ' and '; } } return result; } case '$or': { let result = ''; for (let iter = 0; iter < argumentNumber; iter++) { result += '%s'; if (iter < argumentNumber - 1) { result += ' or '; } } return result; } case '$not': { return 'not %s'; } case '$year': { return 'YEAR(%s)'; } case '$month': { return 'MONTH(%s)'; } case '$weekday': { return 'WEEKDAY(%s)'; } case '$weekOfYear': { return 'WEEKOFYEAR(%s)'; } case '$day': { return 'DAY(%s)'; } case '$dayOfMonth': { return 'DAYOFMONTH(%s)'; } case '$dayOfWeek': { return 'DAYOFWEEK(%s)'; } case '$dayOfYear': { return 'DAYOFYEAR(%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)'; } case '$distance': { (0, assert_1.default)(argumentNumber === 2); return 'ST_DISTANCE(%s, %s)'; } case '$concat': { let result = ' concat(%s'; while (--argumentNumber > 0) { result += ', %s'; } 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}`); } } } translateAttrInExpression(entity, attr, exprText) { const { attributes } = this.schema[entity]; const { type } = attributes[attr]; if (['date', 'time', 'datetime'].includes(type)) { // 从unix时间戵转成date类型参加expr的运算 return `from_unixtime(${exprText} / 1000)`; } return exprText; } translateExpression(entity, alias, expression, refDict) { const translateConstant = (constant) => { if (constant instanceof Date) { return ` from_unixtime(${constant.valueOf()}/1000)`; } else if (typeof constant === 'string') { return ` '${constant}'`; } else { (0, assert_1.default)(typeof constant === 'number'); return ` ${constant}`; } }; const translateInner = (expr) => { const k = Object.keys(expr); let result; if (k.includes('#attr')) { const attrText = `\`${alias}\`.\`${(expr)['#attr']}\``; result = this.translateAttrInExpression(entity, (expr)['#attr'], attrText); } else if (k.includes('#refId')) { const refId = (expr)['#refId']; const refAttr = (expr)['#refAttr']; (0, assert_1.default)(refDict[refId]); const [refAlias, refEntity] = refDict[refId]; const attrText = `\`${refAlias}\`.\`${refAttr}\``; // 这里必须使用refEntity,否则在filter深层嵌套节点表达式时会出现entity不对应 result = this.translateAttrInExpression(refEntity, (expr)['#refAttr'], attrText); } else { (0, assert_1.default)(k.length === 1); 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(...fnArgs.map((ele) => { if (['string', 'number'].includes(typeof ele) || ele instanceof Date) { return translateConstant(ele); } else { return translateInner(ele); } })); result = util_1.format.apply(null, args); } else { // 原有的单参数处理逻辑 const fnName = this.translateFnName(fnKey, 1); const args = [fnName]; const arg = fnArgs; if (['string', 'number'].includes(typeof arg) || arg instanceof Date) { args.push(translateConstant(arg)); } else { args.push(translateInner(arg)); } result = util_1.format.apply(null, args); } } return result; }; return translateInner(expression); } populateSelectStmt(projectionText, fromText, aliasDict, filterText, sorterText, groupByText, indexFrom, count, option) { // todo hint of use index let sql = `select ${projectionText} from ${fromText}`; if (filterText) { sql += ` where ${filterText}`; } if (sorterText) { sql += ` order by ${sorterText}`; } if (groupByText) { sql += ` group by ${groupByText}`; } if (typeof indexFrom === 'number') { (0, assert_1.default)(typeof count === 'number'); sql += ` limit ${indexFrom}, ${count}`; } if (option?.forUpdate) { sql += ' for update'; if (typeof option?.forUpdate === 'string') { sql += ` ${option.forUpdate}`; } } return sql; } populateUpdateStmt(updateText, fromText, aliasDict, filterText, sorterText, indexFrom, count, option) { // todo using index (0, assert_1.default)(updateText); let sql = `update ${fromText} set ${updateText}`; if (filterText) { sql += ` where ${filterText}`; } if (sorterText) { sql += ` order by ${sorterText}`; } if (typeof indexFrom === 'number') { (0, assert_1.default)(typeof count === 'number'); sql += ` limit ${indexFrom}, ${count}`; } return sql; } populateRemoveStmt(updateText, fromText, aliasDict, filterText, sorterText, indexFrom, count, option) { // todo using index const alias = aliasDict['./']; if (option?.deletePhysically) { // assert(!updateText, 'physically delete does not support setting trigger data'); let sql = `delete ${alias} from ${fromText} `; if (filterText) { sql += ` where ${filterText}`; } if (sorterText) { sql += ` order by ${sorterText}`; } if (typeof indexFrom === 'number') { (0, assert_1.default)(typeof count === 'number'); sql += ` limit ${indexFrom}, ${count}`; } return sql; } // 新的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}`; if (filterText) { sql += ` where ${filterText}`; } if (sorterText) { sql += ` order by ${sorterText}`; } if (typeof indexFrom === 'number') { (0, assert_1.default)(typeof count === 'number'); sql += ` limit ${indexFrom}, ${count}`; } return sql; } /** * 将MySQL返回的Type回译成oak的类型,是 populateDataTypeDef 的反函数 * @param type */ reTranslateToAttribute(type) { const withLengthDataTypes = MySqlTranslator.withLengthDataTypes.join('|'); let result = (new RegExp(`^(${withLengthDataTypes})\\((\\d+)\\)$`)).exec(type); if (result) { return { type: result[1], params: { length: parseInt(result[2]), } }; } const withPrecisionDataTypes = MySqlTranslator.withPrecisionDataTypes.join('|'); result = (new RegExp(`^(${withPrecisionDataTypes})\\((\\d+),(d+)\\)$`)).exec(type); if (result) { return { type: result[1], params: { precision: parseInt(result[2]), scale: parseInt(result[3]), }, }; } result = (/^enum\((\S+)\)$/).exec(type); if (result) { const enumeration = result[1].split(',').map(ele => ele.slice(1, -1)); return { type: 'enum', enumeration }; } return { type: type, }; } // 分析当前数据库结构图 async readSchema(execFn) { const result = {}; const sql = 'show tables;'; const [tables] = await execFn(sql); for (const tableItem of tables) { const table = Object.values(tableItem)[0]; const [tableResult] = await execFn(`desc \`${table}\``); const attributes = {}; for (const attrItem of tableResult) { const { Field: attrName, Null: isNull, Type: type, Key: key } = attrItem; attributes[attrName] = { ...this.reTranslateToAttribute(type), notNull: isNull.toUpperCase() === 'NO', unique: key.toUpperCase() === 'UNI', }; // 自增列只可能是seq if (attrName === '$$seq$$') { attributes[attrName].sequenceStart = 10000; } } Object.assign(result, { [table]: { attributes, } }); const [indexedColumns] = (await execFn(`show index from \`${table}\``)); if (indexedColumns.length) { const groupedColumns = (0, lodash_1.groupBy)(indexedColumns.sort((ele1, ele2) => ele1.Key_name.localeCompare(ele2.Key_name) || ele1.Seq_in_index - ele2.Seq_in_index), 'Key_name'); const indexes = Object.values(groupedColumns).map((ele) => { const index = { name: ele[0].Key_name, attributes: ele.map(ele2 => ({ name: ele2.Column_name, direction: ele2.Collation === 'D' ? 'DESC' : (ele2.Collation === 'A' ? 'ASC' : undefined), size: ele2.Sub_part || undefined, })), }; if (ele[0].Non_unique === 0 || ele[0].Index_type.toUpperCase() !== 'BTREE') { index.config = {}; if (ele[0].Non_unique === 0) { index.config.unique = true; } if (ele[0].Index_type.toUpperCase() !== 'BTREE') { index.config.type = ele[0].Index_type.toLowerCase(); } } return index; }); Object.assign(result[table], { indexes, }); } } return result; } } exports.MySqlTranslator = MySqlTranslator;