midway-fatcms 0.0.4 → 0.0.6

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.
Files changed (126) hide show
  1. package/README.md +635 -352
  2. package/dist/controller/manage/CrudStandardDesignApi.d.ts +0 -2
  3. package/dist/controller/manage/CrudStandardDesignApi.js +11 -85
  4. package/dist/index.d.ts +2 -0
  5. package/dist/index.js +2 -0
  6. package/dist/libs/crud-pro/CrudPro.d.ts +9 -1
  7. package/dist/libs/crud-pro/CrudPro.js +15 -0
  8. package/dist/libs/crud-pro/README.md +809 -0
  9. package/dist/libs/crud-pro/README_FUNC.md +193 -0
  10. package/dist/libs/crud-pro/exceptions.d.ts +2 -0
  11. package/dist/libs/crud-pro/exceptions.js +2 -0
  12. package/dist/libs/crud-pro/interfaces.d.ts +34 -1
  13. package/dist/libs/crud-pro/models/ExecuteContext.d.ts +3 -3
  14. package/dist/libs/crud-pro/models/ExecuteContext.js +2 -0
  15. package/dist/libs/crud-pro/models/RequestModel.d.ts +41 -1
  16. package/dist/libs/crud-pro/models/RequestModel.js +103 -39
  17. package/dist/libs/crud-pro/models/ResModel.d.ts +6 -4
  18. package/dist/libs/crud-pro/models/ServiceHub.d.ts +1 -0
  19. package/dist/libs/crud-pro/models/keys.d.ts +6 -1
  20. package/dist/libs/crud-pro/models/keys.js +5 -0
  21. package/dist/libs/crud-pro/services/CrudProDataTypeConvertService.d.ts +52 -0
  22. package/dist/libs/crud-pro/services/CrudProDataTypeConvertService.js +158 -0
  23. package/dist/libs/crud-pro/services/CrudProExecuteSqlService.js +20 -1
  24. package/dist/libs/crud-pro/services/CrudProFieldValidateService.d.ts +7 -0
  25. package/dist/libs/crud-pro/services/CrudProFieldValidateService.js +32 -0
  26. package/dist/libs/crud-pro/services/CrudProGenSqlService.d.ts +13 -0
  27. package/dist/libs/crud-pro/services/CrudProGenSqlService.js +44 -7
  28. package/dist/libs/crud-pro/services/CrudProOriginToExecuteSql.d.ts +43 -0
  29. package/dist/libs/crud-pro/services/CrudProOriginToExecuteSql.js +132 -1
  30. package/dist/libs/crud-pro/services/CrudProTableMetaService.d.ts +15 -1
  31. package/dist/libs/crud-pro/services/CrudProTableMetaService.js +107 -0
  32. package/dist/libs/crud-pro/services/CurdProServiceHub.d.ts +5 -1
  33. package/dist/libs/crud-pro/services/CurdProServiceHub.js +11 -0
  34. package/dist/libs/crud-pro/utils/DateTimeUtils.d.ts +1 -0
  35. package/dist/libs/crud-pro/utils/DateTimeUtils.js +3 -0
  36. package/dist/libs/crud-pro/utils/MixinUtils.d.ts +32 -0
  37. package/dist/libs/crud-pro/utils/MixinUtils.js +85 -1
  38. package/dist/libs/crud-pro/utils/ValidateUtils.js +1 -1
  39. package/dist/libs/crud-sharding/ROUTING_LOGIC.md +944 -0
  40. package/dist/libs/crud-sharding/ShardingConfig.d.ts +218 -0
  41. package/dist/libs/crud-sharding/ShardingConfig.js +32 -0
  42. package/dist/libs/crud-sharding/ShardingCountCache.d.ts +69 -0
  43. package/dist/libs/crud-sharding/ShardingCountCache.js +160 -0
  44. package/dist/libs/crud-sharding/ShardingCrudPro.d.ts +363 -0
  45. package/dist/libs/crud-sharding/ShardingCrudPro.js +699 -0
  46. package/dist/libs/crud-sharding/ShardingMerger.d.ts +130 -0
  47. package/dist/libs/crud-sharding/ShardingMerger.js +280 -0
  48. package/dist/libs/crud-sharding/ShardingRouter.d.ts +69 -0
  49. package/dist/libs/crud-sharding/ShardingRouter.js +377 -0
  50. package/dist/libs/crud-sharding/ShardingTableCreator.d.ts +146 -0
  51. package/dist/libs/crud-sharding/ShardingTableCreator.js +805 -0
  52. package/dist/libs/crud-sharding/ShardingUtils.d.ts +38 -0
  53. package/dist/libs/crud-sharding/ShardingUtils.js +77 -0
  54. package/dist/libs/crud-sharding/index.d.ts +45 -0
  55. package/dist/libs/crud-sharding/index.js +55 -0
  56. package/dist/models/StandardColumns.d.ts +71 -0
  57. package/dist/models/StandardColumns.js +28 -0
  58. package/dist/service/SysAppService.js +2 -2
  59. package/dist/service/SysConfigService.js +1 -1
  60. package/dist/service/SysDictDataService.js +2 -2
  61. package/dist/service/SysMenuService.js +1 -1
  62. package/dist/service/UserAccountService.d.ts +1 -1
  63. package/dist/service/crudstd/CrudStdService.d.ts +0 -1
  64. package/dist/service/crudstd/CrudStdService.js +0 -27
  65. package/dist/service/curd/CrudProQuick.d.ts +134 -4
  66. package/dist/service/curd/CrudProQuick.js +155 -3
  67. package/dist/service/curd/CurdMixService.d.ts +2 -1
  68. package/dist/service/curd/CurdMixService.js +5 -1
  69. package/dist/service/curd/CurdProService.d.ts +44 -2
  70. package/dist/service/curd/CurdProService.js +53 -1
  71. package/dist/service/curd/README.md +1001 -0
  72. package/dist/service/curd/fixSoftDelete.d.ts +14 -0
  73. package/dist/service/curd/fixSoftDelete.js +29 -11
  74. package/dist/service/flow/FlowConfigService.js +1 -1
  75. package/dist/service/flow/FlowInstanceCrudService.js +1 -1
  76. package/package.json +3 -1
  77. package/src/controller/gateway/AsyncTaskController.ts +1 -1
  78. package/src/controller/manage/CrudStandardDesignApi.ts +16 -100
  79. package/src/index.ts +3 -0
  80. package/src/libs/crud-pro/CrudPro.ts +19 -1
  81. package/src/libs/crud-pro/README.md +809 -0
  82. package/src/libs/crud-pro/README_FUNC.md +193 -0
  83. package/src/libs/crud-pro/exceptions.ts +2 -0
  84. package/src/libs/crud-pro/interfaces.ts +38 -1
  85. package/src/libs/crud-pro/models/ExecuteContext.ts +6 -3
  86. package/src/libs/crud-pro/models/RequestModel.ts +108 -44
  87. package/src/libs/crud-pro/models/ResModel.ts +10 -4
  88. package/src/libs/crud-pro/models/ServiceHub.ts +2 -0
  89. package/src/libs/crud-pro/models/keys.ts +5 -0
  90. package/src/libs/crud-pro/services/CrudProDataTypeConvertService.ts +171 -0
  91. package/src/libs/crud-pro/services/CrudProExecuteSqlService.ts +24 -1
  92. package/src/libs/crud-pro/services/CrudProFieldValidateService.ts +53 -1
  93. package/src/libs/crud-pro/services/CrudProGenSqlService.ts +51 -7
  94. package/src/libs/crud-pro/services/CrudProOriginToExecuteSql.ts +159 -2
  95. package/src/libs/crud-pro/services/CrudProTableMetaService.ts +139 -1
  96. package/src/libs/crud-pro/services/CurdProServiceHub.ts +16 -1
  97. package/src/libs/crud-pro/utils/DateTimeUtils.ts +3 -0
  98. package/src/libs/crud-pro/utils/MixinUtils.ts +97 -1
  99. package/src/libs/crud-pro/utils/ValidateUtils.ts +1 -1
  100. package/src/libs/crud-sharding/ROUTING_LOGIC.md +944 -0
  101. package/src/libs/crud-sharding/ShardingConfig.ts +240 -0
  102. package/src/libs/crud-sharding/ShardingCountCache.ts +200 -0
  103. package/src/libs/crud-sharding/ShardingCrudPro.ts +856 -0
  104. package/src/libs/crud-sharding/ShardingMerger.ts +382 -0
  105. package/src/libs/crud-sharding/ShardingRouter.ts +512 -0
  106. package/src/libs/crud-sharding/ShardingTableCreator.ts +1007 -0
  107. package/src/libs/crud-sharding/ShardingUtils.ts +84 -0
  108. package/src/libs/crud-sharding/index.ts +64 -0
  109. package/src/models/StandardColumns.ts +76 -0
  110. package/src/service/FileCenterService.ts +1 -1
  111. package/src/service/SysAppService.ts +2 -2
  112. package/src/service/SysConfigService.ts +1 -1
  113. package/src/service/SysDictDataService.ts +2 -2
  114. package/src/service/SysMenuService.ts +2 -2
  115. package/src/service/WorkbenchService.ts +1 -1
  116. package/src/service/anyapi/AnyApiService.ts +1 -1
  117. package/src/service/asyncTask/AsyncTaskRunnerService.ts +1 -1
  118. package/src/service/crudstd/CrudStdService.ts +0 -32
  119. package/src/service/curd/CrudProQuick.ts +164 -5
  120. package/src/service/curd/CurdMixService.ts +7 -2
  121. package/src/service/curd/CurdProService.ts +62 -3
  122. package/src/service/curd/README.md +1001 -0
  123. package/src/service/curd/fixCfgModel.ts +1 -2
  124. package/src/service/curd/fixSoftDelete.ts +38 -16
  125. package/src/service/flow/FlowConfigService.ts +1 -1
  126. package/src/service/flow/FlowInstanceCrudService.ts +1 -1
