midway-fatcms 0.0.7 → 0.0.9
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/01-quick-start.md +231 -0
- package/.qoder/skills/midway-fatcms/02-crud-quick.md +375 -0
- package/.qoder/skills/midway-fatcms/03-crud-sharding.md +489 -0
- package/.qoder/skills/midway-fatcms/04-condition-operators.md +93 -0
- package/.qoder/skills/midway-fatcms/05-configuration.md +290 -0
- package/.qoder/skills/midway-fatcms/06-builtin-functions.md +241 -0
- package/.qoder/skills/midway-fatcms/07-examples.md +504 -0
- package/.qoder/skills/midway-fatcms/SKILL.md +96 -0
- package/README.md +9 -9
- package/dist/configuration.d.ts +10 -0
- package/dist/configuration.js +26 -0
- package/dist/controller/base/BaseApiController.d.ts +1 -2
- package/dist/controller/base/BaseApiController.js +0 -4
- package/dist/controller/gateway/DocGatewayController.js +1 -1
- package/dist/controller/helpers.controller.d.ts +6 -0
- package/dist/controller/helpers.controller.js +19 -0
- package/dist/controller/manage/FlowConfigManageApi.js +4 -2
- package/dist/controller/manage/SysConfigMangeApi.js +6 -1
- package/dist/controller/manage/UserAccountManageApi.js +7 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/libs/crud-pro/CrudPro.d.ts +51 -3
- package/dist/libs/crud-pro/CrudPro.js +111 -4
- package/dist/libs/crud-pro/exceptions.d.ts +7 -0
- package/dist/libs/crud-pro/exceptions.js +7 -0
- package/dist/libs/crud-pro/interfaces.d.ts +83 -12
- package/dist/libs/crud-pro/models/CrudResult.d.ts +116 -0
- package/dist/libs/crud-pro/models/CrudResult.js +126 -0
- package/dist/libs/crud-pro/models/RequestModel.d.ts +2 -2
- package/dist/libs/crud-pro/models/ServiceHub.d.ts +2 -0
- package/dist/libs/crud-pro/services/CrudProDataTypeConvertService.d.ts +70 -2
- package/dist/libs/crud-pro/services/CrudProDataTypeConvertService.js +205 -13
- package/dist/libs/crud-pro/services/CrudProExecuteSqlService.js +36 -2
- package/dist/libs/crud-pro/services/CrudProGenSqlCondition.js +8 -4
- package/dist/libs/crud-pro/services/CrudProTableMetaService.d.ts +36 -0
- package/dist/libs/crud-pro/services/CrudProTableMetaService.js +97 -4
- package/dist/libs/crud-pro/services/CurdProServiceHub.d.ts +2 -0
- package/dist/libs/crud-pro/services/CurdProServiceHub.js +6 -0
- package/dist/libs/crud-pro-quick/CrudProQuick.d.ts +382 -0
- package/dist/libs/crud-pro-quick/CrudProQuick.js +689 -0
- package/dist/libs/crud-pro-quick/fixSoftDelete.d.ts +30 -0
- package/dist/{service/curd → libs/crud-pro-quick}/fixSoftDelete.js +3 -6
- package/dist/libs/crud-pro-quick/index.d.ts +36 -0
- package/dist/libs/crud-pro-quick/index.js +49 -0
- package/dist/libs/crud-pro-quick/models.d.ts +33 -0
- package/dist/libs/crud-pro-quick/models.js +2 -0
- package/dist/libs/crud-sharding/ShardingBase.d.ts +78 -0
- package/dist/libs/crud-sharding/ShardingBase.js +179 -0
- package/dist/libs/crud-sharding/ShardingByCustomCrud.d.ts +35 -0
- package/dist/libs/crud-sharding/ShardingByCustomCrud.js +297 -0
- package/dist/libs/crud-sharding/ShardingByHashCrud.d.ts +38 -0
- package/dist/libs/crud-sharding/ShardingByHashCrud.js +86 -0
- package/dist/libs/crud-sharding/ShardingByKeyCrud.d.ts +39 -0
- package/dist/libs/crud-sharding/ShardingByKeyCrud.js +74 -0
- package/dist/libs/crud-sharding/ShardingByTimeCrud.d.ts +66 -0
- package/dist/libs/crud-sharding/ShardingByTimeCrud.js +524 -0
- package/dist/libs/crud-sharding/ShardingConfig.d.ts +25 -10
- package/dist/libs/crud-sharding/ShardingConfig.js +5 -5
- package/dist/libs/crud-sharding/ShardingMerger.d.ts +10 -18
- package/dist/libs/crud-sharding/ShardingMerger.js +27 -44
- package/dist/libs/crud-sharding/ShardingResult.d.ts +33 -0
- package/dist/libs/crud-sharding/ShardingResult.js +16 -0
- package/dist/libs/crud-sharding/ShardingTableCreator.d.ts +21 -4
- package/dist/libs/crud-sharding/ShardingTableCreator.js +193 -59
- package/dist/libs/crud-sharding/ShardingUtils.d.ts +48 -0
- package/dist/libs/crud-sharding/ShardingUtils.js +122 -1
- package/dist/libs/crud-sharding/TIME_COLUMN_CLEAN_SPEC.md +488 -0
- package/dist/libs/crud-sharding/index.d.ts +13 -15
- package/dist/libs/crud-sharding/index.js +33 -17
- package/dist/models/RedisKeys.d.ts +1 -0
- package/dist/models/RedisKeys.js +1 -0
- package/dist/models/bizmodels.d.ts +2 -6
- package/dist/service/SysAppService.d.ts +2 -2
- package/dist/service/SysAppService.js +16 -5
- package/dist/service/SysConfigService.d.ts +1 -1
- package/dist/service/SysConfigService.js +7 -2
- package/dist/service/SysDictDataService.js +14 -4
- package/dist/service/SysMenuService.js +7 -2
- package/dist/service/TableMetaCacheRedisSubscriber.d.ts +31 -0
- package/dist/service/TableMetaCacheRedisSubscriber.js +98 -0
- package/dist/service/curd/CurdMixService.d.ts +6 -4
- package/dist/service/curd/CurdMixService.js +16 -2
- package/dist/service/curd/CurdProService.d.ts +149 -29
- package/dist/service/curd/CurdProService.js +157 -38
- package/dist/service/flow/FlowConfigService.js +7 -2
- package/dist/service/flow/FlowInstanceCrudService.js +22 -19
- package/package.json +1 -1
- package/src/configuration.ts +27 -0
- package/src/controller/base/BaseApiController.ts +0 -5
- package/src/controller/gateway/DocGatewayController.ts +1 -1
- package/src/controller/helpers.controller.ts +15 -0
- package/src/controller/manage/CrudStandardDesignApi.ts +4 -3
- package/src/controller/manage/FlowConfigManageApi.ts +4 -2
- package/src/controller/manage/SysConfigMangeApi.ts +6 -1
- package/src/controller/manage/UserAccountManageApi.ts +7 -2
- package/src/index.ts +2 -2
- package/src/libs/crud-pro/CrudPro.ts +134 -7
- package/src/libs/crud-pro/exceptions.ts +8 -0
- package/src/libs/crud-pro/interfaces.ts +111 -15
- package/src/libs/crud-pro/models/CrudResult.ts +178 -0
- package/src/libs/crud-pro/models/RequestModel.ts +2 -2
- package/src/libs/crud-pro/models/ServiceHub.ts +4 -0
- package/src/libs/crud-pro/services/CrudProDataTypeConvertService.ts +238 -15
- package/src/libs/crud-pro/services/CrudProExecuteSqlService.ts +41 -2
- package/src/libs/crud-pro/services/CrudProGenSqlCondition.ts +11 -7
- package/src/libs/crud-pro/services/CrudProTableMetaService.ts +110 -3
- package/src/libs/crud-pro/services/CurdProServiceHub.ts +8 -0
- package/src/libs/crud-pro-quick/CrudProQuick.ts +782 -0
- package/src/{service/curd → libs/crud-pro-quick}/fixSoftDelete.ts +23 -13
- package/src/libs/crud-pro-quick/index.ts +52 -0
- package/src/libs/crud-pro-quick/models.ts +35 -0
- package/src/libs/crud-sharding/ShardingBase.ts +256 -0
- package/src/libs/crud-sharding/ShardingByCustomCrud.ts +329 -0
- package/src/libs/crud-sharding/ShardingByHashCrud.ts +111 -0
- package/src/libs/crud-sharding/ShardingByKeyCrud.ts +97 -0
- package/src/libs/crud-sharding/ShardingByTimeCrud.ts +628 -0
- package/src/libs/crud-sharding/ShardingConfig.ts +28 -10
- package/src/libs/crud-sharding/ShardingMerger.ts +35 -63
- package/src/libs/crud-sharding/ShardingResult.ts +29 -0
- package/src/libs/crud-sharding/ShardingTableCreator.ts +214 -71
- package/src/libs/crud-sharding/ShardingUtils.ts +137 -0
- package/src/libs/crud-sharding/TIME_COLUMN_CLEAN_SPEC.md +488 -0
- package/src/libs/crud-sharding/index.ts +30 -16
- package/src/models/RedisKeys.ts +1 -0
- package/src/models/bizmodels.ts +4 -7
- package/src/service/SysAppService.ts +18 -7
- package/src/service/SysConfigService.ts +8 -3
- package/src/service/SysDictDataService.ts +14 -4
- package/src/service/SysMenuService.ts +7 -2
- package/src/service/TableMetaCacheRedisSubscriber.ts +105 -0
- package/src/service/crudstd/CrudStdService.ts +2 -2
- package/src/service/curd/CurdMixService.ts +26 -5
- package/src/service/curd/CurdProService.ts +186 -45
- package/src/service/flow/FlowConfigService.ts +7 -2
- package/src/service/flow/FlowInstanceCrudService.ts +23 -20
- package/.qoder/skills/midway-fatcms-crud/SKILL.md +0 -375
- package/.qoder/skills/midway-fatcms-crud/examples.md +0 -990
- package/.qoder/skills/midway-fatcms-crud/reference.md +0 -568
- package/dist/libs/crud-pro/README.md +0 -809
- package/dist/libs/crud-pro/README_FUNC.md +0 -193
- package/dist/libs/crud-sharding/ROUTING_LOGIC.md +0 -944
- package/dist/libs/crud-sharding/ShardingCrudPro.d.ts +0 -363
- package/dist/libs/crud-sharding/ShardingCrudPro.js +0 -675
- package/dist/libs/crud-sharding/ShardingRouter.d.ts +0 -69
- package/dist/libs/crud-sharding/ShardingRouter.js +0 -377
- package/dist/models/StandardColumns.d.ts +0 -71
- package/dist/models/StandardColumns.js +0 -28
- package/dist/service/curd/CrudProQuick.d.ts +0 -190
- package/dist/service/curd/CrudProQuick.js +0 -319
- package/dist/service/curd/README.md +0 -1100
- package/dist/service/curd/fixSoftDelete.d.ts +0 -20
- package/src/libs/crud-pro/README.md +0 -809
- package/src/libs/crud-pro/README_FUNC.md +0 -193
- package/src/libs/crud-sharding/ROUTING_LOGIC.md +0 -944
- package/src/libs/crud-sharding/ShardingCrudPro.ts +0 -835
- package/src/libs/crud-sharding/ShardingRouter.ts +0 -512
- package/src/models/StandardColumns.ts +0 -76
- package/src/service/curd/CrudProQuick.ts +0 -360
- package/src/service/curd/README.md +0 -1100
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CrudPro } from '../../libs/crud-pro/CrudPro';
|
|
2
2
|
import { IRequestModel, IRequestCfgModel } from '../../libs/crud-pro/interfaces';
|
|
3
|
+
import { ExecuteContext } from '../../libs/crud-pro/models/ExecuteContext';
|
|
3
4
|
import { ShardingCountCache } from './ShardingCountCache';
|
|
4
5
|
/**
|
|
5
6
|
* 分页查询结果
|
|
@@ -7,6 +8,14 @@ import { ShardingCountCache } from './ShardingCountCache';
|
|
|
7
8
|
export interface IShardingPageQueryResult {
|
|
8
9
|
rows: any[];
|
|
9
10
|
total_count: number;
|
|
11
|
+
lastCtx: ExecuteContext | null;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 列表查询结果
|
|
15
|
+
*/
|
|
16
|
+
export interface IShardingListQueryResult {
|
|
17
|
+
rows: any[];
|
|
18
|
+
lastCtx: ExecuteContext | null;
|
|
10
19
|
}
|
|
11
20
|
/**
|
|
12
21
|
* 分表查询结果合并器
|
|
@@ -65,7 +74,7 @@ export declare class ShardingMerger {
|
|
|
65
74
|
* @param maxRows 最大返回行数(默认 10000,防止 OOM)
|
|
66
75
|
* @returns 数据列表
|
|
67
76
|
*/
|
|
68
|
-
mergeQuery(crudPro: CrudPro, tables: string[], reqJson: IRequestModel, cfgJson: IRequestCfgModel, maxRows?: number): Promise<
|
|
77
|
+
mergeQuery(crudPro: CrudPro, tables: string[], reqJson: IRequestModel, cfgJson: IRequestCfgModel, maxRows?: number): Promise<IShardingListQueryResult>;
|
|
69
78
|
/**
|
|
70
79
|
* 根据各表数量和分页偏移,直接定位目标表
|
|
71
80
|
*
|
|
@@ -95,23 +104,6 @@ export declare class ShardingMerger {
|
|
|
95
104
|
* 支持 COUNT 缓存:历史时间分表使用缓存,当前表实时查询
|
|
96
105
|
*/
|
|
97
106
|
private queryCountSafe;
|
|
98
|
-
/**
|
|
99
|
-
* 安全查询数据行(表不存在时返回空数组)
|
|
100
|
-
*/
|
|
101
|
-
private queryRowsSafe;
|
|
102
|
-
/**
|
|
103
|
-
* 安全查询数据行(支持表内偏移,表不存在时返回空数组)
|
|
104
|
-
*
|
|
105
|
-
* 用于分页定位后,直接从某张表指定偏移处取数据
|
|
106
|
-
*
|
|
107
|
-
* @param crudPro CrudPro 实例
|
|
108
|
-
* @param table 分表名
|
|
109
|
-
* @param reqJson 请求参数
|
|
110
|
-
* @param cfgJson 配置
|
|
111
|
-
* @param innerOffset 表内偏移(跳过前 N 条)
|
|
112
|
-
* @param innerLimit 取多少条
|
|
113
|
-
*/
|
|
114
|
-
private queryRowsSafeWithOffset;
|
|
115
107
|
/**
|
|
116
108
|
* 执行 COUNT 查询
|
|
117
109
|
*/
|
|
@@ -46,25 +46,36 @@ class ShardingMerger {
|
|
|
46
46
|
const { pageNo = 1, pageSize = 10 } = reqJson;
|
|
47
47
|
const offset = (pageNo - 1) * pageSize;
|
|
48
48
|
const targetEnd = offset + pageSize; // 需要取到第几条
|
|
49
|
-
// 1.
|
|
50
|
-
const
|
|
51
|
-
const
|
|
49
|
+
// 1. 串行查询所有分表的记录数
|
|
50
|
+
const counts = [];
|
|
51
|
+
for (const table of tables) {
|
|
52
|
+
const count = await this.queryCountSafe(crudPro, table, reqJson, cfgJson);
|
|
53
|
+
counts.push(count);
|
|
54
|
+
}
|
|
52
55
|
const totalCount = counts.reduce((sum, c) => sum + c, 0);
|
|
53
56
|
// 总数为0,直接返回
|
|
54
57
|
if (totalCount === 0) {
|
|
55
|
-
return { rows: [], total_count: 0 };
|
|
58
|
+
return { rows: [], total_count: 0, lastCtx: null };
|
|
56
59
|
}
|
|
57
60
|
// 2. 根据累计数量直接定位目标表
|
|
58
61
|
const targetTables = this.locateTargetTables(tables, counts, offset, targetEnd);
|
|
59
62
|
// 3. 只查询包含目标数据的分表
|
|
60
63
|
let allRows = [];
|
|
64
|
+
let lastCtx = null;
|
|
61
65
|
for (const target of targetTables) {
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
try {
|
|
67
|
+
const ctx = await this.executeQueryWithOffset(crudPro, target.table, reqJson, cfgJson, target.innerOffset, target.innerLimit);
|
|
68
|
+
lastCtx = ctx;
|
|
69
|
+
allRows = allRows.concat(ctx.getResRows() || []);
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
// 表不存在或其他错误,跳过
|
|
73
|
+
}
|
|
64
74
|
}
|
|
65
75
|
return {
|
|
66
76
|
rows: allRows,
|
|
67
77
|
total_count: totalCount,
|
|
78
|
+
lastCtx,
|
|
68
79
|
};
|
|
69
80
|
}
|
|
70
81
|
/**
|
|
@@ -88,20 +99,27 @@ class ShardingMerger {
|
|
|
88
99
|
async mergeQuery(crudPro, tables, reqJson, cfgJson, maxRows = 10000) {
|
|
89
100
|
// 串行查询分表,按表顺序拼接,达到上限即停
|
|
90
101
|
let allRows = [];
|
|
102
|
+
let lastCtx = null;
|
|
91
103
|
for (const table of tables) {
|
|
92
104
|
const needMore = maxRows - allRows.length;
|
|
93
105
|
if (needMore <= 0) {
|
|
94
106
|
break;
|
|
95
107
|
}
|
|
96
|
-
|
|
97
|
-
|
|
108
|
+
try {
|
|
109
|
+
const ctx = await this.executeQuery(crudPro, table, reqJson, cfgJson, needMore);
|
|
110
|
+
lastCtx = ctx;
|
|
111
|
+
allRows = allRows.concat(ctx.getResRows() || []);
|
|
112
|
+
}
|
|
113
|
+
catch (e) {
|
|
114
|
+
// 表不存在或其他错误,跳过
|
|
115
|
+
}
|
|
98
116
|
// 达到上限后停止,不再查询后续表
|
|
99
117
|
if (allRows.length >= maxRows) {
|
|
100
118
|
allRows = allRows.slice(0, maxRows);
|
|
101
119
|
break;
|
|
102
120
|
}
|
|
103
121
|
}
|
|
104
|
-
return allRows;
|
|
122
|
+
return { rows: allRows, lastCtx };
|
|
105
123
|
}
|
|
106
124
|
// ============ 私有方法:分页定位 ============
|
|
107
125
|
/**
|
|
@@ -192,41 +210,6 @@ class ShardingMerger {
|
|
|
192
210
|
return 0;
|
|
193
211
|
}
|
|
194
212
|
}
|
|
195
|
-
/**
|
|
196
|
-
* 安全查询数据行(表不存在时返回空数组)
|
|
197
|
-
*/
|
|
198
|
-
async queryRowsSafe(crudPro, table, reqJson, cfgJson, limit) {
|
|
199
|
-
try {
|
|
200
|
-
const ctx = await this.executeQuery(crudPro, table, reqJson, cfgJson, limit);
|
|
201
|
-
return ctx.getResRows() || [];
|
|
202
|
-
}
|
|
203
|
-
catch (e) {
|
|
204
|
-
// 表不存在或其他错误,返回空数组
|
|
205
|
-
return [];
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
/**
|
|
209
|
-
* 安全查询数据行(支持表内偏移,表不存在时返回空数组)
|
|
210
|
-
*
|
|
211
|
-
* 用于分页定位后,直接从某张表指定偏移处取数据
|
|
212
|
-
*
|
|
213
|
-
* @param crudPro CrudPro 实例
|
|
214
|
-
* @param table 分表名
|
|
215
|
-
* @param reqJson 请求参数
|
|
216
|
-
* @param cfgJson 配置
|
|
217
|
-
* @param innerOffset 表内偏移(跳过前 N 条)
|
|
218
|
-
* @param innerLimit 取多少条
|
|
219
|
-
*/
|
|
220
|
-
async queryRowsSafeWithOffset(crudPro, table, reqJson, cfgJson, innerOffset, innerLimit) {
|
|
221
|
-
try {
|
|
222
|
-
const ctx = await this.executeQueryWithOffset(crudPro, table, reqJson, cfgJson, innerOffset, innerLimit);
|
|
223
|
-
return ctx.getResRows() || [];
|
|
224
|
-
}
|
|
225
|
-
catch (e) {
|
|
226
|
-
// 表不存在或其他错误,返回空数组
|
|
227
|
-
return [];
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
213
|
/**
|
|
231
214
|
* 执行 COUNT 查询
|
|
232
215
|
*/
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ExecuteContext } from '../../libs/crud-pro/models/ExecuteContext';
|
|
2
|
+
import { CrudResultBase } from '../../libs/crud-pro/models/CrudResult';
|
|
3
|
+
/** 分表批量插入结果 */
|
|
4
|
+
declare class ShardingBatchInsertResult extends CrudResultBase {
|
|
5
|
+
readonly totalAffected: number;
|
|
6
|
+
readonly tableResults: Array<{
|
|
7
|
+
table: string;
|
|
8
|
+
affected: number;
|
|
9
|
+
rowCount: number;
|
|
10
|
+
}>;
|
|
11
|
+
readonly tableCount: number;
|
|
12
|
+
readonly success: boolean;
|
|
13
|
+
readonly errors: Array<{
|
|
14
|
+
table: string;
|
|
15
|
+
error: Error;
|
|
16
|
+
}>;
|
|
17
|
+
constructor(data: {
|
|
18
|
+
totalAffected: number;
|
|
19
|
+
tableResults: Array<{
|
|
20
|
+
table: string;
|
|
21
|
+
affected: number;
|
|
22
|
+
rowCount: number;
|
|
23
|
+
}>;
|
|
24
|
+
tableCount: number;
|
|
25
|
+
success: boolean;
|
|
26
|
+
errors: Array<{
|
|
27
|
+
table: string;
|
|
28
|
+
error: Error;
|
|
29
|
+
}>;
|
|
30
|
+
lastContext: ExecuteContext;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
export { ShardingBatchInsertResult };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ShardingBatchInsertResult = void 0;
|
|
4
|
+
const CrudResult_1 = require("../../libs/crud-pro/models/CrudResult");
|
|
5
|
+
/** 分表批量插入结果 */
|
|
6
|
+
class ShardingBatchInsertResult extends CrudResult_1.CrudResultBase {
|
|
7
|
+
constructor(data) {
|
|
8
|
+
super(data.lastContext);
|
|
9
|
+
this.totalAffected = data.totalAffected;
|
|
10
|
+
this.tableResults = data.tableResults;
|
|
11
|
+
this.tableCount = data.tableCount;
|
|
12
|
+
this.success = data.success;
|
|
13
|
+
this.errors = data.errors;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
exports.ShardingBatchInsertResult = ShardingBatchInsertResult;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { CrudPro } from '../../libs/crud-pro/CrudPro';
|
|
2
1
|
import { SqlDbType } from '../../libs/crud-pro/models/keys';
|
|
3
|
-
import { IShardingConfig, IShardingTableCreateOptions, IShardingTableCreateResult } from './ShardingConfig';
|
|
2
|
+
import { CrudProFactory, IShardingConfig, IShardingTableCreateOptions, IShardingTableCreateResult } from './ShardingConfig';
|
|
4
3
|
/**
|
|
5
4
|
* 分表自动创建器
|
|
6
5
|
*
|
|
@@ -14,9 +13,9 @@ import { IShardingConfig, IShardingTableCreateOptions, IShardingTableCreateResul
|
|
|
14
13
|
* });
|
|
15
14
|
*/
|
|
16
15
|
export declare class ShardingTableCreator {
|
|
17
|
-
private readonly
|
|
16
|
+
private readonly crudProFactory;
|
|
18
17
|
private readonly config;
|
|
19
|
-
constructor(
|
|
18
|
+
constructor(crudProFactory: CrudProFactory, config: IShardingConfig);
|
|
20
19
|
/**
|
|
21
20
|
* 校验表名格式,防止 SQL 注入
|
|
22
21
|
* 只允许合法的数据库标识符:以字母或下划线开头,后续只能包含字母数字下划线
|
|
@@ -34,6 +33,12 @@ export declare class ShardingTableCreator {
|
|
|
34
33
|
sqlDatabase: string;
|
|
35
34
|
sqlDbType: SqlDbType;
|
|
36
35
|
}, options?: IShardingTableCreateOptions): Promise<IShardingTableCreateResult>;
|
|
36
|
+
/**
|
|
37
|
+
* 检查表是否已存在
|
|
38
|
+
* 复用 CrudPro.getAllTableInfos 接口,使用缓存提升性能
|
|
39
|
+
* DDL 操作本身具有幂等性(CREATE TABLE IF NOT EXISTS),缓存短暂延迟可接受
|
|
40
|
+
*/
|
|
41
|
+
private checkTableExists;
|
|
37
42
|
/**
|
|
38
43
|
* 获取基表列信息
|
|
39
44
|
*/
|
|
@@ -102,6 +107,10 @@ export declare class ShardingTableCreator {
|
|
|
102
107
|
* 执行建表 SQL
|
|
103
108
|
*/
|
|
104
109
|
private executeCreateSql;
|
|
110
|
+
/**
|
|
111
|
+
* 判断是否为"已存在"类错误(表已存在、索引已存在等)
|
|
112
|
+
*/
|
|
113
|
+
private isDuplicateError;
|
|
105
114
|
/**
|
|
106
115
|
* 复制索引(可选功能)
|
|
107
116
|
*
|
|
@@ -109,6 +118,14 @@ export declare class ShardingTableCreator {
|
|
|
109
118
|
* 注意:主键索引已在建表时创建,此处只复制普通索引。
|
|
110
119
|
*/
|
|
111
120
|
private copyIndexes;
|
|
121
|
+
/**
|
|
122
|
+
* 获取目标表已存在的所有索引名
|
|
123
|
+
*/
|
|
124
|
+
private getExistingIndexNames;
|
|
125
|
+
/**
|
|
126
|
+
* 从 CREATE INDEX 语句中提取索引名
|
|
127
|
+
*/
|
|
128
|
+
private extractIndexName;
|
|
112
129
|
/**
|
|
113
130
|
* 在索引 SQL 中替换表名
|
|
114
131
|
*
|
|
@@ -15,8 +15,8 @@ const keys_1 = require("../../libs/crud-pro/models/keys");
|
|
|
15
15
|
* });
|
|
16
16
|
*/
|
|
17
17
|
class ShardingTableCreator {
|
|
18
|
-
constructor(
|
|
19
|
-
this.
|
|
18
|
+
constructor(crudProFactory, config) {
|
|
19
|
+
this.crudProFactory = crudProFactory;
|
|
20
20
|
this.config = config;
|
|
21
21
|
this.validateIdentifier(this.config.baseTable, 'baseTable');
|
|
22
22
|
}
|
|
@@ -46,28 +46,50 @@ class ShardingTableCreator {
|
|
|
46
46
|
// 校验分表名格式,防止 SQL 注入
|
|
47
47
|
this.validateIdentifier(tableName, 'tableName');
|
|
48
48
|
const { copyIndexes = false } = options;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
49
|
+
// 0. 先检查表是否已存在
|
|
50
|
+
const tableExists = await this.checkTableExists(tableName, baseCfg);
|
|
51
|
+
if (tableExists) {
|
|
52
|
+
// 表已存在,跳过创建和索引复制
|
|
53
|
+
return { success: true, tableName, createSql: '' };
|
|
54
|
+
}
|
|
55
|
+
// 1. 获取基表元信息
|
|
56
|
+
const tableMeta = await this.getBaseTableMeta(baseCfg);
|
|
57
|
+
if (!tableMeta || !tableMeta.columnDetails || tableMeta.columnDetails.length === 0) {
|
|
58
|
+
throw new Error(`[ShardingTableCreator] 无法获取基表 ${this.config.baseTable} 的结构信息`);
|
|
59
|
+
}
|
|
60
|
+
// 2. 生成建表 SQL
|
|
61
|
+
const createSql = this.generateCreateTableSql(tableName, tableMeta.columnDetails, baseCfg.sqlDbType, options);
|
|
62
|
+
// 3. 执行建表
|
|
63
|
+
await this.executeCreateSql(baseCfg, createSql);
|
|
64
|
+
// 4. 可选:复制索引(只在新建表时执行)
|
|
65
|
+
if (copyIndexes) {
|
|
66
|
+
try {
|
|
61
67
|
await this.copyIndexes(tableName, baseCfg);
|
|
62
68
|
}
|
|
63
|
-
|
|
69
|
+
catch (e) {
|
|
70
|
+
console.error("copyIndexes", e);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return { success: true, tableName, createSql };
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* 检查表是否已存在
|
|
77
|
+
* 复用 CrudPro.getAllTableInfos 接口,使用缓存提升性能
|
|
78
|
+
* DDL 操作本身具有幂等性(CREATE TABLE IF NOT EXISTS),缓存短暂延迟可接受
|
|
79
|
+
*/
|
|
80
|
+
async checkTableExists(tableName, baseCfg) {
|
|
81
|
+
try {
|
|
82
|
+
const { tables } = await this.crudProFactory().getAllTableInfos({
|
|
83
|
+
sqlDatabase: baseCfg.sqlDatabase,
|
|
84
|
+
sqlDbType: baseCfg.sqlDbType,
|
|
85
|
+
}, { skipCache: false } // 使用缓存,避免每次建表都直查数据库
|
|
86
|
+
);
|
|
87
|
+
return tables.some(t => t.name === tableName);
|
|
64
88
|
}
|
|
65
89
|
catch (error) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
70
|
-
};
|
|
90
|
+
console.warn('[ShardingTableCreator] 检查表存在性失败:', error);
|
|
91
|
+
// 检查失败时,假设表不存在,继续尝试创建
|
|
92
|
+
return false;
|
|
71
93
|
}
|
|
72
94
|
}
|
|
73
95
|
/**
|
|
@@ -107,20 +129,19 @@ class ShardingTableCreator {
|
|
|
107
129
|
*/
|
|
108
130
|
async loadMySQLColumns(baseCfg) {
|
|
109
131
|
const sql = `
|
|
110
|
-
SELECT
|
|
111
|
-
COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_KEY,
|
|
132
|
+
SELECT
|
|
133
|
+
COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_KEY,
|
|
112
134
|
COLUMN_DEFAULT, COLUMN_COMMENT, EXTRA
|
|
113
135
|
FROM information_schema.COLUMNS
|
|
114
136
|
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '${this.config.baseTable}'
|
|
115
137
|
ORDER BY ORDINAL_POSITION
|
|
116
138
|
`.trim();
|
|
117
|
-
const
|
|
139
|
+
const rows = await this.crudProFactory().executeSQL({
|
|
118
140
|
isNativeSQL: true,
|
|
119
141
|
sqlDatabase: baseCfg.sqlDatabase,
|
|
120
142
|
sqlDbType: baseCfg.sqlDbType,
|
|
121
143
|
executeSql: sql,
|
|
122
|
-
});
|
|
123
|
-
const rows = (result === null || result === void 0 ? void 0 : result.rows) || [];
|
|
144
|
+
}) || [];
|
|
124
145
|
return rows.map((row) => {
|
|
125
146
|
var _a;
|
|
126
147
|
return ({
|
|
@@ -150,13 +171,12 @@ class ShardingTableCreator {
|
|
|
150
171
|
WHERE table_schema = '${schema}' AND table_name = '${this.config.baseTable}'
|
|
151
172
|
ORDER BY ordinal_position
|
|
152
173
|
`.trim();
|
|
153
|
-
const
|
|
174
|
+
const rows = await this.crudProFactory().executeSQL({
|
|
154
175
|
isNativeSQL: true,
|
|
155
176
|
sqlDatabase: baseCfg.sqlDatabase,
|
|
156
177
|
sqlDbType: baseCfg.sqlDbType,
|
|
157
178
|
executeSql: sql,
|
|
158
|
-
});
|
|
159
|
-
const rows = (result === null || result === void 0 ? void 0 : result.rows) || [];
|
|
179
|
+
}) || [];
|
|
160
180
|
// 获取主键列
|
|
161
181
|
const pkColumns = await this.getPostgreSQLPrimaryKeys(baseCfg, schema);
|
|
162
182
|
return rows.map((row) => {
|
|
@@ -186,13 +206,13 @@ class ShardingTableCreator {
|
|
|
186
206
|
AND c.relname = '${this.config.baseTable}'
|
|
187
207
|
AND i.indisprimary
|
|
188
208
|
`.trim();
|
|
189
|
-
const result = await this.
|
|
209
|
+
const result = await this.crudProFactory().executeSQL({
|
|
190
210
|
isNativeSQL: true,
|
|
191
211
|
sqlDatabase: baseCfg.sqlDatabase,
|
|
192
212
|
sqlDbType: baseCfg.sqlDbType,
|
|
193
213
|
executeSql: sql,
|
|
194
|
-
});
|
|
195
|
-
return
|
|
214
|
+
}) || [];
|
|
215
|
+
return result.map((row) => row.attname);
|
|
196
216
|
}
|
|
197
217
|
/**
|
|
198
218
|
* 加载 SQL Server 表列信息
|
|
@@ -212,13 +232,12 @@ class ShardingTableCreator {
|
|
|
212
232
|
WHERE c.object_id = OBJECT_ID('${this.config.baseTable}')
|
|
213
233
|
ORDER BY c.column_id
|
|
214
234
|
`.trim();
|
|
215
|
-
const
|
|
235
|
+
const rows = await this.crudProFactory().executeSQL({
|
|
216
236
|
isNativeSQL: true,
|
|
217
237
|
sqlDatabase: baseCfg.sqlDatabase,
|
|
218
238
|
sqlDbType: baseCfg.sqlDbType,
|
|
219
239
|
executeSql: sql,
|
|
220
|
-
});
|
|
221
|
-
const rows = (result === null || result === void 0 ? void 0 : result.rows) || [];
|
|
240
|
+
}) || [];
|
|
222
241
|
// 获取主键列
|
|
223
242
|
const pkColumns = await this.getSQLServerPrimaryKeys(baseCfg);
|
|
224
243
|
return rows.map((row) => {
|
|
@@ -248,13 +267,13 @@ class ShardingTableCreator {
|
|
|
248
267
|
WHERE i.object_id = OBJECT_ID('${this.config.baseTable}')
|
|
249
268
|
AND i.is_primary_key = 1
|
|
250
269
|
`.trim();
|
|
251
|
-
const result = await this.
|
|
270
|
+
const result = await this.crudProFactory().executeSQL({
|
|
252
271
|
isNativeSQL: true,
|
|
253
272
|
sqlDatabase: baseCfg.sqlDatabase,
|
|
254
273
|
sqlDbType: baseCfg.sqlDbType,
|
|
255
274
|
executeSql: sql,
|
|
256
|
-
});
|
|
257
|
-
return
|
|
275
|
+
}) || [];
|
|
276
|
+
return result.map((row) => row.name);
|
|
258
277
|
}
|
|
259
278
|
/**
|
|
260
279
|
* 生成建表 SQL
|
|
@@ -560,14 +579,44 @@ class ShardingTableCreator {
|
|
|
560
579
|
/**
|
|
561
580
|
* 执行建表 SQL
|
|
562
581
|
*/
|
|
563
|
-
async executeCreateSql(baseCfg, createSql) {
|
|
582
|
+
async executeCreateSql(baseCfg, createSql, options = {}) {
|
|
564
583
|
const sqlCfgModel = {
|
|
565
584
|
isNativeSQL: true,
|
|
566
585
|
sqlDatabase: baseCfg.sqlDatabase,
|
|
567
586
|
sqlDbType: baseCfg.sqlDbType,
|
|
568
587
|
executeSql: createSql,
|
|
569
588
|
};
|
|
570
|
-
|
|
589
|
+
try {
|
|
590
|
+
await this.crudProFactory().executeSQL(sqlCfgModel);
|
|
591
|
+
}
|
|
592
|
+
catch (error) {
|
|
593
|
+
// 静默处理 DDL "已存在"类错误(表已存在、索引已存在等)
|
|
594
|
+
if (options.silentDuplicate && this.isDuplicateError(error)) {
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
throw error;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* 判断是否为"已存在"类错误(表已存在、索引已存在等)
|
|
602
|
+
*/
|
|
603
|
+
isDuplicateError(error) {
|
|
604
|
+
if (!error)
|
|
605
|
+
return false;
|
|
606
|
+
const code = error.code || error.errno;
|
|
607
|
+
const message = error.message || '';
|
|
608
|
+
// MySQL: ER_TABLE_EXISTS_ERROR (1050), ER_DUP_KEYNAME (1061)
|
|
609
|
+
// PostgreSQL: 42P07 (duplicate_table), 42P06 (duplicate_schema)
|
|
610
|
+
// SQL Server: 错误消息包含 "already exists"
|
|
611
|
+
return code === 'ER_TABLE_EXISTS_ERROR' ||
|
|
612
|
+
code === 'ER_DUP_KEYNAME' ||
|
|
613
|
+
code === 1050 ||
|
|
614
|
+
code === 1061 ||
|
|
615
|
+
code === '42P07' ||
|
|
616
|
+
code === '42P06' ||
|
|
617
|
+
message.includes('already exists') ||
|
|
618
|
+
message.includes('Duplicate') ||
|
|
619
|
+
message.includes('Table') && message.includes('already exists');
|
|
571
620
|
}
|
|
572
621
|
/**
|
|
573
622
|
* 复制索引(可选功能)
|
|
@@ -576,23 +625,111 @@ class ShardingTableCreator {
|
|
|
576
625
|
* 注意:主键索引已在建表时创建,此处只复制普通索引。
|
|
577
626
|
*/
|
|
578
627
|
async copyIndexes(tableName, baseCfg) {
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
628
|
+
const indexDefs = await this.getIndexDefinitions(baseCfg);
|
|
629
|
+
// 过滤掉主键索引和空定义
|
|
630
|
+
const validIndexDefs = indexDefs.filter(def => !def.isPrimary && def.createSql);
|
|
631
|
+
if (validIndexDefs.length === 0) {
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
// 一次性获取目标表已存在的所有索引名
|
|
635
|
+
const existingIndexNames = await this.getExistingIndexNames(tableName, baseCfg);
|
|
636
|
+
// 用于去重的 Set,防止同一请求中重复创建同名索引
|
|
637
|
+
const createdIndexNames = new Set();
|
|
638
|
+
for (const indexDef of validIndexDefs) {
|
|
639
|
+
try {
|
|
640
|
+
// 替换表名
|
|
586
641
|
const indexSql = this.replaceTableNameInIndexSql(indexDef.createSql, tableName, baseCfg.sqlDbType);
|
|
587
|
-
//
|
|
642
|
+
// 重命名索引
|
|
588
643
|
const renamedSql = this.renameIndex(indexSql, tableName, baseCfg.sqlDbType);
|
|
589
|
-
|
|
644
|
+
// 提取新索引名
|
|
645
|
+
const newIndexName = this.extractIndexName(renamedSql, baseCfg.sqlDbType);
|
|
646
|
+
// 如果索引已存在(数据库中或本次请求已创建),跳过创建
|
|
647
|
+
if (existingIndexNames.has(newIndexName) || createdIndexNames.has(newIndexName)) {
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
650
|
+
console.info('[ShardingTableCreator] 准备创建索引 =>' + newIndexName + " ===> renamedSql" + renamedSql);
|
|
651
|
+
// 使用 silentDuplicate 静默处理"索引已存在"错误
|
|
652
|
+
await this.executeCreateSql(baseCfg, renamedSql, { silentDuplicate: true });
|
|
653
|
+
// 创建成功后添加到已创建列表,防止重复创建
|
|
654
|
+
createdIndexNames.add(newIndexName);
|
|
655
|
+
}
|
|
656
|
+
catch (error) {
|
|
657
|
+
// 非预期错误,记录日志
|
|
658
|
+
console.warn('[ShardingTableCreator] 创建索引失败:', (error === null || error === void 0 ? void 0 : error.message) || error);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* 获取目标表已存在的所有索引名
|
|
664
|
+
*/
|
|
665
|
+
async getExistingIndexNames(tableName, baseCfg) {
|
|
666
|
+
const indexNames = new Set();
|
|
667
|
+
try {
|
|
668
|
+
if (baseCfg.sqlDbType === keys_1.SqlDbType.mysql) {
|
|
669
|
+
const sql = `
|
|
670
|
+
SELECT INDEX_NAME
|
|
671
|
+
FROM information_schema.STATISTICS
|
|
672
|
+
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '${tableName}'
|
|
673
|
+
`;
|
|
674
|
+
const result = await this.crudProFactory().executeSQL({
|
|
675
|
+
isNativeSQL: true,
|
|
676
|
+
sqlDatabase: baseCfg.sqlDatabase,
|
|
677
|
+
sqlDbType: baseCfg.sqlDbType,
|
|
678
|
+
executeSql: sql,
|
|
679
|
+
});
|
|
680
|
+
result.forEach(row => indexNames.add(row.INDEX_NAME));
|
|
681
|
+
}
|
|
682
|
+
else if (baseCfg.sqlDbType === keys_1.SqlDbType.postgres) {
|
|
683
|
+
const sql = `
|
|
684
|
+
SELECT indexname
|
|
685
|
+
FROM pg_indexes
|
|
686
|
+
WHERE schemaname = 'public' AND tablename = '${tableName}'
|
|
687
|
+
`;
|
|
688
|
+
const result = await this.crudProFactory().executeSQL({
|
|
689
|
+
isNativeSQL: true,
|
|
690
|
+
sqlDatabase: baseCfg.sqlDatabase,
|
|
691
|
+
sqlDbType: baseCfg.sqlDbType,
|
|
692
|
+
executeSql: sql,
|
|
693
|
+
});
|
|
694
|
+
result.forEach(row => indexNames.add(row.indexname));
|
|
695
|
+
}
|
|
696
|
+
else if (baseCfg.sqlDbType === keys_1.SqlDbType.sqlserver) {
|
|
697
|
+
const sql = `
|
|
698
|
+
SELECT name
|
|
699
|
+
FROM sys.indexes
|
|
700
|
+
WHERE object_id = OBJECT_ID('${tableName}')
|
|
701
|
+
`;
|
|
702
|
+
const result = await this.crudProFactory().executeSQL({
|
|
703
|
+
isNativeSQL: true,
|
|
704
|
+
sqlDatabase: baseCfg.sqlDatabase,
|
|
705
|
+
sqlDbType: baseCfg.sqlDbType,
|
|
706
|
+
executeSql: sql,
|
|
707
|
+
});
|
|
708
|
+
result.forEach(row => indexNames.add(row.name));
|
|
590
709
|
}
|
|
591
710
|
}
|
|
592
711
|
catch (error) {
|
|
593
|
-
console.warn('[ShardingTableCreator]
|
|
594
|
-
// 索引复制失败不影响主流程
|
|
712
|
+
console.warn('[ShardingTableCreator] 获取已存在索引列表失败:', error);
|
|
595
713
|
}
|
|
714
|
+
return indexNames;
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* 从 CREATE INDEX 语句中提取索引名
|
|
718
|
+
*/
|
|
719
|
+
extractIndexName(indexSql, sqlDbType) {
|
|
720
|
+
if (sqlDbType === keys_1.SqlDbType.mysql) {
|
|
721
|
+
const match = indexSql.match(/INDEX `([^`]+)`/i);
|
|
722
|
+
return match ? match[1] : '';
|
|
723
|
+
}
|
|
724
|
+
else if (sqlDbType === keys_1.SqlDbType.postgres) {
|
|
725
|
+
const match = indexSql.match(/INDEX "([^"]+)"/i);
|
|
726
|
+
return match ? match[1] : '';
|
|
727
|
+
}
|
|
728
|
+
else if (sqlDbType === keys_1.SqlDbType.sqlserver) {
|
|
729
|
+
const match = indexSql.match(/INDEX \[([^\]]+)\]/i);
|
|
730
|
+
return match ? match[1] : '';
|
|
731
|
+
}
|
|
732
|
+
return '';
|
|
596
733
|
}
|
|
597
734
|
/**
|
|
598
735
|
* 在索引 SQL 中替换表名
|
|
@@ -654,13 +791,12 @@ class ShardingTableCreator {
|
|
|
654
791
|
AND TABLE_NAME = '${this.config.baseTable}'
|
|
655
792
|
ORDER BY INDEX_NAME, SEQ_IN_INDEX
|
|
656
793
|
`;
|
|
657
|
-
const
|
|
794
|
+
const rows = await this.crudProFactory().executeSQL({
|
|
658
795
|
isNativeSQL: true,
|
|
659
796
|
sqlDatabase: baseCfg.sqlDatabase,
|
|
660
797
|
sqlDbType: baseCfg.sqlDbType,
|
|
661
798
|
executeSql: sql,
|
|
662
|
-
});
|
|
663
|
-
const rows = (result === null || result === void 0 ? void 0 : result.rows) || [];
|
|
799
|
+
}) || [];
|
|
664
800
|
// 按索引名分组
|
|
665
801
|
const indexMap = new Map();
|
|
666
802
|
for (const row of rows) {
|
|
@@ -708,13 +844,12 @@ class ShardingTableCreator {
|
|
|
708
844
|
WHERE n.nspname = '${schema}'
|
|
709
845
|
AND t.relname = '${this.config.baseTable}'
|
|
710
846
|
`;
|
|
711
|
-
const
|
|
847
|
+
const rows = await this.crudProFactory().executeSQL({
|
|
712
848
|
isNativeSQL: true,
|
|
713
849
|
sqlDatabase: baseCfg.sqlDatabase,
|
|
714
850
|
sqlDbType: baseCfg.sqlDbType,
|
|
715
851
|
executeSql: sql,
|
|
716
|
-
});
|
|
717
|
-
const rows = (result === null || result === void 0 ? void 0 : result.rows) || [];
|
|
852
|
+
}) || [];
|
|
718
853
|
return rows.map((row) => ({
|
|
719
854
|
isPrimary: row.is_primary,
|
|
720
855
|
createSql: row.index_def,
|
|
@@ -742,13 +877,12 @@ class ShardingTableCreator {
|
|
|
742
877
|
WHERE i.object_id = OBJECT_ID('${this.config.baseTable}')
|
|
743
878
|
AND i.name IS NOT NULL
|
|
744
879
|
`;
|
|
745
|
-
const
|
|
880
|
+
const rows = await this.crudProFactory().executeSQL({
|
|
746
881
|
isNativeSQL: true,
|
|
747
882
|
sqlDatabase: baseCfg.sqlDatabase,
|
|
748
883
|
sqlDbType: baseCfg.sqlDbType,
|
|
749
884
|
executeSql: sql,
|
|
750
|
-
});
|
|
751
|
-
const rows = (result === null || result === void 0 ? void 0 : result.rows) || [];
|
|
885
|
+
}) || [];
|
|
752
886
|
return rows.map((row) => {
|
|
753
887
|
const isPrimary = row.is_primary;
|
|
754
888
|
const isUnique = row.is_unique;
|