midway-fatcms 0.0.5 → 0.0.7

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 (132) hide show
  1. package/.qoder/skills/midway-fatcms-crud/SKILL.md +375 -0
  2. package/.qoder/skills/midway-fatcms-crud/examples.md +990 -0
  3. package/.qoder/skills/midway-fatcms-crud/reference.md +568 -0
  4. package/README.md +377 -134
  5. package/dist/controller/manage/CrudStandardDesignApi.d.ts +0 -2
  6. package/dist/controller/manage/CrudStandardDesignApi.js +11 -85
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.js +2 -0
  9. package/dist/libs/crud-pro/CrudPro.d.ts +9 -1
  10. package/dist/libs/crud-pro/CrudPro.js +15 -0
  11. package/dist/libs/crud-pro/README.md +809 -0
  12. package/dist/libs/crud-pro/README_FUNC.md +193 -0
  13. package/dist/libs/crud-pro/exceptions.d.ts +2 -0
  14. package/dist/libs/crud-pro/exceptions.js +2 -0
  15. package/dist/libs/crud-pro/interfaces.d.ts +34 -1
  16. package/dist/libs/crud-pro/models/ExecuteContext.d.ts +3 -3
  17. package/dist/libs/crud-pro/models/ExecuteContext.js +2 -0
  18. package/dist/libs/crud-pro/models/RequestModel.d.ts +6 -2
  19. package/dist/libs/crud-pro/models/RequestModel.js +20 -53
  20. package/dist/libs/crud-pro/models/ResModel.d.ts +6 -4
  21. package/dist/libs/crud-pro/models/ServiceHub.d.ts +1 -0
  22. package/dist/libs/crud-pro/models/keys.d.ts +6 -1
  23. package/dist/libs/crud-pro/models/keys.js +5 -0
  24. package/dist/libs/crud-pro/services/CrudProDataTypeConvertService.d.ts +52 -0
  25. package/dist/libs/crud-pro/services/CrudProDataTypeConvertService.js +158 -0
  26. package/dist/libs/crud-pro/services/CrudProExecuteSqlService.js +20 -1
  27. package/dist/libs/crud-pro/services/CrudProFieldValidateService.d.ts +7 -0
  28. package/dist/libs/crud-pro/services/CrudProFieldValidateService.js +32 -0
  29. package/dist/libs/crud-pro/services/CrudProGenSqlService.d.ts +13 -0
  30. package/dist/libs/crud-pro/services/CrudProGenSqlService.js +44 -7
  31. package/dist/libs/crud-pro/services/CrudProOriginToExecuteSql.d.ts +43 -0
  32. package/dist/libs/crud-pro/services/CrudProOriginToExecuteSql.js +132 -1
  33. package/dist/libs/crud-pro/services/CrudProTableMetaService.d.ts +15 -1
  34. package/dist/libs/crud-pro/services/CrudProTableMetaService.js +107 -0
  35. package/dist/libs/crud-pro/services/CurdProServiceHub.d.ts +5 -1
  36. package/dist/libs/crud-pro/services/CurdProServiceHub.js +11 -0
  37. package/dist/libs/crud-pro/utils/DateTimeUtils.d.ts +1 -0
  38. package/dist/libs/crud-pro/utils/DateTimeUtils.js +3 -0
  39. package/dist/libs/crud-pro/utils/MixinUtils.d.ts +32 -0
  40. package/dist/libs/crud-pro/utils/MixinUtils.js +85 -1
  41. package/dist/libs/crud-pro/utils/OrderByUtils.d.ts +70 -0
  42. package/dist/libs/crud-pro/utils/OrderByUtils.js +158 -0
  43. package/dist/libs/crud-pro/utils/ValidateUtils.js +1 -1
  44. package/dist/libs/crud-sharding/ROUTING_LOGIC.md +944 -0
  45. package/dist/libs/crud-sharding/ShardingConfig.d.ts +218 -0
  46. package/dist/libs/crud-sharding/ShardingConfig.js +32 -0
  47. package/dist/libs/crud-sharding/ShardingCountCache.d.ts +69 -0
  48. package/dist/libs/crud-sharding/ShardingCountCache.js +160 -0
  49. package/dist/libs/crud-sharding/ShardingCrudPro.d.ts +363 -0
  50. package/dist/libs/crud-sharding/ShardingCrudPro.js +675 -0
  51. package/dist/libs/crud-sharding/ShardingMerger.d.ts +130 -0
  52. package/dist/libs/crud-sharding/ShardingMerger.js +282 -0
  53. package/dist/libs/crud-sharding/ShardingRouter.d.ts +69 -0
  54. package/dist/libs/crud-sharding/ShardingRouter.js +377 -0
  55. package/dist/libs/crud-sharding/ShardingTableCreator.d.ts +146 -0
  56. package/dist/libs/crud-sharding/ShardingTableCreator.js +805 -0
  57. package/dist/libs/crud-sharding/ShardingUtils.d.ts +38 -0
  58. package/dist/libs/crud-sharding/ShardingUtils.js +77 -0
  59. package/dist/libs/crud-sharding/index.d.ts +45 -0
  60. package/dist/libs/crud-sharding/index.js +55 -0
  61. package/dist/models/StandardColumns.d.ts +71 -0
  62. package/dist/models/StandardColumns.js +28 -0
  63. package/dist/service/SysAppService.js +2 -2
  64. package/dist/service/SysConfigService.js +1 -1
  65. package/dist/service/SysDictDataService.js +2 -2
  66. package/dist/service/SysMenuService.js +1 -1
  67. package/dist/service/UserAccountService.d.ts +1 -1
  68. package/dist/service/crudstd/CrudStdService.d.ts +0 -1
  69. package/dist/service/crudstd/CrudStdService.js +0 -27
  70. package/dist/service/curd/CrudProQuick.d.ts +134 -4
  71. package/dist/service/curd/CrudProQuick.js +155 -3
  72. package/dist/service/curd/CurdMixService.d.ts +2 -1
  73. package/dist/service/curd/CurdMixService.js +5 -1
  74. package/dist/service/curd/CurdProService.d.ts +44 -2
  75. package/dist/service/curd/CurdProService.js +53 -1
  76. package/dist/service/curd/README.md +1100 -0
  77. package/dist/service/curd/fixSoftDelete.d.ts +14 -0
  78. package/dist/service/curd/fixSoftDelete.js +29 -11
  79. package/dist/service/flow/FlowConfigService.js +1 -1
  80. package/dist/service/flow/FlowInstanceCrudService.js +1 -1
  81. package/package.json +4 -1
  82. package/src/controller/gateway/AsyncTaskController.ts +1 -1
  83. package/src/controller/manage/CrudStandardDesignApi.ts +16 -100
  84. package/src/index.ts +3 -0
  85. package/src/libs/crud-pro/CrudPro.ts +19 -1
  86. package/src/libs/crud-pro/README.md +809 -0
  87. package/src/libs/crud-pro/README_FUNC.md +193 -0
  88. package/src/libs/crud-pro/exceptions.ts +2 -0
  89. package/src/libs/crud-pro/interfaces.ts +38 -1
  90. package/src/libs/crud-pro/models/ExecuteContext.ts +6 -3
  91. package/src/libs/crud-pro/models/RequestModel.ts +23 -65
  92. package/src/libs/crud-pro/models/ResModel.ts +10 -4
  93. package/src/libs/crud-pro/models/ServiceHub.ts +2 -0
  94. package/src/libs/crud-pro/models/keys.ts +5 -0
  95. package/src/libs/crud-pro/services/CrudProDataTypeConvertService.ts +171 -0
  96. package/src/libs/crud-pro/services/CrudProExecuteSqlService.ts +24 -1
  97. package/src/libs/crud-pro/services/CrudProFieldValidateService.ts +53 -1
  98. package/src/libs/crud-pro/services/CrudProGenSqlService.ts +51 -7
  99. package/src/libs/crud-pro/services/CrudProOriginToExecuteSql.ts +159 -2
  100. package/src/libs/crud-pro/services/CrudProTableMetaService.ts +139 -1
  101. package/src/libs/crud-pro/services/CurdProServiceHub.ts +16 -1
  102. package/src/libs/crud-pro/utils/DateTimeUtils.ts +3 -0
  103. package/src/libs/crud-pro/utils/MixinUtils.ts +97 -1
  104. package/src/libs/crud-pro/utils/OrderByUtils.ts +169 -0
  105. package/src/libs/crud-pro/utils/ValidateUtils.ts +1 -1
  106. package/src/libs/crud-sharding/ROUTING_LOGIC.md +944 -0
  107. package/src/libs/crud-sharding/ShardingConfig.ts +240 -0
  108. package/src/libs/crud-sharding/ShardingCountCache.ts +200 -0
  109. package/src/libs/crud-sharding/ShardingCrudPro.ts +835 -0
  110. package/src/libs/crud-sharding/ShardingMerger.ts +384 -0
  111. package/src/libs/crud-sharding/ShardingRouter.ts +512 -0
  112. package/src/libs/crud-sharding/ShardingTableCreator.ts +1007 -0
  113. package/src/libs/crud-sharding/ShardingUtils.ts +84 -0
  114. package/src/libs/crud-sharding/index.ts +64 -0
  115. package/src/models/StandardColumns.ts +76 -0
  116. package/src/service/FileCenterService.ts +1 -1
  117. package/src/service/SysAppService.ts +2 -2
  118. package/src/service/SysConfigService.ts +1 -1
  119. package/src/service/SysDictDataService.ts +2 -2
  120. package/src/service/SysMenuService.ts +2 -2
  121. package/src/service/WorkbenchService.ts +1 -1
  122. package/src/service/anyapi/AnyApiService.ts +1 -1
  123. package/src/service/asyncTask/AsyncTaskRunnerService.ts +1 -1
  124. package/src/service/crudstd/CrudStdService.ts +0 -32
  125. package/src/service/curd/CrudProQuick.ts +164 -5
  126. package/src/service/curd/CurdMixService.ts +7 -2
  127. package/src/service/curd/CurdProService.ts +62 -3
  128. package/src/service/curd/README.md +1100 -0
  129. package/src/service/curd/fixCfgModel.ts +1 -2
  130. package/src/service/curd/fixSoftDelete.ts +38 -16
  131. package/src/service/flow/FlowConfigService.ts +1 -1
  132. package/src/service/flow/FlowInstanceCrudService.ts +1 -1