@@ -0,0 +1,52 @@
1
+ import { CrudProServiceBase } from './CrudProServiceBase';
2
+ import { RequestModel } from '../models/RequestModel';
3
+ import { RequestCfgModel } from '../models/RequestCfgModel';
4
+ /**
5
+ * 数据类型转换服务
6
+ * 根据表结构字段类型,在 INSERT/UPDATE 操作前自动转换数据格式,确保数据与数据库方言兼容
7
+ *
8
+ * 当前支持的转换:
9
+ * - PostgreSQL ARRAY 类型:JSON 数组 → PG 数组字面量 {"a","b","c"}
10
+ */
11
+ declare class CrudProDataTypeConvertService extends CrudProServiceBase {
12
+ /**
13
+ * 根据表结构字段类型自动转换 reqModel.data 中的数据格式
14
+ * 仅对 PostgreSQL 的 INSERT/UPDATE 类型操作生效
15
+ *
16
+ * @param reqModel 请求模型
17
+ * @param cfgModel 配置模型
18
+ */
19
+ convertDataTypeByTableMeta(reqModel: RequestModel, cfgModel: RequestCfgModel): Promise<void>;
20
+ /**
21
+ * 遍历数据对象中的 ARRAY 类型字段,将 JSON 数组转为 PG 数组字面量格式
22
+ */
23
+ private convertArrayFields;
24
+ /**
25
+ * 尝试将值解析为数组
26
+ * 支持:数组对象、JSON 数组字符串
27
+ */
28
+ private tryParseArray;
29
+ /**
30
+ * 将 JS 数组转为 PostgreSQL 数组字面量格式
31
+ *
32
+ * PG 数组字面量规则:
33
+ * - 字符串:双引号包裹,内部 " 转义为 \",\ 转义为 \\ 例:{"hello","world"}
34
+ * - 数字:不加引号 例:{1,2,3}
35
+ * - 布尔:转为 t/f 不加引号 例:{t,f}
36
+ * - null/undefined:输出 NULL 不加引号 例:{1,NULL,3}
37
+ * - 空数组:输出 {}
38
+ *
39
+ * @param arr JS 数组
40
+ * @returns PG 数组字面量字符串
41
+ */
42
+ private toPgArrayLiteral;
43
+ /**
44
+ * 将单个 JS 值转为 PG 数组元素的字面量表示
45
+ */
46
+ private toPgArrayItem;
47
+ /**
48
+ * 判断 sqlSimpleName 是否为 INSERT/UPDATE 类型
49
+ */
50
+ private isInsertOrUpdateType;
51
+ }
52
+ export { CrudProDataTypeConvertService };
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CrudProDataTypeConvertService = void 0;
4
+ const CrudProServiceBase_1 = require("./CrudProServiceBase");
5
+ const keys_1 = require("../models/keys");
6
+ /**
7
+ * 数据类型转换服务
8
+ * 根据表结构字段类型,在 INSERT/UPDATE 操作前自动转换数据格式,确保数据与数据库方言兼容
9
+ *
10
+ * 当前支持的转换:
11
+ * - PostgreSQL ARRAY 类型:JSON 数组 → PG 数组字面量 {"a","b","c"}
12
+ */
13
+ class CrudProDataTypeConvertService extends CrudProServiceBase_1.CrudProServiceBase {
14
+ /**
15
+ * 根据表结构字段类型自动转换 reqModel.data 中的数据格式
16
+ * 仅对 PostgreSQL 的 INSERT/UPDATE 类型操作生效
17
+ *
18
+ * @param reqModel 请求模型
19
+ * @param cfgModel 配置模型
20
+ */
21
+ async convertDataTypeByTableMeta(reqModel, cfgModel) {
22
+ if (cfgModel.sqlDbType !== keys_1.SqlDbType.postgres) {
23
+ return;
24
+ }
25
+ const sqlSimpleName = cfgModel.sqlSimpleName;
26
+ if (!this.isInsertOrUpdateType(sqlSimpleName)) {
27
+ return;
28
+ }
29
+ if (!reqModel.data) {
30
+ return;
31
+ }
32
+ const query = {
33
+ sqlTable: cfgModel.sqlTable,
34
+ sqlDatabase: cfgModel.sqlDatabase,
35
+ sqlDbType: cfgModel.sqlDbType,
36
+ sqlSchema: cfgModel.sqlSchema,
37
+ };
38
+ const tableMeta = await this.serviceHub.getTableMeta(query);
39
+ if (!tableMeta || !tableMeta.columnDetails || tableMeta.columnDetails.length === 0) {
40
+ this.logger.warn('CrudProDataTypeConvertService: 无法获取表结构信息,跳过数据类型转换', cfgModel.sqlTable);
41
+ return;
42
+ }
43
+ const arrayFieldSet = new Set();
44
+ for (const col of tableMeta.columnDetails) {
45
+ if (('' + col.type).toUpperCase() === 'ARRAY') {
46
+ arrayFieldSet.add(col.name);
47
+ }
48
+ }
49
+ if (arrayFieldSet.size === 0) {
50
+ return;
51
+ }
52
+ const data = reqModel.data;
53
+ if (Array.isArray(data)) {
54
+ for (const row of data) {
55
+ this.convertArrayFields(row, arrayFieldSet);
56
+ }
57
+ }
58
+ else {
59
+ this.convertArrayFields(data, arrayFieldSet);
60
+ }
61
+ }
62
+ /**
63
+ * 遍历数据对象中的 ARRAY 类型字段,将 JSON 数组转为 PG 数组字面量格式
64
+ */
65
+ convertArrayFields(dataObj, arrayFieldSet) {
66
+ const keys = Object.keys(dataObj);
67
+ for (const key of keys) {
68
+ if (!arrayFieldSet.has(key)) {
69
+ continue;
70
+ }
71
+ const value = dataObj[key];
72
+ const parsed = this.tryParseArray(value);
73
+ if (Array.isArray(parsed)) {
74
+ dataObj[key] = this.toPgArrayLiteral(parsed);
75
+ }
76
+ }
77
+ }
78
+ /**
79
+ * 尝试将值解析为数组
80
+ * 支持:数组对象、JSON 数组字符串
81
+ */
82
+ tryParseArray(value) {
83
+ if (value === null || value === undefined) {
84
+ return null;
85
+ }
86
+ if (Array.isArray(value)) {
87
+ return value;
88
+ }
89
+ if (typeof value === 'string') {
90
+ const trimmed = value.trim();
91
+ if (trimmed.startsWith('[')) {
92
+ try {
93
+ return JSON.parse(trimmed);
94
+ }
95
+ catch (e) {
96
+ return null;
97
+ }
98
+ }
99
+ }
100
+ return null;
101
+ }
102
+ /**
103
+ * 将 JS 数组转为 PostgreSQL 数组字面量格式
104
+ *
105
+ * PG 数组字面量规则:
106
+ * - 字符串:双引号包裹,内部 " 转义为 \",\ 转义为 \\ 例:{"hello","world"}
107
+ * - 数字:不加引号 例:{1,2,3}
108
+ * - 布尔:转为 t/f 不加引号 例:{t,f}
109
+ * - null/undefined:输出 NULL 不加引号 例:{1,NULL,3}
110
+ * - 空数组:输出 {}
111
+ *
112
+ * @param arr JS 数组
113
+ * @returns PG 数组字面量字符串
114
+ */
115
+ toPgArrayLiteral(arr) {
116
+ if (arr.length === 0) {
117
+ return '{}';
118
+ }
119
+ const items = arr.map(v => this.toPgArrayItem(v));
120
+ return '{' + items.join(',') + '}';
121
+ }
122
+ /**
123
+ * 将单个 JS 值转为 PG 数组元素的字面量表示
124
+ */
125
+ toPgArrayItem(value) {
126
+ if (value === null || value === undefined) {
127
+ return 'NULL';
128
+ }
129
+ if (typeof value === 'number') {
130
+ return '' + value;
131
+ }
132
+ if (typeof value === 'boolean') {
133
+ return value ? 't' : 'f';
134
+ }
135
+ // 字符串及其它类型:转义后用双引号包裹
136
+ const escaped = ('' + value)
137
+ .replace(/\\/g, '\\\\')
138
+ .replace(/"/g, '\\"');
139
+ return '"' + escaped + '"';
140
+ }
141
+ /**
142
+ * 判断 sqlSimpleName 是否为 INSERT/UPDATE 类型
143
+ */
144
+ isInsertOrUpdateType(sqlSimpleName) {
145
+ if (!sqlSimpleName) {
146
+ return false;
147
+ }
148
+ const types = [
149
+ keys_1.KeysOfSimpleSQL.SIMPLE_INSERT,
150
+ keys_1.KeysOfSimpleSQL.SIMPLE_UPDATE,
151
+ keys_1.KeysOfSimpleSQL.SIMPLE_INSERT_ON_DUPLICATE_UPDATE,
152
+ keys_1.KeysOfSimpleSQL.SIMPLE_INSERT_OR_UPDATE,
153
+ keys_1.KeysOfSimpleSQL.SIMPLE_BATCH_INSERT,
154
+ ];
155
+ return types.includes(sqlSimpleName);
156
+ }
157
+ }
158
+ exports.CrudProDataTypeConvertService = CrudProDataTypeConvertService;
@@ -169,7 +169,14 @@ class CrudProExecuteSqlService extends CrudProServiceBase_1.CrudProServiceBase {
169
169
  return result === true;
170
170
  }
171
171
  toQueryResByResPicker(rows, originRes, sqlCfgModel) {
172
- const resPicker = sqlCfgModel.resPicker;
172
+ let resPicker = sqlCfgModel.resPicker;
173
+ // resPicker 未设置时,根据 SQL 类型自动推断
174
+ if (isEmpty(resPicker)) {
175
+ const crudType = sqlCfgModel.getCrudType();
176
+ if (crudType === keys_1.KeyOfCrudTypes.INSERT || crudType === keys_1.KeyOfCrudTypes.UPDATE || crudType === keys_1.KeyOfCrudTypes.DELETE) {
177
+ resPicker = keys_1.KeysOfSqlResPicker.UPDATE_RESULT;
178
+ }
179
+ }
173
180
  // 返回第一行
174
181
  if (keys_1.KeysOfSqlResPicker.RESULT_FIRST_ROW === resPicker) {
175
182
  if (rows && rows.length > 0) {
@@ -185,6 +192,18 @@ class CrudProExecuteSqlService extends CrudProServiceBase_1.CrudProServiceBase {
185
192
  const map0 = rows[0];
186
193
  return Number(map0['total_count'] || 0);
187
194
  }
195
+ // 判断元素是否存在,返回 boolean
196
+ if (keys_1.KeysOfSqlResPicker.RESULT_IS_EXIST === resPicker) {
197
+ if (isEmpty(rows) || rows.length === 0) {
198
+ return false;
199
+ }
200
+ const val = rows[0]['is_exist'];
201
+ // MySQL/SQLServer 返回 0/1,PostgreSQL 返回 false/true
202
+ if (typeof val === 'boolean') {
203
+ return val;
204
+ }
205
+ return Number(val) > 0;
206
+ }
188
207
  // 增删改res的内容是修改结果:包括: insert\delete\update
189
208
  if (keys_1.KeysOfSqlResPicker.UPDATE_RESULT === resPicker) {
190
209
  if (sqlCfgModel.sqlDbType === keys_1.SqlDbType.postgres) {
@@ -9,5 +9,12 @@ declare class CrudProFieldValidateService extends CrudProServiceBase {
9
9
  private validateByCfgString;
10
10
  private validateByCfgFunc;
11
11
  private validateRequired;
12
+ /**
13
+ * 校验 data 字段类型是否符合 sqlSimpleName 的要求
14
+ *
15
+ * - SIMPLE_BATCH_INSERT: data 必须是数组
16
+ * - 其他 INSERT/UPDATE 类型: data 必须是普通对象
17
+ */
18
+ validateDataType(cfgModel: RequestCfgModel, reqModel: RequestModel): void;
12
19
  }
13
20
  export { CrudProFieldValidateService };
@@ -144,5 +144,37 @@ class CrudProFieldValidateService extends CrudProServiceBase_1.CrudProServiceBas
144
144
  }
145
145
  }
146
146
  }
147
+ /**
148
+ * 校验 data 字段类型是否符合 sqlSimpleName 的要求
149
+ *
150
+ * - SIMPLE_BATCH_INSERT: data 必须是数组
151
+ * - 其他 INSERT/UPDATE 类型: data 必须是普通对象
152
+ */
153
+ validateDataType(cfgModel, reqModel) {
154
+ const sqlSimpleName = cfgModel.sqlSimpleName;
155
+ if (!sqlSimpleName) {
156
+ return;
157
+ }
158
+ const data = reqModel.data;
159
+ const isArrayData = Array.isArray(data);
160
+ if (sqlSimpleName === keys_1.KeysOfSimpleSQL.SIMPLE_BATCH_INSERT) {
161
+ if (!isArrayData || data.length === 0) {
162
+ throw new exceptions_1.CommonException(exceptions_1.Exceptions.CFG_INVALID_DATA_TYPE, `${sqlSimpleName} 要求 data 必须是数组。并且不能为空数组。`);
163
+ }
164
+ return;
165
+ }
166
+ const needsObjectData = [
167
+ keys_1.KeysOfSimpleSQL.SIMPLE_INSERT,
168
+ keys_1.KeysOfSimpleSQL.SIMPLE_UPDATE,
169
+ keys_1.KeysOfSimpleSQL.SIMPLE_INSERT_ON_DUPLICATE_UPDATE,
170
+ keys_1.KeysOfSimpleSQL.SIMPLE_INSERT_OR_UPDATE,
171
+ ].includes(sqlSimpleName);
172
+ if (needsObjectData && (!data || Object.keys(data).length === 0)) {
173
+ throw new exceptions_1.CommonException(exceptions_1.Exceptions.CFG_INVALID_DATA_TYPE, `${sqlSimpleName} 要求 data 必须是对象,不能为空`);
174
+ }
175
+ if (needsObjectData && isArrayData) {
176
+ throw new exceptions_1.CommonException(exceptions_1.Exceptions.CFG_INVALID_DATA_TYPE, `${sqlSimpleName} 要求 data 必须是普通对象,不能是数组`);
177
+ }
178
+ }
147
179
  }
148
180
  exports.CrudProFieldValidateService = CrudProFieldValidateService;
@@ -2,6 +2,13 @@ import { CrudProServiceBase } from './CrudProServiceBase';
2
2
  declare class CrudProGenSqlService extends CrudProServiceBase {
3
3
  generateSQLList(): Promise<void>;
4
4
  private generateOriginSql;
5
+ /**
6
+ * 判断元素是否存在
7
+ * 使用 SELECT EXISTS 子查询,数据库引擎找到第一条匹配记录即停止扫描,比 COUNT(*) 更高效
8
+ * @param cfgModel
9
+ * @private
10
+ */
11
+ private generateOriginSqlForIsExist;
5
12
  /**
6
13
  * 删除语句
7
14
  * @param cfgModel
@@ -20,6 +27,12 @@ declare class CrudProGenSqlService extends CrudProServiceBase {
20
27
  * @private
21
28
  */
22
29
  private generateOriginSqlForInsert;
30
+ /**
31
+ * 创建批量插入语句
32
+ * @param cfgModel
33
+ * @private
34
+ */
35
+ private generateOriginSqlForBatchInsert;
23
36
  private generateExecuteSql;
24
37
  }
25
38
  export { CrudProGenSqlService };
@@ -31,7 +31,11 @@ class CrudProGenSqlService extends CrudProServiceBase_1.CrudProServiceBase {
31
31
  let sql1 = null;
32
32
  let sql2 = null;
33
33
  let sql3 = null;
34
- if (equalsIgnoreCase(keys_1.KeysOfSimpleSQL.SIMPLE_QUERY, simpleSqlName)) {
34
+ if (equalsIgnoreCase(keys_1.KeysOfSimpleSQL.SIMPLE_QUERY_EXIST, simpleSqlName)) {
35
+ sql1 = this.generateOriginSqlForIsExist(cfgModel);
36
+ cfgModel.addSqlCfgModel('is_exist', sql1, keys_1.KeysOfSqlResPicker.RESULT_IS_EXIST);
37
+ }
38
+ else if (equalsIgnoreCase(keys_1.KeysOfSimpleSQL.SIMPLE_QUERY, simpleSqlName)) {
35
39
  sql1 = 'select @@columns from @@table where @@asWhere:condition @@orderBys @@offsetLimit';
36
40
  cfgModel.addSqlCfgModel('rows', sql1);
37
41
  }
@@ -57,6 +61,10 @@ class CrudProGenSqlService extends CrudProServiceBase_1.CrudProServiceBase {
57
61
  sql1 = this.generateOriginSqlForInsert(cfgModel);
58
62
  cfgModel.addSqlCfgModel('affected', sql1, keys_1.KeysOfSqlResPicker.UPDATE_RESULT);
59
63
  }
64
+ else if (equalsIgnoreCase(keys_1.KeysOfSimpleSQL.SIMPLE_BATCH_INSERT, simpleSqlName)) {
65
+ sql1 = this.generateOriginSqlForBatchInsert(cfgModel);
66
+ cfgModel.addSqlCfgModel('affected', sql1, keys_1.KeysOfSqlResPicker.UPDATE_RESULT);
67
+ }
60
68
  else if (equalsIgnoreCase(keys_1.KeysOfSimpleSQL.SIMPLE_UPDATE, simpleSqlName)) {
61
69
  sql1 = 'update @@table set @@asUpdate:data where @@asWhere:condition ';
62
70
  cfgModel.addSqlCfgModel('affected', sql1, keys_1.KeysOfSqlResPicker.UPDATE_RESULT);
@@ -66,18 +74,18 @@ class CrudProGenSqlService extends CrudProServiceBase_1.CrudProServiceBase {
66
74
  cfgModel.addSqlCfgModel('affected', sql1, keys_1.KeysOfSqlResPicker.UPDATE_RESULT);
67
75
  }
68
76
  else if (equalsIgnoreCase(keys_1.KeysOfSimpleSQL.SIMPLE_INSERT_OR_UPDATE, simpleSqlName)) {
69
- sql1 = 'select count(0) as total_count from @@table where @@asWhere:condition ';
70
- sql2 = 'insert into @@table ( @@asInsertKeys:data ) values( @@asInsertValues:data )';
77
+ sql1 = this.generateOriginSqlForIsExist(cfgModel); // 判断元素是否存在
78
+ sql2 = this.generateOriginSqlForInsert(cfgModel); // 插入语句
71
79
  sql3 = 'update @@table set @@asUpdate:data where @@asWhere:condition ';
72
80
  const insertWhen2 = {
73
81
  functionName: 'eq',
74
- functionParams: [{ contextAsNumber: 'res.total_count' }, { constNumber: 0 }],
82
+ functionParams: [{ contextAsBool: 'res.is_exist' }, { constBool: false }],
75
83
  };
76
84
  const updateWhen3 = {
77
- functionName: 'gt',
78
- functionParams: [{ contextAsNumber: 'res.total_count' }, { constNumber: 0 }],
85
+ functionName: 'eq',
86
+ functionParams: [{ contextAsBool: 'res.is_exist' }, { constBool: true }],
79
87
  };
80
- cfgModel.addSqlCfgModel('total_count', sql1, keys_1.KeysOfSqlResPicker.RESULT_TOTAL_COUNT);
88
+ cfgModel.addSqlCfgModel('is_exist', sql1, keys_1.KeysOfSqlResPicker.RESULT_IS_EXIST);
81
89
  cfgModel.addSqlCfgModel('insert_affected', sql2, keys_1.KeysOfSqlResPicker.UPDATE_RESULT, insertWhen2);
82
90
  cfgModel.addSqlCfgModel('update_affected', sql3, keys_1.KeysOfSqlResPicker.UPDATE_RESULT, updateWhen3);
83
91
  }
@@ -85,6 +93,21 @@ class CrudProGenSqlService extends CrudProServiceBase_1.CrudProServiceBase {
85
93
  throw new exceptions_1.CommonException(exceptions_1.Exceptions.CFG_NOT_SUPPORT_THE_SIMPLE_SQL, simpleSqlName);
86
94
  }
87
95
  }
96
+ /**
97
+ * 判断元素是否存在
98
+ * 使用 SELECT EXISTS 子查询,数据库引擎找到第一条匹配记录即停止扫描,比 COUNT(*) 更高效
99
+ * @param cfgModel
100
+ * @private
101
+ */
102
+ generateOriginSqlForIsExist(cfgModel) {
103
+ if (cfgModel.sqlDbType === keys_1.SqlDbType.postgres) {
104
+ return 'SELECT EXISTS(SELECT 1 FROM @@table WHERE @@asWhere:condition) AS is_exist';
105
+ }
106
+ if (cfgModel.sqlDbType === keys_1.SqlDbType.sqlserver) {
107
+ return 'SELECT CASE WHEN EXISTS(SELECT 1 FROM @@table WHERE @@asWhere:condition) THEN 1 ELSE 0 END AS is_exist';
108
+ }
109
+ return 'SELECT EXISTS(SELECT 1 FROM @@table WHERE @@asWhere:condition) AS is_exist';
110
+ }
88
111
  /**
89
112
  * 删除语句
90
113
  * @param cfgModel
@@ -143,6 +166,20 @@ class CrudProGenSqlService extends CrudProServiceBase_1.CrudProServiceBase {
143
166
  }
144
167
  return 'insert into @@table ( @@asInsertKeys:data ) values( @@asInsertValues:data )'; // 关键字的前后,必须有空格
145
168
  }
169
+ /**
170
+ * 创建批量插入语句
171
+ * @param cfgModel
172
+ * @private
173
+ */
174
+ generateOriginSqlForBatchInsert(cfgModel) {
175
+ if (cfgModel.sqlDbType === keys_1.SqlDbType.postgres) {
176
+ return 'insert into @@table ( @@asBatchInsertKeys:data ) values @@asBatchInsertValues:data RETURNING * ';
177
+ }
178
+ if (cfgModel.sqlDbType === keys_1.SqlDbType.sqlserver) {
179
+ return 'insert into @@table ( @@asBatchInsertKeys:data ) OUTPUT INSERTED.* values @@asBatchInsertValues:data ';
180
+ }
181
+ return 'insert into @@table ( @@asBatchInsertKeys:data ) values @@asBatchInsertValues:data '; // MySQL 默认语法
182
+ }
146
183
  async generateExecuteSql() {
147
184
  const exeCtx = this.getExecuteContext();
148
185
  const reqCfgModel = exeCtx.getCfgModel();
@@ -2,6 +2,15 @@ import { CrudProServiceBase } from './CrudProServiceBase';
2
2
  import { SqlCfgModel } from '../models/SqlCfgModel';
3
3
  declare class CrudProOriginToExecuteSql extends CrudProServiceBase {
4
4
  convertOriginToExecuteSql(sqlCfgModel: SqlCfgModel): Promise<void>;
5
+ /**
6
+ * 对于自定义 INSERT SQL,自动追加数据库方言子句:
7
+ * - PostgreSQL: RETURNING *
8
+ * - SQL Server: OUTPUT INSERTED.*
9
+ * - MySQL: 无需追加,驱动自动返回 insertId
10
+ *
11
+ * 仅当 originSql 中用户未手动添加这些子句时才追加
12
+ */
13
+ private appendDialectClause;
5
14
  private generateWordMap;
6
15
  private generateWord;
7
16
  private generateDataAsInsertKeys;
@@ -21,6 +30,40 @@ declare class CrudProOriginToExecuteSql extends CrudProServiceBase {
21
30
  private generateGetAttr;
22
31
  private generateDataUpdate;
23
32
  private generateDataAsInsertValues;
33
+ /**
34
+ * 获取批量插入的字段名列表
35
+ *
36
+ * 策略:
37
+ * - 如果指定了 columns,直接使用 columns(允许数据行包含额外字段,仅插入指定列)
38
+ * - 否则取第一条数据的 keys,并校验后续行是否一致(严格模式)
39
+ *
40
+ * 严格模式限制:
41
+ * - 所有数据行必须包含完全相同的字段
42
+ * - 字段数量必须一致
43
+ * - 字段名必须一致(顺序可不同)
44
+ * - 若不一致,抛出 BATCH_INSERT_KEYS_MISMATCH 异常
45
+ *
46
+ * @param req RequestModel 请求模型
47
+ * @param dataArray 数据数组
48
+ * @returns 排序后的字段名数组
49
+ */
50
+ private getBatchInsertKeys;
51
+ /**
52
+ * 批量插入:生成列名部分
53
+ *
54
+ * 列名获取策略:
55
+ * - 优先使用 req.columns(若指定)
56
+ * - 否则使用严格模式:取第一条数据的 keys,校验后续行字段一致性
57
+ */
58
+ private generateDataAsBatchInsertKeys;
59
+ /**
60
+ * 批量插入:生成值部分,格式为 (?,?,?),(?,?,?),...
61
+ *
62
+ * 列名获取策略:
63
+ * - 优先使用 req.columns(若指定)
64
+ * - 否则使用严格模式:取第一条数据的 keys,校验后续行字段一致性
65
+ */
66
+ private generateDataAsBatchInsertValues;
24
67
  private generateSqlJavaFunction;
25
68
  /**
26
69
  * 不会返回空。
@@ -60,7 +60,40 @@ class CrudProOriginToExecuteSql extends CrudProServiceBase_1.CrudProServiceBase
60
60
  });
61
61
  }
62
62
  sqlCfgModel.executeSqlArgs = argList;
63
- sqlCfgModel.executeSql = executeSql;
63
+ sqlCfgModel.executeSql = this.appendDialectClause(executeSql, sqlCfgModel);
64
+ }
65
+ /**
66
+ * 对于自定义 INSERT SQL,自动追加数据库方言子句:
67
+ * - PostgreSQL: RETURNING *
68
+ * - SQL Server: OUTPUT INSERTED.*
69
+ * - MySQL: 无需追加,驱动自动返回 insertId
70
+ *
71
+ * 仅当 originSql 中用户未手动添加这些子句时才追加
72
+ */
73
+ appendDialectClause(executeSql, sqlCfgModel) {
74
+ const sqlDbType = sqlCfgModel.sqlDbType;
75
+ const sqlUpper = executeSql.trim().toUpperCase();
76
+ if (!sqlUpper.startsWith(keys_1.KeyOfCrudTypes.INSERT)) {
77
+ return executeSql;
78
+ }
79
+ if (sqlDbType === keys_1.SqlDbType.postgres) {
80
+ if (sqlUpper.indexOf('RETURNING') < 0) {
81
+ return executeSql + ' RETURNING *';
82
+ }
83
+ }
84
+ else if (sqlDbType === keys_1.SqlDbType.sqlserver) {
85
+ if (sqlUpper.indexOf('OUTPUT INSERTED') < 0) {
86
+ // SQL Server: OUTPUT INSERTED.* 需放在 VALUES 之前
87
+ // INSERT INTO table (...) OUTPUT INSERTED.* VALUES (...)
88
+ const valuesIndex = sqlUpper.lastIndexOf('VALUES');
89
+ if (valuesIndex > 0) {
90
+ const beforeValues = executeSql.substring(0, valuesIndex);
91
+ const fromValues = executeSql.substring(valuesIndex);
92
+ return beforeValues + ' OUTPUT INSERTED.* ' + fromValues;
93
+ }
94
+ }
95
+ }
96
+ return executeSql;
64
97
  }
65
98
  async generateWordMap(originSql, sqlCfgModel) {
66
99
  // ['@@columns', '@@table', '@@asWhere:condition', '@@orderBys']
@@ -115,6 +148,12 @@ class CrudProOriginToExecuteSql extends CrudProServiceBase_1.CrudProServiceBase
115
148
  else if (word.startsWith(keys_1.KeysOfCustomSQL.SQL_AS_INSERT_VALUES)) {
116
149
  return this.generateDataAsInsertValues(reqModel, MixinUtils_1.MixinUtils.removeStringPrefix(word, keys_1.KeysOfCustomSQL.SQL_AS_INSERT_VALUES));
117
150
  }
151
+ else if (word.startsWith(keys_1.KeysOfCustomSQL.SQL_AS_BATCH_INSERT_KEYS)) {
152
+ return this.generateDataAsBatchInsertKeys(reqModel, sqlCfg, MixinUtils_1.MixinUtils.removeStringPrefix(word, keys_1.KeysOfCustomSQL.SQL_AS_BATCH_INSERT_KEYS));
153
+ }
154
+ else if (word.startsWith(keys_1.KeysOfCustomSQL.SQL_AS_BATCH_INSERT_VALUES)) {
155
+ return this.generateDataAsBatchInsertValues(reqModel, MixinUtils_1.MixinUtils.removeStringPrefix(word, keys_1.KeysOfCustomSQL.SQL_AS_BATCH_INSERT_VALUES));
156
+ }
118
157
  else if (word.startsWith(keys_1.KeysOfCustomSQL.SQL_FUNCTION)) {
119
158
  return this.generateSqlJavaFunction(reqCfg, MixinUtils_1.MixinUtils.removeStringPrefix(word, keys_1.KeysOfCustomSQL.SQL_FUNCTION));
120
159
  }
@@ -287,6 +326,98 @@ class CrudProOriginToExecuteSql extends CrudProServiceBase_1.CrudProServiceBase
287
326
  const sql = sqlList.join(','); // "?,?,?,?"
288
327
  return new SqlSegArg_1.SqlSegArg(sql, argList);
289
328
  }
329
+ /**
330
+ * 获取批量插入的字段名列表
331
+ *
332
+ * 策略:
333
+ * - 如果指定了 columns,直接使用 columns(允许数据行包含额外字段,仅插入指定列)
334
+ * - 否则取第一条数据的 keys,并校验后续行是否一致(严格模式)
335
+ *
336
+ * 严格模式限制:
337
+ * - 所有数据行必须包含完全相同的字段
338
+ * - 字段数量必须一致
339
+ * - 字段名必须一致(顺序可不同)
340
+ * - 若不一致,抛出 BATCH_INSERT_KEYS_MISMATCH 异常
341
+ *
342
+ * @param req RequestModel 请求模型
343
+ * @param dataArray 数据数组
344
+ * @returns 排序后的字段名数组
345
+ */
346
+ getBatchInsertKeys(req, dataArray) {
347
+ // 如果指定了 columns,直接使用
348
+ if (MixinUtils_1.MixinUtils.isNotEmpty(req.columns) && req.columns.length > 0) {
349
+ return [...req.columns].sort();
350
+ }
351
+ // 严格模式:取第一条数据的 keys,并校验后续行
352
+ const firstRow = dataArray[0];
353
+ const keys = Object.keys(firstRow).sort();
354
+ for (let i = 1; i < dataArray.length; i++) {
355
+ const row = dataArray[i];
356
+ const rowKeys = Object.keys(row).sort();
357
+ if (rowKeys.length !== keys.length) {
358
+ throw new exceptions_1.CommonException(exceptions_1.Exceptions.BATCH_INSERT_KEYS_MISMATCH, `批量插入第 ${i + 1} 行数据字段数量不一致,期望 ${keys.length} 个,实际 ${rowKeys.length} 个`);
359
+ }
360
+ for (let j = 0; j < keys.length; j++) {
361
+ if (rowKeys[j] !== keys[j]) {
362
+ throw new exceptions_1.CommonException(exceptions_1.Exceptions.BATCH_INSERT_KEYS_MISMATCH, `批量插入第 ${i + 1} 行数据字段不一致,缺少字段 "${keys[j]}" 或包含额外字段`);
363
+ }
364
+ }
365
+ }
366
+ return keys;
367
+ }
368
+ /**
369
+ * 批量插入:生成列名部分
370
+ *
371
+ * 列名获取策略:
372
+ * - 优先使用 req.columns(若指定)
373
+ * - 否则使用严格模式:取第一条数据的 keys,校验后续行字段一致性
374
+ */
375
+ generateDataAsBatchInsertKeys(req, sqlCfgModel, dataName) {
376
+ const dataArray = req.getCondOrDataAsArray(dataName);
377
+ if (MixinUtils_1.MixinUtils.isEmpty(dataArray) || dataArray.length === 0) {
378
+ throw new exceptions_1.CommonException(exceptions_1.Exceptions.DATA_GET_DATA_EMPTY_ON_INSERT_KEYS, dataName);
379
+ }
380
+ const keys = this.getBatchInsertKeys(req, dataArray);
381
+ const columnList = keys.map(columnName => {
382
+ return (0, convertColumnName_1.toSqlColumnName)(columnName, sqlCfgModel);
383
+ });
384
+ const sql = columnList.join(','); // `column1`,`column2`,`column3`
385
+ return new SqlSegArg_1.SqlSegArg(sql);
386
+ }
387
+ /**
388
+ * 批量插入:生成值部分,格式为 (?,?,?),(?,?,?),...
389
+ *
390
+ * 列名获取策略:
391
+ * - 优先使用 req.columns(若指定)
392
+ * - 否则使用严格模式:取第一条数据的 keys,校验后续行字段一致性
393
+ */
394
+ generateDataAsBatchInsertValues(req, dataName) {
395
+ const dataArray = req.getCondOrDataAsArray(dataName);
396
+ if (MixinUtils_1.MixinUtils.isEmpty(dataArray) || dataArray.length === 0) {
397
+ throw new exceptions_1.CommonException(exceptions_1.Exceptions.DATA_GET_DATA_EMPTY_ON_INSERT_VALUES, dataName);
398
+ }
399
+ const keys = this.getBatchInsertKeys(req, dataArray);
400
+ const valueGroups = [];
401
+ const argList = [];
402
+ for (let i = 0; i < dataArray.length; i++) {
403
+ const row = dataArray[i];
404
+ const rowValues = [];
405
+ for (let j = 0; j < keys.length; j++) {
406
+ const key = keys[j];
407
+ const arg = row[key];
408
+ if (SqlFuncUtils_1.sqlFuncUtils.isSqlFuncArg(arg)) {
409
+ rowValues.push(SqlFuncUtils_1.sqlFuncUtils.toFuncSQL(arg));
410
+ }
411
+ else {
412
+ rowValues.push('?');
413
+ argList.push(arg);
414
+ }
415
+ }
416
+ valueGroups.push('(' + rowValues.join(',') + ')');
417
+ }
418
+ const sql = valueGroups.join(','); // "(?,?,?),(?,?,?),..."
419
+ return new SqlSegArg_1.SqlSegArg(sql, argList);
420
+ }
290
421
  generateSqlJavaFunction(requestCfg, tmpFuncName) {
291
422
  const exeCtx = this.getExecuteContext();
292
423
  const functionCfg = requestCfg.functionCfg;
@@ -1,7 +1,21 @@
1
1
  import { CrudProServiceBase } from './CrudProServiceBase';
2
- import { ITableMeta, ITableMetaQuery } from '../interfaces';
2
+ import { ITableListResult, ITableMeta, ITableMetaQuery, ITableNamesQuery, ITableNamesOptions } from '../interfaces';
3
3
  declare class CrudProTableMetaService extends CrudProServiceBase {
4
4
  getTableMeta(query: ITableMetaQuery): Promise<ITableMeta>;
5
+ /**
6
+ * 获取数据库所有表和视图信息(包含类型)
7
+ *
8
+ * @param query 数据库查询参数
9
+ * @param options 缓存控制选项
10
+ * - skipCache: 跳过缓存,直接查询数据库
11
+ * - refreshCache: 查询后更新缓存
12
+ */
13
+ getAllTableInfos(query: ITableNamesQuery, options?: ITableNamesOptions): Promise<ITableListResult>;
14
+ private getTableInfoCacheKey;
15
+ private loadAllTableInfos;
16
+ private loadMySQLAllTableInfos;
17
+ private loadPostgreSQLAllTableInfos;
18
+ private loadSQLServerAllTableInfos;
5
19
  private loadTableMeta;
6
20
  private loadTableColumnDetails;
7
21
  private loadMySQLColumnDetails;