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