oak-db 3.3.11 → 3.3.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -119,21 +119,21 @@ class MySqlTranslator extends sqlTranslator_1.SqlTranslator {
119
119
  // numeric types
120
120
  "bit",
121
121
  "int",
122
- "integer",
122
+ "integer", // synonym for int
123
123
  "tinyint",
124
124
  "smallint",
125
125
  "mediumint",
126
126
  "bigint",
127
127
  "float",
128
128
  "double",
129
- "double precision",
130
- "real",
129
+ "double precision", // synonym for double
130
+ "real", // synonym for double
131
131
  "decimal",
132
- "dec",
133
- "numeric",
134
- "fixed",
135
- "bool",
136
- "boolean",
132
+ "dec", // synonym for decimal
133
+ "numeric", // synonym for decimal
134
+ "fixed", // synonym for decimal
135
+ "bool", // synonym for tinyint
136
+ "boolean", // synonym for tinyint
137
137
  // date and time types
138
138
  "date",
139
139
  "datetime",
@@ -142,10 +142,10 @@ class MySqlTranslator extends sqlTranslator_1.SqlTranslator {
142
142
  "year",
143
143
  // string types
144
144
  "char",
145
- "nchar",
145
+ "nchar", // synonym for national char
146
146
  "national char",
147
147
  "varchar",
148
- "nvarchar",
148
+ "nvarchar", // synonym for national varchar
149
149
  "national varchar",
150
150
  "blob",
151
151
  "text",
@@ -266,14 +266,18 @@ class MySqlTranslator extends sqlTranslator_1.SqlTranslator {
266
266
  return 'text ';
267
267
  }
268
268
  if (type === 'ref') {
269
- return 'char(36)';
269
+ return 'char(36) ';
270
+ }
271
+ if (['bool', 'boolean'].includes(type)) {
272
+ // MySQL读出来就是tinyint(1)
273
+ return 'tinyint(1) ';
270
274
  }
271
275
  if (type === 'money') {
272
- return 'bigint';
276
+ return 'bigint ';
273
277
  }
274
278
  if (type === 'enum') {
275
279
  (0, assert_1.default)(enumeration);
276
- return `enum(${enumeration.map(ele => `'${ele}'`).join(',')})`;
280
+ return `enum(${enumeration.map(ele => `'${ele}'`).join(',')}) `;
277
281
  }
278
282
  if (MySqlTranslator.withLengthDataTypes.includes(type)) {
279
283
  if (params) {
@@ -291,34 +295,34 @@ class MySqlTranslator extends sqlTranslator_1.SqlTranslator {
291
295
  if (typeof scale === 'number') {
292
296
  return `${type}(${precision}, ${scale}) `;
293
297
  }
294
- return `${type}(${precision})`;
298
+ return `${type}(${precision}) `;
295
299
  }
296
300
  else {
297
301
  const { precision, scale } = MySqlTranslator.dataTypeDefaults[type];
298
302
  if (typeof scale === 'number') {
299
303
  return `${type}(${precision}, ${scale}) `;
300
304
  }
301
- return `${type}(${precision})`;
305
+ return `${type}(${precision}) `;
302
306
  }
303
307
  }
304
308
  if (MySqlTranslator.withWidthDataTypes.includes(type)) {
305
309
  (0, assert_1.default)(type === 'int');
306
- const { width } = params;
310
+ const { width } = params || { width: 4 };
307
311
  switch (width) {
308
312
  case 1: {
309
- return 'tinyint';
313
+ return 'tinyint ';
310
314
  }
311
315
  case 2: {
312
- return 'smallint';
316
+ return 'smallint ';
313
317
  }
314
318
  case 3: {
315
- return 'mediumint';
319
+ return 'mediumint ';
316
320
  }
317
321
  case 4: {
318
- return 'int';
322
+ return 'int ';
319
323
  }
320
324
  default: {
321
- return 'bigint';
325
+ return 'bigint ';
322
326
  }
323
327
  }
324
328
  }
@@ -553,6 +557,28 @@ class MySqlTranslator extends sqlTranslator_1.SqlTranslator {
553
557
  const columns2 = attributes.map(({ name }) => `${alias}.${name}`);
554
558
  return ` match(${columns2.join(',')}) against ('${$search}' in natural language mode)`;
555
559
  }
560
+ translateAttributeDef(attr, attrDef) {
561
+ let sql = `\`${attr}\` `;
562
+ const { type, params, default: defaultValue, unique, notNull, sequenceStart, enumeration, } = attrDef;
563
+ sql += this.populateDataTypeDef(type, params, enumeration);
564
+ if (notNull || type === 'geometry') {
565
+ sql += ' not null ';
566
+ }
567
+ if (unique) {
568
+ sql += ' unique ';
569
+ }
570
+ if (typeof sequenceStart === 'number') {
571
+ sql += ' auto_increment unique ';
572
+ }
573
+ if (defaultValue !== undefined) {
574
+ (0, assert_1.default)(type !== 'ref');
575
+ sql += ` default ${this.translateAttrValue(type, defaultValue)}`;
576
+ }
577
+ if (attr === types_1.PrimaryKeyAttribute) {
578
+ sql += ' primary key';
579
+ }
580
+ return sql;
581
+ }
556
582
  translateCreateEntity(entity, options) {
557
583
  const ifExists = options?.ifExists || 'drop';
558
584
  const { schema } = this;
@@ -578,32 +604,14 @@ class MySqlTranslator extends sqlTranslator_1.SqlTranslator {
578
604
  sql += '(';
579
605
  // 翻译所有的属性
580
606
  Object.keys(attributes).forEach((attr, idx) => {
581
- const attrDef = attributes[attr];
582
- const { type, params, default: defaultValue, unique, notNull, sequenceStart, enumeration, } = attrDef;
583
- sql += `\`${attr}\` `;
584
- sql += this.populateDataTypeDef(type, params, enumeration);
585
- if (notNull || type === 'geometry') {
586
- sql += ' not null ';
587
- }
588
- if (unique) {
589
- sql += ' unique ';
590
- }
591
- if (sequenceStart) {
592
- if (hasSequence) {
593
- throw new Error(`「${entity}」只能有一个sequence列`);
594
- }
595
- hasSequence = sequenceStart;
596
- sql += ' auto_increment unique ';
597
- }
598
- if (defaultValue !== undefined) {
599
- (0, assert_1.default)(type !== 'ref');
600
- sql += ` default ${this.translateAttrValue(type, defaultValue)}`;
607
+ const attrSql = this.translateAttributeDef(attr, attributes[attr]);
608
+ if (idx !== 0) {
609
+ sql += ', ';
601
610
  }
602
- if (attr === 'id') {
603
- sql += ' primary key';
604
- }
605
- if (idx < Object.keys(attributes).length - 1) {
606
- sql += ',\n';
611
+ sql += attrSql;
612
+ if (typeof attributes[attr].sequenceStart === 'number') {
613
+ (0, assert_1.default)(hasSequence === false, 'Entity can only have one auto increment attribute.');
614
+ hasSequence = attributes[attr].sequenceStart;
607
615
  }
608
616
  });
609
617
  // 翻译索引信息
@@ -621,12 +629,11 @@ class MySqlTranslator extends sqlTranslator_1.SqlTranslator {
621
629
  else if (type === 'spatial') {
622
630
  sql += ' spatial ';
623
631
  }
624
- sql += `index ${name} `;
632
+ sql += `index \`${name}\` `;
625
633
  if (type === 'hash') {
626
634
  sql += ` using hash `;
627
635
  }
628
636
  sql += '(';
629
- let includeDeleteAt = false;
630
637
  attributes.forEach(({ name, size, direction }, idx2) => {
631
638
  sql += `\`${name}\``;
632
639
  if (size) {
@@ -636,15 +643,9 @@ class MySqlTranslator extends sqlTranslator_1.SqlTranslator {
636
643
  sql += ` ${direction}`;
637
644
  }
638
645
  if (idx2 < attributes.length - 1) {
639
- sql += ',';
640
- }
641
- if (name === '$$deleteAt$$') {
642
- includeDeleteAt = true;
646
+ sql += ', ';
643
647
  }
644
648
  });
645
- if (!includeDeleteAt && !type) {
646
- sql += ', `$$deleteAt$$`'; // 在mysql80+之后,需要给属性加上``包裹,否则会报错
647
- }
648
649
  sql += ')';
649
650
  if (parser) {
650
651
  sql += ` with parser ${parser}`;
@@ -796,10 +797,11 @@ class MySqlTranslator extends sqlTranslator_1.SqlTranslator {
796
797
  case '$dayOfYear': {
797
798
  return 'DAYOFYEAR(%s)';
798
799
  }
799
- case '$dateDiff': {
800
- (0, assert_1.default)(argumentNumber === 3);
801
- return 'DATEDIFF(%s, %s, %s)';
802
- }
800
+ // 这个实现有问题,DATEDIFF只是计算两个日期之间的天数差,只接受两个参数,放在translateExperession里实现
801
+ // case '$dateDiff': {
802
+ // assert(argumentNumber === 3);
803
+ // return 'DATEDIFF(%s, %s, %s)';
804
+ // }
803
805
  case '$contains': {
804
806
  (0, assert_1.default)(argumentNumber === 2);
805
807
  return 'ST_CONTAINS(%s, %s)';
@@ -816,6 +818,22 @@ class MySqlTranslator extends sqlTranslator_1.SqlTranslator {
816
818
  result += ')';
817
819
  return result;
818
820
  }
821
+ // ========== 聚合函数 ==========
822
+ case '$$count': {
823
+ return 'COUNT(%s)';
824
+ }
825
+ case '$$sum': {
826
+ return 'SUM(%s)';
827
+ }
828
+ case '$$max': {
829
+ return 'MAX(%s)';
830
+ }
831
+ case '$$min': {
832
+ return 'MIN(%s)';
833
+ }
834
+ case '$$avg': {
835
+ return 'AVG(%s)';
836
+ }
819
837
  default: {
820
838
  throw new Error(`unrecoganized function ${fnName}`);
821
839
  }
@@ -854,15 +872,142 @@ class MySqlTranslator extends sqlTranslator_1.SqlTranslator {
854
872
  const refId = (expr)['#refId'];
855
873
  const refAttr = (expr)['#refAttr'];
856
874
  (0, assert_1.default)(refDict[refId]);
857
- const attrText = `\`${refDict[refId][0]}\`.\`${refAttr}\``;
858
- result = this.translateAttrInExpression(entity, (expr)['#refAttr'], attrText);
875
+ const [refAlias, refEntity] = refDict[refId];
876
+ const attrText = `\`${refAlias}\`.\`${refAttr}\``;
877
+ // 这里必须使用refEntity,否则在filter深层嵌套节点表达式时会出现entity不对应
878
+ result = this.translateAttrInExpression(refEntity, (expr)['#refAttr'], attrText);
859
879
  }
860
880
  else {
861
881
  (0, assert_1.default)(k.length === 1);
862
- if ((expr)[k[0]] instanceof Array) {
863
- const fnName = this.translateFnName(k[0], (expr)[k[0]].length);
882
+ const fnKey = k[0];
883
+ const fnArgs = (expr)[fnKey];
884
+ // 特殊处理日期相关函数
885
+ if (fnKey === '$dateDiff') {
886
+ // $dateDiff: [date1, date2, unit]
887
+ (0, assert_1.default)(fnArgs instanceof Array && fnArgs.length === 3);
888
+ const [date1Expr, date2Expr, unit] = fnArgs;
889
+ // 转换日期表达式
890
+ const translateDateArg = (arg) => {
891
+ if (arg instanceof Date) {
892
+ return `FROM_UNIXTIME(${arg.valueOf()} / 1000)`;
893
+ }
894
+ else if (typeof arg === 'number') {
895
+ return `FROM_UNIXTIME(${arg} / 1000)`;
896
+ }
897
+ else {
898
+ return translateInner(arg);
899
+ }
900
+ };
901
+ const date1Str = translateDateArg(date1Expr);
902
+ const date2Str = translateDateArg(date2Expr);
903
+ // MySQL TIMESTAMPDIFF 单位映射
904
+ const unitMap = {
905
+ 's': 'SECOND',
906
+ 'm': 'MINUTE',
907
+ 'h': 'HOUR',
908
+ 'd': 'DAY',
909
+ 'M': 'MONTH',
910
+ 'y': 'YEAR'
911
+ };
912
+ const mysqlUnit = unitMap[unit];
913
+ if (!mysqlUnit) {
914
+ throw new Error(`Unsupported date diff unit: ${unit}`);
915
+ }
916
+ // TIMESTAMPDIFF(unit, date1, date2) 返回 date2 - date1
917
+ // 但类型定义是 date1 - date2,所以参数顺序要反过来
918
+ result = `TIMESTAMPDIFF(${mysqlUnit}, ${date2Str}, ${date1Str})`;
919
+ }
920
+ else if (fnKey === '$dateCeil') {
921
+ // $dateCeil: [date, unit]
922
+ (0, assert_1.default)(fnArgs instanceof Array && fnArgs.length === 2);
923
+ const [dateExpr, unit] = fnArgs;
924
+ const getTimestampExpr = (arg) => {
925
+ if (arg instanceof Date) {
926
+ return `${arg.valueOf()}`;
927
+ }
928
+ else if (typeof arg === 'number') {
929
+ return `${arg}`;
930
+ }
931
+ else {
932
+ const k = Object.keys(arg);
933
+ if (k.includes('#attr')) {
934
+ return `\`${alias}\`.\`${arg['#attr']}\``;
935
+ }
936
+ else if (k.includes('#refId')) {
937
+ const refId = arg['#refId'];
938
+ const refAttr = arg['#refAttr'];
939
+ return `\`${refDict[refId][0]}\`.\`${refAttr}\``;
940
+ }
941
+ return translateInner(arg);
942
+ }
943
+ };
944
+ const tsExpr = getTimestampExpr(dateExpr);
945
+ const msPerUnit = {
946
+ 's': 1000,
947
+ 'm': 60000,
948
+ 'h': 3600000,
949
+ 'd': 86400000,
950
+ };
951
+ if (msPerUnit[unit]) {
952
+ // CEIL 向上取整
953
+ result = `CEIL(${tsExpr} / ${msPerUnit[unit]}) * ${msPerUnit[unit]}`;
954
+ }
955
+ else {
956
+ (0, assert_1.default)(typeof unit === 'string', 'unit should be string');
957
+ console.warn('暂不支持 $dateCeil 对月年单位的处理');
958
+ throw new Error(`Unsupported date ceil unit: ${unit}`);
959
+ }
960
+ }
961
+ else if (fnKey === '$dateFloor') {
962
+ // $dateFloor: [date, unit]
963
+ (0, assert_1.default)(fnArgs instanceof Array && fnArgs.length === 2);
964
+ const [dateExpr, unit] = fnArgs;
965
+ // 获取毫秒时间戳表达式
966
+ const getTimestampExpr = (arg) => {
967
+ if (arg instanceof Date) {
968
+ return `${arg.valueOf()}`;
969
+ }
970
+ else if (typeof arg === 'number') {
971
+ return `${arg}`;
972
+ }
973
+ else {
974
+ // 属性引用,直接返回属性(存储本身就是毫秒时间戳)
975
+ const k = Object.keys(arg);
976
+ if (k.includes('#attr')) {
977
+ return `\`${alias}\`.\`${arg['#attr']}\``;
978
+ }
979
+ else if (k.includes('#refId')) {
980
+ const refId = arg['#refId'];
981
+ const refAttr = arg['#refAttr'];
982
+ return `\`${refDict[refId][0]}\`.\`${refAttr}\``;
983
+ }
984
+ // 其他表达式递归处理
985
+ return translateInner(arg);
986
+ }
987
+ };
988
+ const tsExpr = getTimestampExpr(dateExpr);
989
+ // 固定间隔单位:直接用时间戳数学运算
990
+ const msPerUnit = {
991
+ 's': 1000,
992
+ 'm': 60000,
993
+ 'h': 3600000,
994
+ 'd': 86400000,
995
+ };
996
+ if (msPerUnit[unit]) {
997
+ // FLOOR(timestamp / interval) * interval
998
+ result = `FLOOR(${tsExpr} / ${msPerUnit[unit]}) * ${msPerUnit[unit]}`;
999
+ }
1000
+ else {
1001
+ (0, assert_1.default)(typeof unit === 'string', 'unit should be string');
1002
+ console.warn('暂不支持 $dateFloor 对月年单位的处理');
1003
+ throw new Error(`Unsupported date floor unit: ${unit}`);
1004
+ }
1005
+ }
1006
+ else if (fnArgs instanceof Array) {
1007
+ // 原有的数组参数处理逻辑
1008
+ const fnName = this.translateFnName(fnKey, fnArgs.length);
864
1009
  const args = [fnName];
865
- args.push(...(expr)[k[0]].map((ele) => {
1010
+ args.push(...fnArgs.map((ele) => {
866
1011
  if (['string', 'number'].includes(typeof ele) || ele instanceof Date) {
867
1012
  return translateConstant(ele);
868
1013
  }
@@ -873,9 +1018,10 @@ class MySqlTranslator extends sqlTranslator_1.SqlTranslator {
873
1018
  result = util_1.format.apply(null, args);
874
1019
  }
875
1020
  else {
876
- const fnName = this.translateFnName(k[0], 1);
1021
+ // 原有的单参数处理逻辑
1022
+ const fnName = this.translateFnName(fnKey, 1);
877
1023
  const args = [fnName];
878
- const arg = (expr)[k[0]];
1024
+ const arg = fnArgs;
879
1025
  if (['string', 'number'].includes(typeof arg) || arg instanceof Date) {
880
1026
  args.push(translateConstant(arg));
881
1027
  }
@@ -949,7 +1095,7 @@ class MySqlTranslator extends sqlTranslator_1.SqlTranslator {
949
1095
  }
950
1096
  // 新的remove应该包含$$deleteAt$$的值了
951
1097
  /* const now = Date.now();
952
-
1098
+
953
1099
  const updateText2 = updateText ? `${updateText}, \`${alias}\`.\`$$deleteAt$$\` = '${now}'` : `\`${alias}\`.\`$$deleteAt$$\` = '${now}'`; */
954
1100
  (0, assert_1.default)(updateText.includes(types_1.DeleteAtAttribute));
955
1101
  let sql = `update ${fromText} set ${updateText}`;
@@ -965,5 +1111,99 @@ class MySqlTranslator extends sqlTranslator_1.SqlTranslator {
965
1111
  }
966
1112
  return sql;
967
1113
  }
1114
+ /**
1115
+ * 将MySQL返回的Type回译成oak的类型,是 populateDataTypeDef 的反函数
1116
+ * @param type
1117
+ */
1118
+ reTranslateToAttribute(type) {
1119
+ const withLengthDataTypes = MySqlTranslator.withLengthDataTypes.join('|');
1120
+ let result = (new RegExp(`^(${withLengthDataTypes})\\((\\d+)\\)$`)).exec(type);
1121
+ if (result) {
1122
+ return {
1123
+ type: result[1],
1124
+ params: {
1125
+ length: parseInt(result[2]),
1126
+ }
1127
+ };
1128
+ }
1129
+ const withPrecisionDataTypes = MySqlTranslator.withPrecisionDataTypes.join('|');
1130
+ result = (new RegExp(`^(${withPrecisionDataTypes})\\((\\d+),(d+)\\)$`)).exec(type);
1131
+ if (result) {
1132
+ return {
1133
+ type: result[1],
1134
+ params: {
1135
+ precision: parseInt(result[2]),
1136
+ scale: parseInt(result[3]),
1137
+ },
1138
+ };
1139
+ }
1140
+ result = (/^enum\((\S+)\)$/).exec(type);
1141
+ if (result) {
1142
+ const enumeration = result[1].split(',').map(ele => ele.slice(1, -1));
1143
+ return {
1144
+ type: 'enum',
1145
+ enumeration
1146
+ };
1147
+ }
1148
+ return {
1149
+ type: type,
1150
+ };
1151
+ }
1152
+ // 分析当前数据库结构图
1153
+ async readSchema(execFn) {
1154
+ const result = {};
1155
+ const sql = 'show tables;';
1156
+ const [tables] = await execFn(sql);
1157
+ for (const tableItem of tables) {
1158
+ const table = Object.values(tableItem)[0];
1159
+ const [tableResult] = await execFn(`desc \`${table}\``);
1160
+ const attributes = {};
1161
+ for (const attrItem of tableResult) {
1162
+ const { Field: attrName, Null: isNull, Type: type, Key: key } = attrItem;
1163
+ attributes[attrName] = {
1164
+ ...this.reTranslateToAttribute(type),
1165
+ notNull: isNull.toUpperCase() === 'NO',
1166
+ unique: key.toUpperCase() === 'UNI',
1167
+ };
1168
+ // 自增列只可能是seq
1169
+ if (attrName === '$$seq$$') {
1170
+ attributes[attrName].sequenceStart = 10000;
1171
+ }
1172
+ }
1173
+ Object.assign(result, {
1174
+ [table]: {
1175
+ attributes,
1176
+ }
1177
+ });
1178
+ const [indexedColumns] = (await execFn(`show index from \`${table}\``));
1179
+ if (indexedColumns.length) {
1180
+ 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');
1181
+ const indexes = Object.values(groupedColumns).map((ele) => {
1182
+ const index = {
1183
+ name: ele[0].Key_name,
1184
+ attributes: ele.map(ele2 => ({
1185
+ name: ele2.Column_name,
1186
+ direction: ele2.Collation === 'D' ? 'DESC' : (ele2.Collation === 'A' ? 'ASC' : undefined),
1187
+ size: ele2.Sub_part || undefined,
1188
+ })),
1189
+ };
1190
+ if (ele[0].Non_unique === 0 || ele[0].Index_type.toUpperCase() !== 'BTREE') {
1191
+ index.config = {};
1192
+ if (ele[0].Non_unique === 0) {
1193
+ index.config.unique = true;
1194
+ }
1195
+ if (ele[0].Index_type.toUpperCase() !== 'BTREE') {
1196
+ index.config.type = ele[0].Index_type.toLowerCase();
1197
+ }
1198
+ }
1199
+ return index;
1200
+ });
1201
+ Object.assign(result[table], {
1202
+ indexes,
1203
+ });
1204
+ }
1205
+ }
1206
+ return result;
1207
+ }
968
1208
  }
969
1209
  exports.MySqlTranslator = MySqlTranslator;
@@ -1,12 +1,12 @@
1
- export type MySQLConfiguration = {
2
- host: string;
3
- user: string;
4
- password: string;
5
- database: string;
6
- charset: 'utf8mb4_general_ci';
7
- connectionLimit: number;
8
- port?: number;
9
- };
10
- export type Configuration = {
11
- mysql: MySQLConfiguration;
12
- };
1
+ export type MySQLConfiguration = {
2
+ host: string;
3
+ user: string;
4
+ password: string;
5
+ database: string;
6
+ charset: 'utf8mb4_general_ci';
7
+ connectionLimit: number;
8
+ port?: number;
9
+ };
10
+ export type Configuration = {
11
+ mysql: MySQLConfiguration;
12
+ };
@@ -1,2 +1,2 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,27 @@
1
+ import { Pool, PoolClient, QueryResult, QueryResultRow } from 'pg';
2
+ import { TxnOption } from 'oak-domain/lib/types';
3
+ import { PostgreSQLConfiguration } from './types/Configuration';
4
+ export declare class PostgreSQLConnector {
5
+ pool: Pool;
6
+ configuration: PostgreSQLConfiguration;
7
+ txnDict: Record<string, PoolClient>;
8
+ constructor(configuration: PostgreSQLConfiguration);
9
+ connect(): void;
10
+ disconnect(): Promise<void>;
11
+ startTransaction(option?: TxnOption): Promise<string>;
12
+ /**
13
+ * 映射隔离级别到 PostgreSQL 语法
14
+ */
15
+ private mapIsolationLevel;
16
+ exec(sql: string, txn?: string): Promise<[QueryResultRow[], QueryResult]>;
17
+ commitTransaction(txn: string): Promise<void>;
18
+ rollbackTransaction(txn: string): Promise<void>;
19
+ /**
20
+ * 获取连接池状态
21
+ */
22
+ getPoolStatus(): {
23
+ total: number;
24
+ idle: number;
25
+ waiting: number;
26
+ };
27
+ }