@@ -0,0 +1,805 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ShardingTableCreator = void 0;
4
+ const keys_1 = require("../../libs/crud-pro/models/keys");
5
+ /**
6
+ * 分表自动创建器
7
+ *
8
+ * 根据基表结构自动创建分表,支持 MySQL、PostgreSQL、SQL Server。
9
+ *
10
+ * @example
11
+ * const creator = new ShardingTableCreator(crudPro, config);
12
+ * await creator.createTableIfNeeded('t_order_202403', {
13
+ * sqlDatabase: 'mydb',
14
+ * sqlDbType: SqlDbType.mysql,
15
+ * });
16
+ */
17
+ class ShardingTableCreator {
18
+ constructor(crudPro, config) {
19
+ this.crudPro = crudPro;
20
+ this.config = config;
21
+ this.validateIdentifier(this.config.baseTable, 'baseTable');
22
+ }
23
+ /**
24
+ * 校验表名格式,防止 SQL 注入
25
+ * 只允许合法的数据库标识符:以字母或下划线开头,后续只能包含字母数字下划线
26
+ */
27
+ validateIdentifier(tableName, fieldName) {
28
+ const validIdentifierPattern = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
29
+ if (!tableName) {
30
+ throw new Error(`[ShardingTableCreator] ${fieldName} 不能为空`);
31
+ }
32
+ if (!validIdentifierPattern.test(tableName)) {
33
+ throw new Error(`[ShardingTableCreator] ${fieldName} 格式不合法: "${tableName}"。` +
34
+ `只允许以字母或下划线开头,后续包含字母、数字、下划线的标识符`);
35
+ }
36
+ }
37
+ /**
38
+ * 创建分表(如果不存在)
39
+ *
40
+ * @param tableName 目标分表名
41
+ * @param baseCfg 基础配置(sqlDatabase, sqlDbType)
42
+ * @param options 创建选项
43
+ * @returns 创建结果
44
+ */
45
+ async createTableIfNeeded(tableName, baseCfg, options = {}) {
46
+ // 校验分表名格式,防止 SQL 注入
47
+ this.validateIdentifier(tableName, 'tableName');
48
+ const { copyIndexes = false } = options;
49
+ try {
50
+ // 1. 获取基表元信息
51
+ const tableMeta = await this.getBaseTableMeta(baseCfg);
52
+ if (!tableMeta || !tableMeta.columnDetails || tableMeta.columnDetails.length === 0) {
53
+ throw new Error(`[ShardingTableCreator] 无法获取基表 ${this.config.baseTable} 的结构信息`);
54
+ }
55
+ // 2. 生成建表 SQL
56
+ const createSql = this.generateCreateTableSql(tableName, tableMeta.columnDetails, baseCfg.sqlDbType, options);
57
+ // 3. 执行建表
58
+ await this.executeCreateSql(baseCfg, createSql);
59
+ // 4. 可选:复制索引
60
+ if (copyIndexes) {
61
+ await this.copyIndexes(tableName, baseCfg);
62
+ }
63
+ return { success: true, tableName, createSql };
64
+ }
65
+ catch (error) {
66
+ return {
67
+ success: false,
68
+ tableName,
69
+ error: error instanceof Error ? error : new Error(String(error)),
70
+ };
71
+ }
72
+ }
73
+ /**
74
+ * 获取基表列信息
75
+ */
76
+ async getBaseTableMeta(baseCfg) {
77
+ try {
78
+ const columns = await this.loadTableColumns(baseCfg);
79
+ if (!columns || columns.length === 0) {
80
+ return null;
81
+ }
82
+ return { columnDetails: columns };
83
+ }
84
+ catch (error) {
85
+ console.error('[ShardingTableCreator] 获取基表元信息失败:', error);
86
+ return null;
87
+ }
88
+ }
89
+ /**
90
+ * 根据数据库类型加载表列信息
91
+ */
92
+ async loadTableColumns(baseCfg) {
93
+ const { sqlDbType } = baseCfg;
94
+ if (sqlDbType === keys_1.SqlDbType.mysql) {
95
+ return this.loadMySQLColumns(baseCfg);
96
+ }
97
+ else if (sqlDbType === keys_1.SqlDbType.postgres) {
98
+ return this.loadPostgreSQLColumns(baseCfg);
99
+ }
100
+ else if (sqlDbType === keys_1.SqlDbType.sqlserver) {
101
+ return this.loadSQLServerColumns(baseCfg);
102
+ }
103
+ throw new Error(`[ShardingTableCreator] 不支持的数据库类型: ${sqlDbType}`);
104
+ }
105
+ /**
106
+ * 加载 MySQL 表列信息
107
+ */
108
+ async loadMySQLColumns(baseCfg) {
109
+ const sql = `
110
+ SELECT
111
+ COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_KEY,
112
+ COLUMN_DEFAULT, COLUMN_COMMENT, EXTRA
113
+ FROM information_schema.COLUMNS
114
+ WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '${this.config.baseTable}'
115
+ ORDER BY ORDINAL_POSITION
116
+ `.trim();
117
+ const result = await this.crudPro.executeSQL({
118
+ isNativeSQL: true,
119
+ sqlDatabase: baseCfg.sqlDatabase,
120
+ sqlDbType: baseCfg.sqlDbType,
121
+ executeSql: sql,
122
+ });
123
+ const rows = (result === null || result === void 0 ? void 0 : result.rows) || [];
124
+ return rows.map((row) => {
125
+ var _a;
126
+ return ({
127
+ name: row.COLUMN_NAME,
128
+ type: row.COLUMN_TYPE,
129
+ isNullable: row.IS_NULLABLE === 'YES',
130
+ isPrimaryKey: row.COLUMN_KEY === 'PRI',
131
+ defaultValue: row.COLUMN_DEFAULT,
132
+ comment: row.COLUMN_COMMENT || undefined,
133
+ isAutoIncrement: ((_a = row.EXTRA) === null || _a === void 0 ? void 0 : _a.includes('auto_increment')) || false,
134
+ });
135
+ });
136
+ }
137
+ /**
138
+ * 加载 PostgreSQL 表列信息
139
+ */
140
+ async loadPostgreSQLColumns(baseCfg) {
141
+ const schema = 'public';
142
+ const sql = `
143
+ SELECT
144
+ column_name,
145
+ data_type,
146
+ is_nullable,
147
+ column_default,
148
+ character_maximum_length
149
+ FROM information_schema.columns
150
+ WHERE table_schema = '${schema}' AND table_name = '${this.config.baseTable}'
151
+ ORDER BY ordinal_position
152
+ `.trim();
153
+ const result = await this.crudPro.executeSQL({
154
+ isNativeSQL: true,
155
+ sqlDatabase: baseCfg.sqlDatabase,
156
+ sqlDbType: baseCfg.sqlDbType,
157
+ executeSql: sql,
158
+ });
159
+ const rows = (result === null || result === void 0 ? void 0 : result.rows) || [];
160
+ // 获取主键列
161
+ const pkColumns = await this.getPostgreSQLPrimaryKeys(baseCfg, schema);
162
+ return rows.map((row) => {
163
+ const { value, isSerial } = this.normalizePostgreSQLDefaultValue(row.column_default);
164
+ return {
165
+ name: row.column_name,
166
+ type: row.data_type,
167
+ isNullable: row.is_nullable === 'YES',
168
+ isPrimaryKey: pkColumns.includes(row.column_name),
169
+ defaultValue: value,
170
+ maxLength: row.character_maximum_length,
171
+ isAutoIncrement: isSerial,
172
+ };
173
+ });
174
+ }
175
+ /**
176
+ * 获取 PostgreSQL 表主键列
177
+ */
178
+ async getPostgreSQLPrimaryKeys(baseCfg, schema) {
179
+ const sql = `
180
+ SELECT a.attname
181
+ FROM pg_index i
182
+ JOIN pg_class c ON i.indrelid = c.oid
183
+ JOIN pg_namespace n ON c.relnamespace = n.oid
184
+ JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum = ANY(i.indkey)
185
+ WHERE n.nspname = '${schema}'
186
+ AND c.relname = '${this.config.baseTable}'
187
+ AND i.indisprimary
188
+ `.trim();
189
+ const result = await this.crudPro.executeSQL({
190
+ isNativeSQL: true,
191
+ sqlDatabase: baseCfg.sqlDatabase,
192
+ sqlDbType: baseCfg.sqlDbType,
193
+ executeSql: sql,
194
+ });
195
+ return ((result === null || result === void 0 ? void 0 : result.rows) || []).map((row) => row.attname);
196
+ }
197
+ /**
198
+ * 加载 SQL Server 表列信息
199
+ */
200
+ async loadSQLServerColumns(baseCfg) {
201
+ const sql = `
202
+ SELECT
203
+ c.name AS column_name,
204
+ t.name AS data_type,
205
+ c.max_length,
206
+ c.is_nullable,
207
+ c.is_identity,
208
+ dc.definition AS default_value
209
+ FROM sys.columns c
210
+ JOIN sys.types t ON c.user_type_id = t.user_type_id
211
+ LEFT JOIN sys.default_constraints dc ON dc.parent_column_id = c.column_id AND dc.parent_object_id = c.object_id
212
+ WHERE c.object_id = OBJECT_ID('${this.config.baseTable}')
213
+ ORDER BY c.column_id
214
+ `.trim();
215
+ const result = await this.crudPro.executeSQL({
216
+ isNativeSQL: true,
217
+ sqlDatabase: baseCfg.sqlDatabase,
218
+ sqlDbType: baseCfg.sqlDbType,
219
+ executeSql: sql,
220
+ });
221
+ const rows = (result === null || result === void 0 ? void 0 : result.rows) || [];
222
+ // 获取主键列
223
+ const pkColumns = await this.getSQLServerPrimaryKeys(baseCfg);
224
+ return rows.map((row) => {
225
+ let type = row.data_type;
226
+ if (row.max_length > 0 && ['varchar', 'nvarchar', 'char', 'nchar'].includes(row.data_type.toLowerCase())) {
227
+ type = `${row.data_type}(${row.max_length})`;
228
+ }
229
+ return {
230
+ name: row.column_name,
231
+ type,
232
+ isNullable: row.is_nullable,
233
+ isPrimaryKey: pkColumns.includes(row.column_name),
234
+ defaultValue: this.normalizeSQLServerDefaultValue(row.default_value),
235
+ isAutoIncrement: !!row.is_identity,
236
+ };
237
+ });
238
+ }
239
+ /**
240
+ * 获取 SQL Server 表主键列
241
+ */
242
+ async getSQLServerPrimaryKeys(baseCfg) {
243
+ const sql = `
244
+ SELECT c.name
245
+ FROM sys.indexes i
246
+ JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
247
+ JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
248
+ WHERE i.object_id = OBJECT_ID('${this.config.baseTable}')
249
+ AND i.is_primary_key = 1
250
+ `.trim();
251
+ const result = await this.crudPro.executeSQL({
252
+ isNativeSQL: true,
253
+ sqlDatabase: baseCfg.sqlDatabase,
254
+ sqlDbType: baseCfg.sqlDbType,
255
+ executeSql: sql,
256
+ });
257
+ return ((result === null || result === void 0 ? void 0 : result.rows) || []).map((row) => row.name);
258
+ }
259
+ /**
260
+ * 生成建表 SQL
261
+ */
262
+ generateCreateTableSql(tableName, columns, sqlDbType, options) {
263
+ switch (sqlDbType) {
264
+ case keys_1.SqlDbType.mysql:
265
+ return this.generateMySQLCreateSql(tableName, columns, options);
266
+ case keys_1.SqlDbType.postgres:
267
+ return this.generatePostgreSQLCreateSql(tableName, columns, options);
268
+ case keys_1.SqlDbType.sqlserver:
269
+ return this.generateSQLServerCreateSql(tableName, columns, options);
270
+ default:
271
+ throw new Error(`[ShardingTableCreator] 不支持的数据库类型: ${sqlDbType}`);
272
+ }
273
+ }
274
+ // ============ MySQL ============
275
+ generateMySQLCreateSql(tableName, columns, options) {
276
+ const columnDefs = [];
277
+ // 列定义
278
+ for (const col of columns) {
279
+ columnDefs.push(this.formatMySQLColumn(col));
280
+ }
281
+ // 主键
282
+ const pkColumns = columns.filter(c => c.isPrimaryKey).map(c => `\`${c.name}\``);
283
+ if (pkColumns.length > 0) {
284
+ columnDefs.push(`PRIMARY KEY (${pkColumns.join(', ')})`);
285
+ }
286
+ // 表选项
287
+ const tableOptions = options.tableOptions || 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb4';
288
+ return `CREATE TABLE IF NOT EXISTS \`${tableName}\` (\n ${columnDefs.join(',\n ')}\n) ${tableOptions}`;
289
+ }
290
+ formatMySQLColumn(col) {
291
+ const parts = [`\`${col.name}\``];
292
+ // 类型
293
+ parts.push(col.type);
294
+ // NOT NULL
295
+ if (!col.isNullable) {
296
+ parts.push('NOT NULL');
297
+ }
298
+ // AUTO_INCREMENT
299
+ const isAutoIncrement = !!col.isAutoIncrement;
300
+ if (isAutoIncrement) {
301
+ parts.push('AUTO_INCREMENT');
302
+ }
303
+ // 默认值(AUTO_INCREMENT 列不设默认值)
304
+ if (!isAutoIncrement) {
305
+ if (col.defaultValue !== null && col.defaultValue !== undefined) {
306
+ parts.push(`DEFAULT ${this.formatDefaultValue(col.defaultValue, col.type)}`);
307
+ }
308
+ else if (col.isNullable) {
309
+ parts.push('DEFAULT NULL');
310
+ }
311
+ }
312
+ // 注释
313
+ if (col.comment) {
314
+ parts.push(`COMMENT '${this.escapeString(col.comment)}'`);
315
+ }
316
+ return parts.join(' ');
317
+ }
318
+ // ============ PostgreSQL ============
319
+ generatePostgreSQLCreateSql(tableName, columns, options) {
320
+ const columnDefs = [];
321
+ // 列定义
322
+ for (const col of columns) {
323
+ columnDefs.push(this.formatPostgreSQLColumn(col));
324
+ }
325
+ // 主键
326
+ const pkColumns = columns.filter(c => c.isPrimaryKey).map(c => `"${c.name}"`);
327
+ if (pkColumns.length > 0) {
328
+ columnDefs.push(`PRIMARY KEY (${pkColumns.join(', ')})`);
329
+ }
330
+ // 表选项(PostgreSQL 表选项通过 WITH 子句)
331
+ const tableOptions = options.tableOptions ? ` WITH (${options.tableOptions})` : '';
332
+ return `CREATE TABLE IF NOT EXISTS "${tableName}" (\n ${columnDefs.join(',\n ')}\n)${tableOptions}`;
333
+ }
334
+ formatPostgreSQLColumn(col) {
335
+ const parts = [`"${col.name}"`];
336
+ // 类型映射
337
+ const pgType = this.mapToPostgreSQLType(col.type);
338
+ parts.push(pgType);
339
+ // NOT NULL
340
+ if (!col.isNullable) {
341
+ parts.push('NOT NULL');
342
+ }
343
+ // 默认值
344
+ if (col.defaultValue !== null && col.defaultValue !== undefined) {
345
+ parts.push(`DEFAULT ${this.formatDefaultValue(col.defaultValue, col.type)}`);
346
+ }
347
+ return parts.join(' ');
348
+ }
349
+ mapToPostgreSQLType(mysqlType) {
350
+ // MySQL -> PostgreSQL 类型映射
351
+ const typeMap = {
352
+ 'int': 'INTEGER',
353
+ 'bigint': 'BIGINT',
354
+ 'smallint': 'SMALLINT',
355
+ 'tinyint': 'SMALLINT',
356
+ 'varchar': 'VARCHAR',
357
+ 'char': 'CHAR',
358
+ 'text': 'TEXT',
359
+ 'longtext': 'TEXT',
360
+ 'mediumtext': 'TEXT',
361
+ 'datetime': 'TIMESTAMP',
362
+ 'timestamp': 'TIMESTAMP',
363
+ 'date': 'DATE',
364
+ 'time': 'TIME',
365
+ 'decimal': 'NUMERIC',
366
+ 'double': 'DOUBLE PRECISION',
367
+ 'float': 'REAL',
368
+ 'blob': 'BYTEA',
369
+ 'longblob': 'BYTEA',
370
+ 'mediumblob': 'BYTEA',
371
+ 'json': 'JSONB',
372
+ };
373
+ // 提取基础类型(去掉长度等参数)
374
+ const baseType = mysqlType.toLowerCase().split('(')[0].trim();
375
+ const mapped = typeMap[baseType] || mysqlType.toUpperCase();
376
+ // 保留长度参数
377
+ const lengthMatch = mysqlType.match(/\((\d+)(,(\d+))?\)/);
378
+ if (lengthMatch && (baseType === 'varchar' || baseType === 'char' || baseType === 'decimal')) {
379
+ return mapped + '(' + lengthMatch[1] + (lengthMatch[3] ? ',' + lengthMatch[3] : '') + ')';
380
+ }
381
+ return mapped;
382
+ }
383
+ // ============ SQL Server ============
384
+ generateSQLServerCreateSql(tableName, columns, options) {
385
+ const columnDefs = [];
386
+ // 列定义
387
+ for (const col of columns) {
388
+ columnDefs.push(this.formatSQLServerColumn(col));
389
+ }
390
+ // 主键
391
+ const pkColumns = columns.filter(c => c.isPrimaryKey).map(c => `[${c.name}]`);
392
+ if (pkColumns.length > 0) {
393
+ columnDefs.push(`PRIMARY KEY (${pkColumns.join(', ')})`);
394
+ }
395
+ // 表选项
396
+ const tableOptions = options.tableOptions ? ` ${options.tableOptions}` : '';
397
+ return `IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = '${tableName}')\n` +
398
+ `CREATE TABLE [${tableName}] (\n ${columnDefs.join(',\n ')}\n)${tableOptions}`;
399
+ }
400
+ formatSQLServerColumn(col) {
401
+ const parts = [`[${col.name}]`];
402
+ // 类型映射
403
+ const sqlServerType = this.mapToSQLServerType(col.type);
404
+ parts.push(sqlServerType);
405
+ // NOT NULL
406
+ if (!col.isNullable) {
407
+ parts.push('NOT NULL');
408
+ }
409
+ // 默认值(IDENTITY 列不设默认值)
410
+ const isAutoIncrement = !!col.isAutoIncrement;
411
+ if (!isAutoIncrement && col.defaultValue !== null && col.defaultValue !== undefined) {
412
+ parts.push(`DEFAULT ${this.formatDefaultValue(col.defaultValue, col.type)}`);
413
+ }
414
+ // 标识列(自增)
415
+ if (isAutoIncrement) {
416
+ parts.push('IDENTITY(1,1)');
417
+ }
418
+ return parts.join(' ');
419
+ }
420
+ mapToSQLServerType(mysqlType) {
421
+ const typeMap = {
422
+ 'int': 'INT',
423
+ 'bigint': 'BIGINT',
424
+ 'smallint': 'SMALLINT',
425
+ 'tinyint': 'TINYINT',
426
+ 'varchar': 'NVARCHAR',
427
+ 'char': 'NCHAR',
428
+ 'text': 'NVARCHAR(MAX)',
429
+ 'longtext': 'NVARCHAR(MAX)',
430
+ 'datetime': 'DATETIME',
431
+ 'timestamp': 'DATETIME2',
432
+ 'date': 'DATE',
433
+ 'time': 'TIME',
434
+ 'decimal': 'DECIMAL',
435
+ 'double': 'FLOAT',
436
+ 'float': 'FLOAT',
437
+ 'blob': 'VARBINARY(MAX)',
438
+ 'json': 'NVARCHAR(MAX)',
439
+ };
440
+ const baseType = mysqlType.toLowerCase().split('(')[0].trim();
441
+ const mapped = typeMap[baseType] || mysqlType.toUpperCase();
442
+ // 保留长度参数
443
+ const lengthMatch = mysqlType.match(/\((\d+)(,(\d+))?\)/);
444
+ if (lengthMatch && (baseType === 'varchar' || baseType === 'char' || baseType === 'decimal')) {
445
+ const len = parseInt(lengthMatch[1]);
446
+ // SQL Server NVARCHAR 最大 4000
447
+ if (baseType === 'varchar' && len > 4000) {
448
+ return 'NVARCHAR(MAX)';
449
+ }
450
+ return mapped + '(' + len + (lengthMatch[3] ? ',' + lengthMatch[3] : '') + ')';
451
+ }
452
+ return mapped;
453
+ }
454
+ // ============ 系统目录默认值清洗 ============
455
+ /**
456
+ * 清理 SQL Server 系统目录中的默认值表达式
457
+ *
458
+ * sys.default_constraints.definition 返回格式如 ((1)), ('abc'), (getdate())
459
+ * 需要去除最外层括号,还原为可用的默认值
460
+ */
461
+ normalizeSQLServerDefaultValue(raw) {
462
+ if (!raw)
463
+ return undefined;
464
+ let val = raw.trim();
465
+ // 去除最外层成对括号(可能多层,如 ((1)) )
466
+ while (val.startsWith('(') && val.endsWith(')')) {
467
+ let depth = 0;
468
+ let balanced = true;
469
+ for (let i = 0; i < val.length - 1; i++) {
470
+ if (val[i] === '(')
471
+ depth++;
472
+ else if (val[i] === ')')
473
+ depth--;
474
+ if (depth === 0 && i < val.length - 1) {
475
+ balanced = false;
476
+ break;
477
+ }
478
+ }
479
+ if (!balanced)
480
+ break;
481
+ val = val.slice(1, -1).trim();
482
+ }
483
+ // SQL 引号包裹的字符串:'hello' → 提取内容
484
+ if (val.startsWith("'") && val.endsWith("'")) {
485
+ return val.slice(1, -1).replace(/''/g, "'");
486
+ }
487
+ // 数字
488
+ if (/^-?\d+(\.\d+)?$/.test(val)) {
489
+ return parseFloat(val);
490
+ }
491
+ // 其他(函数表达式如 getdate()、newid() 等)直接返回字符串
492
+ return val;
493
+ }
494
+ /**
495
+ * 清理 PostgreSQL 系统目录中的默认值表达式
496
+ *
497
+ * column_default 返回值如:
498
+ * - nextval('xxx_seq'::regclass) → 序列自增,不应作为 DEFAULT
499
+ * - 'hello'::character varying → 带类型后缀的字符串默认值
500
+ * - 1, true, now() 等
501
+ */
502
+ normalizePostgreSQLDefaultValue(raw) {
503
+ if (!raw)
504
+ return { value: undefined, isSerial: false };
505
+ const val = raw.trim();
506
+ // 序列自增(SERIAL/BIGSERIAL 列)
507
+ if (val.startsWith('nextval(')) {
508
+ return { value: undefined, isSerial: true };
509
+ }
510
+ // 去除 ::type 后缀(PostgreSQL 类型转换语法)
511
+ let cleaned = val.replace(/::[\w."\[\]]+(\[\])?$/, '');
512
+ // SQL 引号包裹的字符串
513
+ if (cleaned.startsWith("'") && cleaned.endsWith("'")) {
514
+ return { value: cleaned.slice(1, -1).replace(/''/g, "'"), isSerial: false };
515
+ }
516
+ // 数字
517
+ if (/^-?\d+(\.\d+)?$/.test(cleaned)) {
518
+ return { value: parseFloat(cleaned), isSerial: false };
519
+ }
520
+ // 布尔
521
+ if (cleaned.toLowerCase() === 'true')
522
+ return { value: true, isSerial: false };
523
+ if (cleaned.toLowerCase() === 'false')
524
+ return { value: false, isSerial: false };
525
+ // 其他(函数表达式)
526
+ return { value: cleaned, isSerial: false };
527
+ }
528
+ // ============ 通用方法 ============
529
+ /**
530
+ * 格式化默认值
531
+ */
532
+ formatDefaultValue(value, colType) {
533
+ if (value === null || value === undefined) {
534
+ return 'NULL';
535
+ }
536
+ if (typeof value === 'number') {
537
+ return String(value);
538
+ }
539
+ if (typeof value === 'boolean') {
540
+ return value ? 'TRUE' : 'FALSE';
541
+ }
542
+ if (typeof value === 'string') {
543
+ const trimmed = value.trim();
544
+ // SQL 函数表达式(如 CURRENT_TIMESTAMP, GETDATE(), now())
545
+ // 标识符后跟 ( 视为函数调用,直接输出不加引号
546
+ if (/^[a-zA-Z_]+\s*\(/.test(trimmed)
547
+ || ['CURRENT_TIMESTAMP', 'CURRENT_DATE', 'CURRENT_TIME'].includes(trimmed.toUpperCase())) {
548
+ return trimmed;
549
+ }
550
+ return `'${this.escapeString(value)}'`;
551
+ }
552
+ return String(value);
553
+ }
554
+ /**
555
+ * 转义字符串
556
+ */
557
+ escapeString(str) {
558
+ return str.replace(/'/g, "''");
559
+ }
560
+ /**
561
+ * 执行建表 SQL
562
+ */
563
+ async executeCreateSql(baseCfg, createSql) {
564
+ const sqlCfgModel = {
565
+ isNativeSQL: true,
566
+ sqlDatabase: baseCfg.sqlDatabase,
567
+ sqlDbType: baseCfg.sqlDbType,
568
+ executeSql: createSql,
569
+ };
570
+ await this.crudPro.executeSQL(sqlCfgModel);
571
+ }
572
+ /**
573
+ * 复制索引(可选功能)
574
+ *
575
+ * 从基表复制索引到分表。
576
+ * 注意:主键索引已在建表时创建,此处只复制普通索引。
577
+ */
578
+ async copyIndexes(tableName, baseCfg) {
579
+ try {
580
+ const indexDefs = await this.getIndexDefinitions(baseCfg);
581
+ for (const indexDef of indexDefs) {
582
+ // 跳过主键索引(已创建)
583
+ if (indexDef.isPrimary || !indexDef.createSql)
584
+ continue;
585
+ // 替换表名(支持不同数据库的标识符语法)
586
+ const indexSql = this.replaceTableNameInIndexSql(indexDef.createSql, tableName, baseCfg.sqlDbType);
587
+ // 重命名索引以避免冲突
588
+ const renamedSql = this.renameIndex(indexSql, tableName, baseCfg.sqlDbType);
589
+ await this.executeCreateSql(baseCfg, renamedSql);
590
+ }
591
+ }
592
+ catch (error) {
593
+ console.warn('[ShardingTableCreator] 复制索引失败:', error);
594
+ // 索引复制失败不影响主流程
595
+ }
596
+ }
597
+ /**
598
+ * 在索引 SQL 中替换表名
599
+ *
600
+ * 支持 MySQL(反引号)、PostgreSQL(双引号)、SQL Server(方括号) 的标识符语法
601
+ */
602
+ replaceTableNameInIndexSql(indexSql, tableName, sqlDbType) {
603
+ const baseTable = this.config.baseTable;
604
+ if (sqlDbType === keys_1.SqlDbType.mysql) {
605
+ // MySQL: 反引号包裹
606
+ return indexSql.replace(new RegExp(`\\\`${this.escapeRegExp(baseTable)}\\\``, 'g'), `\`${tableName}\``);
607
+ }
608
+ else if (sqlDbType === keys_1.SqlDbType.postgres) {
609
+ // PostgreSQL: 双引号包裹
610
+ return indexSql.replace(new RegExp(`"${this.escapeRegExp(baseTable)}"`, 'g'), `"${tableName}"`);
611
+ }
612
+ else if (sqlDbType === keys_1.SqlDbType.sqlserver) {
613
+ // SQL Server: 方括号包裹
614
+ return indexSql.replace(new RegExp(`\\[${this.escapeRegExp(baseTable)}\\]`, 'g'), `[${tableName}]`);
615
+ }
616
+ return indexSql;
617
+ }
618
+ /**
619
+ * 获取基表的索引定义
620
+ *
621
+ * 支持 MySQL、PostgreSQL、SQL Server,返回可直接执行的 CREATE INDEX 语句
622
+ */
623
+ async getIndexDefinitions(baseCfg) {
624
+ const { sqlDbType } = baseCfg;
625
+ try {
626
+ if (sqlDbType === keys_1.SqlDbType.mysql) {
627
+ return this.getMySQLIndexDefinitions(baseCfg);
628
+ }
629
+ else if (sqlDbType === keys_1.SqlDbType.postgres) {
630
+ return this.getPostgreSQLIndexDefinitions(baseCfg);
631
+ }
632
+ else if (sqlDbType === keys_1.SqlDbType.sqlserver) {
633
+ return this.getSQLServerIndexDefinitions(baseCfg);
634
+ }
635
+ }
636
+ catch (error) {
637
+ console.warn('[ShardingTableCreator] 获取索引定义失败:', error);
638
+ }
639
+ return [];
640
+ }
641
+ /**
642
+ * 获取 MySQL 索引定义
643
+ */
644
+ async getMySQLIndexDefinitions(baseCfg) {
645
+ const sql = `
646
+ SELECT
647
+ INDEX_NAME,
648
+ COLUMN_NAME,
649
+ NON_UNIQUE,
650
+ INDEX_TYPE,
651
+ SEQ_IN_INDEX
652
+ FROM information_schema.STATISTICS
653
+ WHERE TABLE_SCHEMA = DATABASE()
654
+ AND TABLE_NAME = '${this.config.baseTable}'
655
+ ORDER BY INDEX_NAME, SEQ_IN_INDEX
656
+ `;
657
+ const result = await this.crudPro.executeSQL({
658
+ isNativeSQL: true,
659
+ sqlDatabase: baseCfg.sqlDatabase,
660
+ sqlDbType: baseCfg.sqlDbType,
661
+ executeSql: sql,
662
+ });
663
+ const rows = (result === null || result === void 0 ? void 0 : result.rows) || [];
664
+ // 按索引名分组
665
+ const indexMap = new Map();
666
+ for (const row of rows) {
667
+ const indexName = row.INDEX_NAME;
668
+ const columnName = row.COLUMN_NAME;
669
+ const isUnique = row.NON_UNIQUE === 0;
670
+ const isPrimary = indexName === 'PRIMARY';
671
+ if (!indexMap.has(indexName)) {
672
+ indexMap.set(indexName, { columns: [], isUnique, isPrimary });
673
+ }
674
+ indexMap.get(indexName).columns.push(columnName);
675
+ }
676
+ // 生成 CREATE INDEX 语句
677
+ const definitions = [];
678
+ for (const [indexName, info] of indexMap) {
679
+ const { columns, isUnique, isPrimary } = info;
680
+ if (isPrimary) {
681
+ // 主键索引在建表时已创建,标记为主键但不需要执行 SQL
682
+ definitions.push({ isPrimary: true, createSql: '' });
683
+ continue;
684
+ }
685
+ const columnList = columns.map(c => `\`${c}\``).join(', ');
686
+ const uniqueKeyword = isUnique ? 'UNIQUE ' : '';
687
+ const createSql = `CREATE ${uniqueKeyword}INDEX \`${indexName}\` ON \`${this.config.baseTable}\` (${columnList})`;
688
+ definitions.push({ isPrimary: false, createSql });
689
+ }
690
+ return definitions;
691
+ }
692
+ /**
693
+ * 获取 PostgreSQL 索引定义
694
+ */
695
+ async getPostgreSQLIndexDefinitions(baseCfg) {
696
+ const schema = 'public';
697
+ // 查询索引定义
698
+ const sql = `
699
+ SELECT
700
+ i.relname as index_name,
701
+ ix.indisprimary as is_primary,
702
+ ix.indisunique as is_unique,
703
+ pg_get_indexdef(ix.indexrelid) as index_def
704
+ FROM pg_index ix
705
+ JOIN pg_class t ON t.oid = ix.indrelid
706
+ JOIN pg_class i ON i.oid = ix.indexrelid
707
+ JOIN pg_namespace n ON n.oid = t.relnamespace
708
+ WHERE n.nspname = '${schema}'
709
+ AND t.relname = '${this.config.baseTable}'
710
+ `;
711
+ const result = await this.crudPro.executeSQL({
712
+ isNativeSQL: true,
713
+ sqlDatabase: baseCfg.sqlDatabase,
714
+ sqlDbType: baseCfg.sqlDbType,
715
+ executeSql: sql,
716
+ });
717
+ const rows = (result === null || result === void 0 ? void 0 : result.rows) || [];
718
+ return rows.map((row) => ({
719
+ isPrimary: row.is_primary,
720
+ createSql: row.index_def,
721
+ }));
722
+ }
723
+ /**
724
+ * 获取 SQL Server 索引定义
725
+ */
726
+ async getSQLServerIndexDefinitions(baseCfg) {
727
+ const sql = `
728
+ SELECT
729
+ i.name as index_name,
730
+ i.is_primary_key as is_primary,
731
+ i.is_unique as is_unique,
732
+ i.type_desc as index_type,
733
+ STUFF((
734
+ SELECT ', [' + c.name + ']'
735
+ FROM sys.index_columns ic
736
+ JOIN sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id
737
+ WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id
738
+ ORDER BY ic.key_ordinal
739
+ FOR XML PATH('')
740
+ ), 1, 2, '') as column_list
741
+ FROM sys.indexes i
742
+ WHERE i.object_id = OBJECT_ID('${this.config.baseTable}')
743
+ AND i.name IS NOT NULL
744
+ `;
745
+ const result = await this.crudPro.executeSQL({
746
+ isNativeSQL: true,
747
+ sqlDatabase: baseCfg.sqlDatabase,
748
+ sqlDbType: baseCfg.sqlDbType,
749
+ executeSql: sql,
750
+ });
751
+ const rows = (result === null || result === void 0 ? void 0 : result.rows) || [];
752
+ return rows.map((row) => {
753
+ const isPrimary = row.is_primary;
754
+ const isUnique = row.is_unique;
755
+ const indexName = row.index_name;
756
+ const columnList = row.column_list;
757
+ const indexType = row.index_type === 'NONCLUSTERED' ? 'NONCLUSTERED' : 'CLUSTERED';
758
+ if (isPrimary) {
759
+ return { isPrimary: true, createSql: '' };
760
+ }
761
+ const uniqueKeyword = isUnique ? 'UNIQUE ' : '';
762
+ const createSql = `CREATE ${uniqueKeyword}${indexType} INDEX [${indexName}] ON [${this.config.baseTable}] (${columnList})`;
763
+ return { isPrimary: false, createSql };
764
+ });
765
+ }
766
+ /**
767
+ * 重命名索引(避免不同分表索引名冲突)
768
+ *
769
+ * 支持 MySQL、PostgreSQL、SQL Server 的标识符语法
770
+ */
771
+ renameIndex(indexSql, tableName, sqlDbType) {
772
+ // 提取表名后缀(如 202403)
773
+ const suffix = tableName.replace(this.config.baseTable + '_', '');
774
+ // 根据不同数据库类型使用不同的标识符包裹方式
775
+ let indexPattern;
776
+ let replacement;
777
+ if (sqlDbType === keys_1.SqlDbType.mysql) {
778
+ // MySQL: 反引号包裹
779
+ indexPattern = /INDEX `([^`]+)`/i;
780
+ replacement = `INDEX \`$1_${suffix}\``;
781
+ }
782
+ else if (sqlDbType === keys_1.SqlDbType.postgres) {
783
+ // PostgreSQL: 双引号包裹
784
+ indexPattern = /INDEX "([^"]+)"/i;
785
+ replacement = `INDEX "$1_${suffix}"`;
786
+ }
787
+ else if (sqlDbType === keys_1.SqlDbType.sqlserver) {
788
+ // SQL Server: 方括号包裹
789
+ indexPattern = /INDEX \[([^\]]+)\]/i;
790
+ replacement = `INDEX [$1_${suffix}]`;
791
+ }
792
+ else {
793
+ // 默认:无包裹(不太可能到达这里)
794
+ return indexSql;
795
+ }
796
+ return indexSql.replace(indexPattern, replacement);
797
+ }
798
+ /**
799
+ * 转义正则特殊字符
800
+ */
801
+ escapeRegExp(str) {
802
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
803
+ }
804
+ }
805
+ exports.ShardingTableCreator = ShardingTableCreator;