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.
- package/.qoder/skills/midway-fatcms-crud/SKILL.md +375 -0
- package/.qoder/skills/midway-fatcms-crud/examples.md +990 -0
- package/.qoder/skills/midway-fatcms-crud/reference.md +568 -0
- package/README.md +377 -134
- package/dist/controller/manage/CrudStandardDesignApi.d.ts +0 -2
- package/dist/controller/manage/CrudStandardDesignApi.js +11 -85
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/libs/crud-pro/CrudPro.d.ts +9 -1
- package/dist/libs/crud-pro/CrudPro.js +15 -0
- package/dist/libs/crud-pro/README.md +809 -0
- package/dist/libs/crud-pro/README_FUNC.md +193 -0
- package/dist/libs/crud-pro/exceptions.d.ts +2 -0
- package/dist/libs/crud-pro/exceptions.js +2 -0
- package/dist/libs/crud-pro/interfaces.d.ts +34 -1
- package/dist/libs/crud-pro/models/ExecuteContext.d.ts +3 -3
- package/dist/libs/crud-pro/models/ExecuteContext.js +2 -0
- package/dist/libs/crud-pro/models/RequestModel.d.ts +6 -2
- package/dist/libs/crud-pro/models/RequestModel.js +20 -53
- package/dist/libs/crud-pro/models/ResModel.d.ts +6 -4
- package/dist/libs/crud-pro/models/ServiceHub.d.ts +1 -0
- package/dist/libs/crud-pro/models/keys.d.ts +6 -1
- package/dist/libs/crud-pro/models/keys.js +5 -0
- package/dist/libs/crud-pro/services/CrudProDataTypeConvertService.d.ts +52 -0
- package/dist/libs/crud-pro/services/CrudProDataTypeConvertService.js +158 -0
- package/dist/libs/crud-pro/services/CrudProExecuteSqlService.js +20 -1
- package/dist/libs/crud-pro/services/CrudProFieldValidateService.d.ts +7 -0
- package/dist/libs/crud-pro/services/CrudProFieldValidateService.js +32 -0
- package/dist/libs/crud-pro/services/CrudProGenSqlService.d.ts +13 -0
- package/dist/libs/crud-pro/services/CrudProGenSqlService.js +44 -7
- package/dist/libs/crud-pro/services/CrudProOriginToExecuteSql.d.ts +43 -0
- package/dist/libs/crud-pro/services/CrudProOriginToExecuteSql.js +132 -1
- package/dist/libs/crud-pro/services/CrudProTableMetaService.d.ts +15 -1
- package/dist/libs/crud-pro/services/CrudProTableMetaService.js +107 -0
- package/dist/libs/crud-pro/services/CurdProServiceHub.d.ts +5 -1
- package/dist/libs/crud-pro/services/CurdProServiceHub.js +11 -0
- package/dist/libs/crud-pro/utils/DateTimeUtils.d.ts +1 -0
- package/dist/libs/crud-pro/utils/DateTimeUtils.js +3 -0
- package/dist/libs/crud-pro/utils/MixinUtils.d.ts +32 -0
- package/dist/libs/crud-pro/utils/MixinUtils.js +85 -1
- package/dist/libs/crud-pro/utils/OrderByUtils.d.ts +70 -0
- package/dist/libs/crud-pro/utils/OrderByUtils.js +158 -0
- package/dist/libs/crud-pro/utils/ValidateUtils.js +1 -1
- package/dist/libs/crud-sharding/ROUTING_LOGIC.md +944 -0
- package/dist/libs/crud-sharding/ShardingConfig.d.ts +218 -0
- package/dist/libs/crud-sharding/ShardingConfig.js +32 -0
- package/dist/libs/crud-sharding/ShardingCountCache.d.ts +69 -0
- package/dist/libs/crud-sharding/ShardingCountCache.js +160 -0
- package/dist/libs/crud-sharding/ShardingCrudPro.d.ts +363 -0
- package/dist/libs/crud-sharding/ShardingCrudPro.js +675 -0
- package/dist/libs/crud-sharding/ShardingMerger.d.ts +130 -0
- package/dist/libs/crud-sharding/ShardingMerger.js +282 -0
- package/dist/libs/crud-sharding/ShardingRouter.d.ts +69 -0
- package/dist/libs/crud-sharding/ShardingRouter.js +377 -0
- package/dist/libs/crud-sharding/ShardingTableCreator.d.ts +146 -0
- package/dist/libs/crud-sharding/ShardingTableCreator.js +805 -0
- package/dist/libs/crud-sharding/ShardingUtils.d.ts +38 -0
- package/dist/libs/crud-sharding/ShardingUtils.js +77 -0
- package/dist/libs/crud-sharding/index.d.ts +45 -0
- package/dist/libs/crud-sharding/index.js +55 -0
- package/dist/models/StandardColumns.d.ts +71 -0
- package/dist/models/StandardColumns.js +28 -0
- package/dist/service/SysAppService.js +2 -2
- package/dist/service/SysConfigService.js +1 -1
- package/dist/service/SysDictDataService.js +2 -2
- package/dist/service/SysMenuService.js +1 -1
- package/dist/service/UserAccountService.d.ts +1 -1
- package/dist/service/crudstd/CrudStdService.d.ts +0 -1
- package/dist/service/crudstd/CrudStdService.js +0 -27
- package/dist/service/curd/CrudProQuick.d.ts +134 -4
- package/dist/service/curd/CrudProQuick.js +155 -3
- package/dist/service/curd/CurdMixService.d.ts +2 -1
- package/dist/service/curd/CurdMixService.js +5 -1
- package/dist/service/curd/CurdProService.d.ts +44 -2
- package/dist/service/curd/CurdProService.js +53 -1
- package/dist/service/curd/README.md +1100 -0
- package/dist/service/curd/fixSoftDelete.d.ts +14 -0
- package/dist/service/curd/fixSoftDelete.js +29 -11
- package/dist/service/flow/FlowConfigService.js +1 -1
- package/dist/service/flow/FlowInstanceCrudService.js +1 -1
- package/package.json +4 -1
- package/src/controller/gateway/AsyncTaskController.ts +1 -1
- package/src/controller/manage/CrudStandardDesignApi.ts +16 -100
- package/src/index.ts +3 -0
- package/src/libs/crud-pro/CrudPro.ts +19 -1
- package/src/libs/crud-pro/README.md +809 -0
- package/src/libs/crud-pro/README_FUNC.md +193 -0
- package/src/libs/crud-pro/exceptions.ts +2 -0
- package/src/libs/crud-pro/interfaces.ts +38 -1
- package/src/libs/crud-pro/models/ExecuteContext.ts +6 -3
- package/src/libs/crud-pro/models/RequestModel.ts +23 -65
- package/src/libs/crud-pro/models/ResModel.ts +10 -4
- package/src/libs/crud-pro/models/ServiceHub.ts +2 -0
- package/src/libs/crud-pro/models/keys.ts +5 -0
- package/src/libs/crud-pro/services/CrudProDataTypeConvertService.ts +171 -0
- package/src/libs/crud-pro/services/CrudProExecuteSqlService.ts +24 -1
- package/src/libs/crud-pro/services/CrudProFieldValidateService.ts +53 -1
- package/src/libs/crud-pro/services/CrudProGenSqlService.ts +51 -7
- package/src/libs/crud-pro/services/CrudProOriginToExecuteSql.ts +159 -2
- package/src/libs/crud-pro/services/CrudProTableMetaService.ts +139 -1
- package/src/libs/crud-pro/services/CurdProServiceHub.ts +16 -1
- package/src/libs/crud-pro/utils/DateTimeUtils.ts +3 -0
- package/src/libs/crud-pro/utils/MixinUtils.ts +97 -1
- package/src/libs/crud-pro/utils/OrderByUtils.ts +169 -0
- package/src/libs/crud-pro/utils/ValidateUtils.ts +1 -1
- package/src/libs/crud-sharding/ROUTING_LOGIC.md +944 -0
- package/src/libs/crud-sharding/ShardingConfig.ts +240 -0
- package/src/libs/crud-sharding/ShardingCountCache.ts +200 -0
- package/src/libs/crud-sharding/ShardingCrudPro.ts +835 -0
- package/src/libs/crud-sharding/ShardingMerger.ts +384 -0
- package/src/libs/crud-sharding/ShardingRouter.ts +512 -0
- package/src/libs/crud-sharding/ShardingTableCreator.ts +1007 -0
- package/src/libs/crud-sharding/ShardingUtils.ts +84 -0
- package/src/libs/crud-sharding/index.ts +64 -0
- package/src/models/StandardColumns.ts +76 -0
- package/src/service/FileCenterService.ts +1 -1
- package/src/service/SysAppService.ts +2 -2
- package/src/service/SysConfigService.ts +1 -1
- package/src/service/SysDictDataService.ts +2 -2
- package/src/service/SysMenuService.ts +2 -2
- package/src/service/WorkbenchService.ts +1 -1
- package/src/service/anyapi/AnyApiService.ts +1 -1
- package/src/service/asyncTask/AsyncTaskRunnerService.ts +1 -1
- package/src/service/crudstd/CrudStdService.ts +0 -32
- package/src/service/curd/CrudProQuick.ts +164 -5
- package/src/service/curd/CurdMixService.ts +7 -2
- package/src/service/curd/CurdProService.ts +62 -3
- package/src/service/curd/README.md +1100 -0
- package/src/service/curd/fixCfgModel.ts +1 -2
- package/src/service/curd/fixSoftDelete.ts +38 -16
- package/src/service/flow/FlowConfigService.ts +1 -1
- 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;
|