oak-db 3.3.13 → 4.0.0
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/README.md +5 -1
- package/lib/MySQL/connector.d.ts +9 -1
- package/lib/MySQL/connector.js +33 -14
- package/lib/MySQL/migration.d.ts +10 -0
- package/lib/MySQL/migration.js +649 -0
- package/lib/MySQL/store.d.ts +19 -2
- package/lib/MySQL/store.js +159 -110
- package/lib/MySQL/translator.d.ts +5 -1
- package/lib/MySQL/translator.js +47 -14
- package/lib/PostgreSQL/connector.d.ts +10 -0
- package/lib/PostgreSQL/connector.js +58 -51
- package/lib/PostgreSQL/migration.d.ts +10 -0
- package/lib/PostgreSQL/migration.js +984 -0
- package/lib/PostgreSQL/prepare.d.ts +2 -0
- package/lib/PostgreSQL/prepare.js +69 -0
- package/lib/PostgreSQL/store.d.ts +16 -2
- package/lib/PostgreSQL/store.js +196 -163
- package/lib/PostgreSQL/translator.d.ts +28 -8
- package/lib/PostgreSQL/translator.js +208 -226
- package/lib/index.d.ts +1 -0
- package/lib/migration.d.ts +27 -0
- package/lib/migration.js +1029 -0
- package/lib/sqlTranslator.d.ts +5 -1
- package/lib/sqlTranslator.js +12 -4
- package/lib/types/dbStore.d.ts +8 -15
- package/lib/types/migration.d.ts +251 -0
- package/lib/types/migration.js +2 -0
- package/lib/utils/indexInspection.d.ts +4 -0
- package/lib/utils/indexInspection.js +32 -0
- package/lib/utils/indexName.d.ts +15 -0
- package/lib/utils/indexName.js +76 -0
- package/lib/utils/inspection.d.ts +13 -0
- package/lib/utils/inspection.js +56 -0
- package/package.json +5 -2
package/lib/MySQL/store.d.ts
CHANGED
|
@@ -8,12 +8,14 @@ import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
|
|
|
8
8
|
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
|
|
9
9
|
import { CreateEntityOption } from '../types/Translator';
|
|
10
10
|
import { DbStore, Plan } from '../types/dbStore';
|
|
11
|
+
import { MigrationPlanningOptions } from '../types/migration';
|
|
11
12
|
export declare class MysqlStore<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED>> extends CascadeStore<ED> implements DbStore<ED, Cxt> {
|
|
12
13
|
protected countAbjointRow<T extends keyof ED, OP extends SelectOption, Cxt extends SyncContext<ED>>(entity: T, selection: Pick<ED[T]['Selection'], 'filter' | 'count'>, context: Cxt, option: OP): number;
|
|
13
14
|
protected aggregateAbjointRowSync<T extends keyof ED, OP extends SelectOption, Cxt extends SyncContext<ED>>(entity: T, aggregation: ED[T]['Aggregation'], context: Cxt, option: OP): AggregationResult<ED[T]['Schema']>;
|
|
14
15
|
protected selectAbjointRow<T extends keyof ED, OP extends SelectOption>(entity: T, selection: ED[T]['Selection'], context: SyncContext<ED>, option: OP): Partial<ED[T]['Schema']>[];
|
|
15
16
|
protected updateAbjointRow<T extends keyof ED, OP extends OperateOption>(entity: T, operation: ED[T]['Operation'], context: SyncContext<ED>, option: OP): number;
|
|
16
17
|
exec(script: string, txnId?: string): Promise<void>;
|
|
18
|
+
supportsTransactionalDdl(): boolean;
|
|
17
19
|
connector: MySqlConnector;
|
|
18
20
|
translator: MySqlTranslator<ED>;
|
|
19
21
|
constructor(storageSchema: StorageSchema<ED>, configuration: MySQLConfiguration);
|
|
@@ -22,9 +24,12 @@ export declare class MysqlStore<ED extends EntityDict & BaseEntityDict, Cxt exte
|
|
|
22
24
|
aggregate<T extends keyof ED, OP extends SelectOption>(entity: T, aggregation: ED[T]['Aggregation'], context: Cxt, option: OP): Promise<AggregationResult<ED[T]['Schema']>>;
|
|
23
25
|
protected supportManyToOneJoin(): boolean;
|
|
24
26
|
protected supportMultipleCreate(): boolean;
|
|
27
|
+
protected supportUpdateReturning(): boolean;
|
|
25
28
|
private formResult;
|
|
26
29
|
protected selectAbjointRowAsync<T extends keyof ED>(entity: T, selection: ED[T]['Selection'], context: AsyncContext<ED>, option?: MySqlSelectOption): Promise<Partial<ED[T]['Schema']>[]>;
|
|
30
|
+
protected updateAbjointRowReturningSync<T extends keyof ED>(entity: T, operation: ED[T]['Operation'], context: AsyncContext<ED>, returning: Record<string, any>, option?: MysqlOperateOption): [number, Partial<ED[T]['Schema']>[]];
|
|
27
31
|
protected updateAbjointRowAsync<T extends keyof ED>(entity: T, operation: ED[T]['Operation'], context: AsyncContext<ED>, option?: MysqlOperateOption): Promise<number>;
|
|
32
|
+
protected updateAbjointRowReturningAsync<T extends keyof ED>(entity: T, operation: ED[T]['Operation'], context: AsyncContext<ED>, returning?: Record<string, any>, option?: MysqlOperateOption): Promise<[number, Partial<ED[T]['Schema']>[]]>;
|
|
28
33
|
operate<T extends keyof ED>(entity: T, operation: ED[T]['Operation'], context: Cxt, option: OperateOption): Promise<OperationResult<ED>>;
|
|
29
34
|
select<T extends keyof ED>(entity: T, selection: ED[T]['Selection'], context: Cxt, option: SelectOption): Promise<Partial<ED[T]['Schema']>[]>;
|
|
30
35
|
protected countAbjointRowAsync<T extends keyof ED>(entity: T, selection: Pick<ED[T]['Selection'], 'filter' | 'count'>, context: AsyncContext<ED>, option: SelectOption): Promise<number>;
|
|
@@ -34,17 +39,29 @@ export declare class MysqlStore<ED extends EntityDict & BaseEntityDict, Cxt exte
|
|
|
34
39
|
rollback(txnId: string): Promise<void>;
|
|
35
40
|
connect(): Promise<void>;
|
|
36
41
|
disconnect(): Promise<void>;
|
|
42
|
+
private shouldResetEntity;
|
|
43
|
+
private quoteIdentifier;
|
|
44
|
+
private buildResetDropSql;
|
|
45
|
+
private buildBatchAddForeignKeySql;
|
|
46
|
+
private shouldLogInitializeProgress;
|
|
47
|
+
private isFullManagedReset;
|
|
48
|
+
private getCreateOptionForInitialize;
|
|
49
|
+
private resetManagedSchema;
|
|
50
|
+
private createManagedSchema;
|
|
51
|
+
private applyForeignKeysFromPlan;
|
|
52
|
+
private applyDeclaredForeignKeys;
|
|
53
|
+
private applyMissingForeignKeys;
|
|
37
54
|
initialize(option: CreateEntityOption): Promise<void>;
|
|
38
55
|
readSchema(): Promise<StorageSchema<ED>>;
|
|
39
56
|
/**
|
|
40
57
|
* 根据载入的dataSchema,和数据库中原来的schema,决定如何来upgrade
|
|
41
58
|
* 制订出来的plan分为两阶段:增加阶段和削减阶段,在两个阶段之间,由用户来修正数据
|
|
42
59
|
*/
|
|
43
|
-
makeUpgradePlan(): Promise<Plan
|
|
60
|
+
makeUpgradePlan(options?: MigrationPlanningOptions): Promise<Plan<ED>>;
|
|
44
61
|
/**
|
|
45
62
|
* 比较两个schema的不同,这里计算的是new对old的增量
|
|
46
63
|
* @param schemaOld
|
|
47
64
|
* @param SchemaNew
|
|
48
65
|
*/
|
|
49
|
-
diffSchema(schemaOld: StorageSchema<
|
|
66
|
+
diffSchema(schemaOld: StorageSchema<ED>, schemaNew: StorageSchema<ED>, options?: MigrationPlanningOptions): Plan<ED>;
|
|
50
67
|
}
|
package/lib/MySQL/store.js
CHANGED
|
@@ -37,6 +37,9 @@ class MysqlStore extends CascadeStore_1.CascadeStore {
|
|
|
37
37
|
async exec(script, txnId) {
|
|
38
38
|
await this.connector.exec(script, txnId);
|
|
39
39
|
}
|
|
40
|
+
supportsTransactionalDdl() {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
40
43
|
connector;
|
|
41
44
|
translator;
|
|
42
45
|
constructor(storageSchema, configuration) {
|
|
@@ -61,6 +64,9 @@ class MysqlStore extends CascadeStore_1.CascadeStore {
|
|
|
61
64
|
supportMultipleCreate() {
|
|
62
65
|
return true;
|
|
63
66
|
}
|
|
67
|
+
supportUpdateReturning() {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
64
70
|
formResult(entity, result) {
|
|
65
71
|
const schema = this.getSchema();
|
|
66
72
|
/* function resolveObject(r: Record<string, any>, path: string, value: any) {
|
|
@@ -236,6 +242,9 @@ class MysqlStore extends CascadeStore_1.CascadeStore {
|
|
|
236
242
|
const result = await this.connector.exec(sql, context.getCurrentTxnId());
|
|
237
243
|
return this.formResult(entity, result[0]);
|
|
238
244
|
}
|
|
245
|
+
updateAbjointRowReturningSync(entity, operation, context, returning, option) {
|
|
246
|
+
throw new Error('Mysql store 不支持同步更新数据');
|
|
247
|
+
}
|
|
239
248
|
async updateAbjointRowAsync(entity, operation, context, option) {
|
|
240
249
|
const { translator, connector } = this;
|
|
241
250
|
const { action } = operation;
|
|
@@ -251,7 +260,7 @@ class MysqlStore extends CascadeStore_1.CascadeStore {
|
|
|
251
260
|
const sql = translator.translateRemove(entity, operation, option);
|
|
252
261
|
const result = await connector.exec(sql, txn);
|
|
253
262
|
// todo 这里对sorter和indexfrom/count的支持不完整
|
|
254
|
-
return result[0].
|
|
263
|
+
return result[0].affectedRows;
|
|
255
264
|
}
|
|
256
265
|
default: {
|
|
257
266
|
(0, assert_1.default)(!['select', 'download', 'stat'].includes(action));
|
|
@@ -262,6 +271,9 @@ class MysqlStore extends CascadeStore_1.CascadeStore {
|
|
|
262
271
|
}
|
|
263
272
|
}
|
|
264
273
|
}
|
|
274
|
+
async updateAbjointRowReturningAsync(entity, operation, context, returning, option) {
|
|
275
|
+
throw new Error('MySQL store不支持返回更新数据');
|
|
276
|
+
}
|
|
265
277
|
async operate(entity, operation, context, option) {
|
|
266
278
|
const { action } = operation;
|
|
267
279
|
(0, assert_1.default)(!['select', 'download', 'stat'].includes(action), '现在不支持使用select operation');
|
|
@@ -295,26 +307,164 @@ class MysqlStore extends CascadeStore_1.CascadeStore {
|
|
|
295
307
|
async disconnect() {
|
|
296
308
|
await this.connector.disconnect();
|
|
297
309
|
}
|
|
298
|
-
|
|
310
|
+
shouldResetEntity(option, entity) {
|
|
311
|
+
const ifExists = option?.ifExists || 'drop';
|
|
312
|
+
const entityDef = this.translator.schema[entity];
|
|
313
|
+
if (ifExists === 'drop') {
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
if (ifExists === 'dropIfNotStatic') {
|
|
317
|
+
return !entityDef.static;
|
|
318
|
+
}
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
quoteIdentifier(identifier) {
|
|
322
|
+
return `\`${identifier.replace(/`/g, '``')}\``;
|
|
323
|
+
}
|
|
324
|
+
buildResetDropSql(tableNames) {
|
|
325
|
+
return `DROP TABLE IF EXISTS ${tableNames.map((tableName) => this.quoteIdentifier(tableName)).join(', ')};`;
|
|
326
|
+
}
|
|
327
|
+
buildBatchAddForeignKeySql(sqls) {
|
|
328
|
+
const grouped = new Map();
|
|
329
|
+
const orderedTables = [];
|
|
330
|
+
const passthroughSqls = [];
|
|
331
|
+
sqls.forEach((sql) => {
|
|
332
|
+
const normalized = sql.trim();
|
|
333
|
+
const match = /^ALTER TABLE\s+(`[^`]+`)\s+(ADD CONSTRAINT[\s\S]+);$/i.exec(normalized);
|
|
334
|
+
if (!match) {
|
|
335
|
+
passthroughSqls.push(normalized);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const [, tableName, clause] = match;
|
|
339
|
+
if (!grouped.has(tableName)) {
|
|
340
|
+
grouped.set(tableName, []);
|
|
341
|
+
orderedTables.push(tableName);
|
|
342
|
+
}
|
|
343
|
+
grouped.get(tableName).push(clause);
|
|
344
|
+
});
|
|
345
|
+
return [
|
|
346
|
+
...orderedTables.map((tableName) => {
|
|
347
|
+
const clauses = grouped.get(tableName);
|
|
348
|
+
return `ALTER TABLE ${tableName} ${clauses.join(', ')};`;
|
|
349
|
+
}),
|
|
350
|
+
...passthroughSqls,
|
|
351
|
+
];
|
|
352
|
+
}
|
|
353
|
+
shouldLogInitializeProgress(current, total, step) {
|
|
354
|
+
return total <= step || current === 1 || current === total || current % step === 0;
|
|
355
|
+
}
|
|
356
|
+
isFullManagedReset(option) {
|
|
357
|
+
return (option?.ifExists || 'drop') === 'drop';
|
|
358
|
+
}
|
|
359
|
+
getCreateOptionForInitialize() {
|
|
360
|
+
return {
|
|
361
|
+
ifExists: 'omit',
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
async resetManagedSchema(option) {
|
|
365
|
+
const tableNames = Object.keys(this.translator.schema).filter((entity) => this.shouldResetEntity(option, entity)).map((entity) => this.translator.schema[entity].storageName || entity);
|
|
366
|
+
if (tableNames.length === 0) {
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
await this.connector.withConnection(async (connection) => {
|
|
370
|
+
// MySQL 的 reset 必须在同一个 session 里关闭 FK 检查。
|
|
371
|
+
// 而且要把同一批表放进尽量少的 DROP TABLE 语句里;
|
|
372
|
+
// 逐表 DROP 在复杂 FK 图上会触发 metadata lock deadlock。
|
|
373
|
+
await connection.query('SET FOREIGN_KEY_CHECKS = 0;');
|
|
374
|
+
try {
|
|
375
|
+
await connection.query(this.buildResetDropSql(tableNames));
|
|
376
|
+
}
|
|
377
|
+
finally {
|
|
378
|
+
try {
|
|
379
|
+
await connection.query('SET FOREIGN_KEY_CHECKS = 1;');
|
|
380
|
+
}
|
|
381
|
+
catch (err) {
|
|
382
|
+
connection.destroy();
|
|
383
|
+
throw err;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
async createManagedSchema(option) {
|
|
299
389
|
const schema = this.getSchema();
|
|
300
|
-
|
|
390
|
+
const entities = Object.keys(schema);
|
|
391
|
+
// drop/reset 阶段已经独立完成,这里统一走 omit create,
|
|
392
|
+
// 避免 translator 再带入“每个实体自带 drop SQL”的旧流程。
|
|
393
|
+
for (let idx = 0; idx < entities.length; idx++) {
|
|
394
|
+
const entity = entities[idx];
|
|
395
|
+
if (this.shouldLogInitializeProgress(idx + 1, entities.length, 10)) {
|
|
396
|
+
console.log(`Initializing MySQL tables (${idx + 1}/${entities.length}): ${entity}`);
|
|
397
|
+
}
|
|
301
398
|
const sqls = this.translator.translateCreateEntity(entity, option);
|
|
302
399
|
for (const sql of sqls) {
|
|
303
400
|
await this.connector.exec(sql);
|
|
304
401
|
}
|
|
305
402
|
}
|
|
306
403
|
}
|
|
404
|
+
async applyForeignKeysFromPlan(plan) {
|
|
405
|
+
const sqls = [
|
|
406
|
+
...plan.forwardSql.filter((stmt) => /foreign key/i.test(stmt)),
|
|
407
|
+
...plan.manualSql.filter((stmt) => /add constraint .*foreign key/i.test(stmt)),
|
|
408
|
+
];
|
|
409
|
+
const batchedSqls = this.buildBatchAddForeignKeySql(sqls);
|
|
410
|
+
// initialize 阶段只执行“新增 FK”动作:
|
|
411
|
+
// 全量 reset 直接使用目标 schema 里的声明;半保留场景则先 inspect 再只补缺失项。
|
|
412
|
+
for (let idx = 0; idx < batchedSqls.length; idx++) {
|
|
413
|
+
if (this.shouldLogInitializeProgress(idx + 1, batchedSqls.length, 20)) {
|
|
414
|
+
console.log(`Initializing MySQL foreign keys (${idx + 1}/${batchedSqls.length})`);
|
|
415
|
+
}
|
|
416
|
+
const sql = batchedSqls[idx];
|
|
417
|
+
await this.connector.exec(sql);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
async applyDeclaredForeignKeys() {
|
|
421
|
+
const plan = this.connector.diffSchema({}, this.translator.schema, this.translator, {
|
|
422
|
+
compareForeignKeys: true,
|
|
423
|
+
});
|
|
424
|
+
await this.applyForeignKeysFromPlan(plan);
|
|
425
|
+
}
|
|
426
|
+
async applyMissingForeignKeys() {
|
|
427
|
+
const inspection = await this.connector.inspectSchema(this.translator);
|
|
428
|
+
const plan = this.connector.diffSchema(inspection.schema, this.translator.schema, this.translator, {
|
|
429
|
+
compareForeignKeys: true,
|
|
430
|
+
currentTableStats: inspection.tableStats,
|
|
431
|
+
});
|
|
432
|
+
await this.applyForeignKeysFromPlan(plan);
|
|
433
|
+
}
|
|
434
|
+
async initialize(option) {
|
|
435
|
+
console.log('Initializing MySQL schema reset phase...');
|
|
436
|
+
await this.resetManagedSchema(option);
|
|
437
|
+
console.log('Initializing MySQL schema create phase...');
|
|
438
|
+
await this.createManagedSchema(this.getCreateOptionForInitialize());
|
|
439
|
+
console.log('Initializing MySQL foreign key phase...');
|
|
440
|
+
if (this.isFullManagedReset(option)) {
|
|
441
|
+
await this.applyDeclaredForeignKeys();
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
await this.applyMissingForeignKeys();
|
|
445
|
+
}
|
|
446
|
+
}
|
|
307
447
|
// 从数据库中读取当前schema
|
|
308
448
|
readSchema() {
|
|
309
|
-
return this.
|
|
449
|
+
return this.connector.readSchema(this.translator);
|
|
310
450
|
}
|
|
311
451
|
/**
|
|
312
452
|
* 根据载入的dataSchema,和数据库中原来的schema,决定如何来upgrade
|
|
313
453
|
* 制订出来的plan分为两阶段:增加阶段和削减阶段,在两个阶段之间,由用户来修正数据
|
|
314
454
|
*/
|
|
315
|
-
async makeUpgradePlan() {
|
|
316
|
-
const
|
|
317
|
-
|
|
455
|
+
async makeUpgradePlan(options) {
|
|
456
|
+
const inspection = await this.connector.inspectSchema(this.translator);
|
|
457
|
+
// 自动探测“历史库完全没有物理 FK”的情况:
|
|
458
|
+
// 这类旧库先关闭 FK 对比,避免 upgrade 持续产出噪音计划。
|
|
459
|
+
const hasPhysicalForeignKeys = Object.values(inspection.schema).some((tableDef) => Object.values(tableDef.attributes || {}).some((attr) => {
|
|
460
|
+
const attribute = attr;
|
|
461
|
+
return attribute.type === 'ref' && typeof attribute.ref === 'string';
|
|
462
|
+
}));
|
|
463
|
+
const plan = this.diffSchema(inspection.schema, this.translator.schema, {
|
|
464
|
+
...options,
|
|
465
|
+
currentTableStats: options?.currentTableStats || inspection.tableStats,
|
|
466
|
+
compareForeignKeys: options?.compareForeignKeys ?? hasPhysicalForeignKeys,
|
|
467
|
+
});
|
|
318
468
|
return plan;
|
|
319
469
|
}
|
|
320
470
|
/**
|
|
@@ -322,109 +472,8 @@ class MysqlStore extends CascadeStore_1.CascadeStore {
|
|
|
322
472
|
* @param schemaOld
|
|
323
473
|
* @param SchemaNew
|
|
324
474
|
*/
|
|
325
|
-
diffSchema(schemaOld, schemaNew) {
|
|
326
|
-
|
|
327
|
-
newTables: {},
|
|
328
|
-
newIndexes: {},
|
|
329
|
-
updatedIndexes: {},
|
|
330
|
-
updatedTables: {},
|
|
331
|
-
};
|
|
332
|
-
for (const table in schemaNew) {
|
|
333
|
-
// mysql数据字典不分大小写的
|
|
334
|
-
if (schemaOld[table] || schemaOld[table.toLowerCase()]) {
|
|
335
|
-
const { attributes, indexes } = schemaOld[table] || schemaOld[table.toLowerCase()];
|
|
336
|
-
const { attributes: attributesNew, indexes: indexesNew } = schemaNew[table];
|
|
337
|
-
const assignToUpdateTables = (attr, isNew) => {
|
|
338
|
-
if (!plan.updatedTables[table]) {
|
|
339
|
-
plan.updatedTables[table] = {
|
|
340
|
-
attributes: {
|
|
341
|
-
[attr]: {
|
|
342
|
-
...attributesNew[attr],
|
|
343
|
-
isNew,
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
};
|
|
347
|
-
}
|
|
348
|
-
else {
|
|
349
|
-
plan.updatedTables[table].attributes[attr] = {
|
|
350
|
-
...attributesNew[attr],
|
|
351
|
-
isNew,
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
};
|
|
355
|
-
for (const attr in attributesNew) {
|
|
356
|
-
if (attributes[attr]) {
|
|
357
|
-
// 因为反向无法复原原来定义的attribute类型,这里就比较两次创建的sql是不是一致,不是太好的设计。
|
|
358
|
-
const sql1 = this.translator.translateAttributeDef(attr, attributesNew[attr]);
|
|
359
|
-
const sql2 = this.translator.translateAttributeDef(attr, attributes[attr]);
|
|
360
|
-
if (!this.translator.compareSql(sql1, sql2)) {
|
|
361
|
-
assignToUpdateTables(attr, false);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
else {
|
|
365
|
-
assignToUpdateTables(attr, true);
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
if (indexesNew) {
|
|
369
|
-
const assignToIndexes = (index, isNew) => {
|
|
370
|
-
if (isNew) {
|
|
371
|
-
if (plan.newIndexes[table]) {
|
|
372
|
-
plan.newIndexes[table].push(index);
|
|
373
|
-
}
|
|
374
|
-
else {
|
|
375
|
-
plan.newIndexes[table] = [index];
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
else {
|
|
379
|
-
if (plan.updatedIndexes[table]) {
|
|
380
|
-
plan.updatedIndexes[table].push(index);
|
|
381
|
-
}
|
|
382
|
-
else {
|
|
383
|
-
plan.updatedIndexes[table] = [index];
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
};
|
|
387
|
-
const compareConfig = (config1, config2) => {
|
|
388
|
-
const unique1 = config1?.unique || false;
|
|
389
|
-
const unique2 = config2?.unique || false;
|
|
390
|
-
if (unique1 !== unique2) {
|
|
391
|
-
return false;
|
|
392
|
-
}
|
|
393
|
-
const type1 = config1?.type || 'btree';
|
|
394
|
-
const type2 = config2?.type || 'btree';
|
|
395
|
-
// parser目前无法从mysql中读出来,所以不比了
|
|
396
|
-
return type1 === type2;
|
|
397
|
-
};
|
|
398
|
-
for (const index of indexesNew) {
|
|
399
|
-
const { name, config, attributes } = index;
|
|
400
|
-
const origin = indexes?.find(ele => ele.name === name);
|
|
401
|
-
if (origin) {
|
|
402
|
-
if (JSON.stringify(attributes) !== JSON.stringify(origin.attributes)) {
|
|
403
|
-
// todo,这里要细致比较,不能用json.stringify
|
|
404
|
-
assignToIndexes(index, false);
|
|
405
|
-
}
|
|
406
|
-
else {
|
|
407
|
-
if (!compareConfig(config, origin.config)) {
|
|
408
|
-
assignToIndexes(index, false);
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
else {
|
|
413
|
-
assignToIndexes(index, true);
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
else {
|
|
419
|
-
plan.newTables[table] = {
|
|
420
|
-
attributes: schemaNew[table].attributes,
|
|
421
|
-
};
|
|
422
|
-
if (schemaNew[table].indexes) {
|
|
423
|
-
plan.newIndexes[table] = schemaNew[table].indexes;
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
return plan;
|
|
475
|
+
diffSchema(schemaOld, schemaNew, options) {
|
|
476
|
+
return this.connector.diffSchema(schemaOld, schemaNew, this.translator, options);
|
|
428
477
|
}
|
|
429
478
|
}
|
|
430
479
|
exports.MysqlStore = MysqlStore;
|
|
@@ -8,6 +8,9 @@ export interface MySqlSelectOption extends SqlSelectOption {
|
|
|
8
8
|
export interface MysqlOperateOption extends SqlOperateOption {
|
|
9
9
|
}
|
|
10
10
|
export declare class MySqlTranslator<ED extends EntityDict & BaseEntityDict> extends SqlTranslator<ED> {
|
|
11
|
+
readonly maxIndexNameLength = 64;
|
|
12
|
+
getPhysicalIndexName(entityName: string, tableName: string, logicalName: string, suffix?: string): string;
|
|
13
|
+
getLegacyPhysicalIndexNames(entityName: string, tableName: string, logicalName: string, suffix?: string): string[];
|
|
11
14
|
protected getDefaultSelectFilter(alias: string, option?: MySqlSelectOption): string;
|
|
12
15
|
private makeUpSchema;
|
|
13
16
|
constructor(schema: StorageSchema<ED>);
|
|
@@ -98,6 +101,7 @@ export declare class MySqlTranslator<ED extends EntityDict & BaseEntityDict> ext
|
|
|
98
101
|
protected translateAttrValue(dataType: DataType | Ref, value: any): string;
|
|
99
102
|
protected translateFullTextSearch<T extends keyof ED>(value: Q_FullTextValue, entity: T, alias: string): string;
|
|
100
103
|
translateAttributeDef(attr: string, attrDef: Attribute): string;
|
|
104
|
+
translateColumnDefinition(entity: string, attr: string, attrDef: Attribute): string;
|
|
101
105
|
translateCreateEntity<T extends keyof ED>(entity: T, options?: CreateEntityOption): string[];
|
|
102
106
|
private translateFnName;
|
|
103
107
|
private translateAttrInExpression;
|
|
@@ -109,6 +113,6 @@ export declare class MySqlTranslator<ED extends EntityDict & BaseEntityDict> ext
|
|
|
109
113
|
* 将MySQL返回的Type回译成oak的类型,是 populateDataTypeDef 的反函数
|
|
110
114
|
* @param type
|
|
111
115
|
*/
|
|
112
|
-
|
|
116
|
+
reTranslateToAttribute(type: string): Attribute;
|
|
113
117
|
readSchema(execFn: (sql: string) => Promise<any>): Promise<StorageSchema<ED>>;
|
|
114
118
|
}
|
package/lib/MySQL/translator.js
CHANGED
|
@@ -7,6 +7,7 @@ const util_1 = require("util");
|
|
|
7
7
|
const lodash_1 = require("lodash");
|
|
8
8
|
const types_1 = require("oak-domain/lib/types");
|
|
9
9
|
const sqlTranslator_1 = require("../sqlTranslator");
|
|
10
|
+
const indexName_1 = require("../utils/indexName");
|
|
10
11
|
const GeoTypes = [
|
|
11
12
|
{
|
|
12
13
|
type: 'point',
|
|
@@ -72,6 +73,26 @@ function transformGeoData(data) {
|
|
|
72
73
|
}
|
|
73
74
|
}
|
|
74
75
|
class MySqlTranslator extends sqlTranslator_1.SqlTranslator {
|
|
76
|
+
maxIndexNameLength = 64;
|
|
77
|
+
getPhysicalIndexName(entityName, tableName, logicalName, suffix = '') {
|
|
78
|
+
return (0, indexName_1.buildCompactPhysicalIndexName)({
|
|
79
|
+
entityName,
|
|
80
|
+
tableName,
|
|
81
|
+
logicalName,
|
|
82
|
+
suffix,
|
|
83
|
+
maxLength: this.maxIndexNameLength,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
getLegacyPhysicalIndexNames(entityName, tableName, logicalName, suffix = '') {
|
|
87
|
+
return (0, indexName_1.buildLegacyPhysicalIndexNames)({
|
|
88
|
+
entityName,
|
|
89
|
+
tableName,
|
|
90
|
+
logicalName,
|
|
91
|
+
suffix,
|
|
92
|
+
maxLength: this.maxIndexNameLength,
|
|
93
|
+
serverTruncatesWhenOverflow: false,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
75
96
|
getDefaultSelectFilter(alias, option) {
|
|
76
97
|
if (option?.includedDeleted) {
|
|
77
98
|
return '';
|
|
@@ -579,6 +600,9 @@ class MySqlTranslator extends sqlTranslator_1.SqlTranslator {
|
|
|
579
600
|
}
|
|
580
601
|
return sql;
|
|
581
602
|
}
|
|
603
|
+
translateColumnDefinition(entity, attr, attrDef) {
|
|
604
|
+
return this.translateAttributeDef(attr, attrDef);
|
|
605
|
+
}
|
|
582
606
|
translateCreateEntity(entity, options) {
|
|
583
607
|
const ifExists = options?.ifExists || 'drop';
|
|
584
608
|
const { schema } = this;
|
|
@@ -619,17 +643,17 @@ class MySqlTranslator extends sqlTranslator_1.SqlTranslator {
|
|
|
619
643
|
sql += ',\n';
|
|
620
644
|
indexes.forEach(({ name, attributes, config }, idx) => {
|
|
621
645
|
const { unique, type, parser } = config || {};
|
|
622
|
-
|
|
623
|
-
|
|
646
|
+
const physicalName = this.getPhysicalIndexName(String(entity), storageName || entity, name);
|
|
647
|
+
if (unique) {
|
|
624
648
|
sql += ' unique ';
|
|
625
649
|
}
|
|
626
|
-
else
|
|
650
|
+
else if (type === 'fulltext') {
|
|
627
651
|
sql += ' fulltext ';
|
|
628
652
|
}
|
|
629
653
|
else if (type === 'spatial') {
|
|
630
654
|
sql += ' spatial ';
|
|
631
655
|
}
|
|
632
|
-
sql += `index \`${
|
|
656
|
+
sql += `index \`${physicalName}\` `;
|
|
633
657
|
if (type === 'hash') {
|
|
634
658
|
sql += ` using hash `;
|
|
635
659
|
}
|
|
@@ -1069,9 +1093,12 @@ class MySqlTranslator extends sqlTranslator_1.SqlTranslator {
|
|
|
1069
1093
|
if (sorterText) {
|
|
1070
1094
|
sql += ` order by ${sorterText}`;
|
|
1071
1095
|
}
|
|
1072
|
-
if (typeof
|
|
1073
|
-
|
|
1074
|
-
|
|
1096
|
+
if (typeof count === 'number') {
|
|
1097
|
+
// MySQL UPDATE 不支持 LIMIT offset, count 语法
|
|
1098
|
+
if (typeof indexFrom === 'number' && indexFrom > 0) {
|
|
1099
|
+
throw new Error('MySQL does not support LIMIT with OFFSET in UPDATE statements. Use indexFrom=0 or omit indexFrom.');
|
|
1100
|
+
}
|
|
1101
|
+
sql += ` limit ${count}`;
|
|
1075
1102
|
}
|
|
1076
1103
|
return sql;
|
|
1077
1104
|
}
|
|
@@ -1087,9 +1114,12 @@ class MySqlTranslator extends sqlTranslator_1.SqlTranslator {
|
|
|
1087
1114
|
if (sorterText) {
|
|
1088
1115
|
sql += ` order by ${sorterText}`;
|
|
1089
1116
|
}
|
|
1090
|
-
if (typeof
|
|
1091
|
-
|
|
1092
|
-
|
|
1117
|
+
if (typeof count === 'number') {
|
|
1118
|
+
// MySQL DELETE 不支持 LIMIT offset, count 语法
|
|
1119
|
+
if (typeof indexFrom === 'number' && indexFrom > 0) {
|
|
1120
|
+
throw new Error('MySQL does not support LIMIT with OFFSET in DELETE statements. Use indexFrom=0 or omit indexFrom.');
|
|
1121
|
+
}
|
|
1122
|
+
sql += ` limit ${count}`;
|
|
1093
1123
|
}
|
|
1094
1124
|
return sql;
|
|
1095
1125
|
}
|
|
@@ -1105,9 +1135,12 @@ class MySqlTranslator extends sqlTranslator_1.SqlTranslator {
|
|
|
1105
1135
|
if (sorterText) {
|
|
1106
1136
|
sql += ` order by ${sorterText}`;
|
|
1107
1137
|
}
|
|
1108
|
-
if (typeof
|
|
1109
|
-
|
|
1110
|
-
|
|
1138
|
+
if (typeof count === 'number') {
|
|
1139
|
+
// MySQL UPDATE 不支持 LIMIT offset, count 语法(软删除使用UPDATE)
|
|
1140
|
+
if (typeof indexFrom === 'number' && indexFrom > 0) {
|
|
1141
|
+
throw new Error('MySQL does not support LIMIT with OFFSET in UPDATE statements (soft delete). Use indexFrom=0 or omit indexFrom.');
|
|
1142
|
+
}
|
|
1143
|
+
sql += ` limit ${count}`;
|
|
1111
1144
|
}
|
|
1112
1145
|
return sql;
|
|
1113
1146
|
}
|
|
@@ -1127,7 +1160,7 @@ class MySqlTranslator extends sqlTranslator_1.SqlTranslator {
|
|
|
1127
1160
|
};
|
|
1128
1161
|
}
|
|
1129
1162
|
const withPrecisionDataTypes = MySqlTranslator.withPrecisionDataTypes.join('|');
|
|
1130
|
-
result = (new RegExp(`^(${withPrecisionDataTypes})\\((\\d+),(d+)\\)$`)).exec(type);
|
|
1163
|
+
result = (new RegExp(`^(${withPrecisionDataTypes})\\((\\d+),(\\d+)\\)$`)).exec(type);
|
|
1131
1164
|
if (result) {
|
|
1132
1165
|
return {
|
|
1133
1166
|
type: result[1],
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { Pool, PoolClient, QueryResult, QueryResultRow } from 'pg';
|
|
2
2
|
import { TxnOption } from 'oak-domain/lib/types';
|
|
3
|
+
import { EntityDict, StorageSchema } from 'oak-domain/lib/types';
|
|
4
|
+
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
|
3
5
|
import { PostgreSQLConfiguration } from './types/Configuration';
|
|
6
|
+
import { PostgreSQLTranslator } from './translator';
|
|
7
|
+
import { MigrationPlanningOptions, Plan, SchemaInspectionResult } from '../types/migration';
|
|
4
8
|
export declare class PostgreSQLConnector {
|
|
5
9
|
pool: Pool;
|
|
6
10
|
configuration: PostgreSQLConfiguration;
|
|
7
11
|
txnDict: Record<string, PoolClient>;
|
|
12
|
+
txnExecQueueDict: Record<string, Promise<void>>;
|
|
8
13
|
constructor(configuration: PostgreSQLConfiguration);
|
|
9
14
|
connect(): void;
|
|
10
15
|
disconnect(): Promise<void>;
|
|
@@ -14,6 +19,11 @@ export declare class PostgreSQLConnector {
|
|
|
14
19
|
*/
|
|
15
20
|
private mapIsolationLevel;
|
|
16
21
|
exec(sql: string, txn?: string): Promise<[QueryResultRow[], QueryResult]>;
|
|
22
|
+
private enqueueTxnQuery;
|
|
23
|
+
private enqueueTxnTask;
|
|
24
|
+
readSchema<ED extends EntityDict & BaseEntityDict>(translator: PostgreSQLTranslator<ED>): Promise<StorageSchema<ED>>;
|
|
25
|
+
inspectSchema<ED extends EntityDict & BaseEntityDict>(translator: PostgreSQLTranslator<ED>): Promise<SchemaInspectionResult<ED>>;
|
|
26
|
+
diffSchema<ED extends EntityDict & BaseEntityDict>(currentSchema: StorageSchema<ED>, targetSchema: StorageSchema<ED>, translator: PostgreSQLTranslator<ED>, options?: MigrationPlanningOptions): Plan<ED>;
|
|
17
27
|
commitTransaction(txn: string): Promise<void>;
|
|
18
28
|
rollbackTransaction(txn: string): Promise<void>;
|
|
19
29
|
/**
|