oak-db 3.3.14 → 4.0.1
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 +24 -0
- package/lib/MySQL/migration.d.ts +10 -0
- package/lib/MySQL/migration.js +649 -0
- package/lib/MySQL/store.d.ts +16 -2
- package/lib/MySQL/store.js +149 -109
- package/lib/MySQL/translator.d.ts +5 -1
- package/lib/MySQL/translator.js +29 -5
- package/lib/PostgreSQL/connector.d.ts +10 -0
- package/lib/PostgreSQL/connector.js +37 -11
- 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 +13 -2
- package/lib/PostgreSQL/store.js +142 -151
- package/lib/PostgreSQL/translator.d.ts +10 -2
- package/lib/PostgreSQL/translator.js +125 -9
- package/lib/index.d.ts +1 -0
- package/lib/migration.d.ts +27 -0
- package/lib/migration.js +1029 -0
- package/lib/types/dbStore.d.ts +7 -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.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) {
|
|
@@ -304,26 +307,164 @@ class MysqlStore extends CascadeStore_1.CascadeStore {
|
|
|
304
307
|
async disconnect() {
|
|
305
308
|
await this.connector.disconnect();
|
|
306
309
|
}
|
|
307
|
-
|
|
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) {
|
|
308
389
|
const schema = this.getSchema();
|
|
309
|
-
|
|
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
|
+
}
|
|
310
398
|
const sqls = this.translator.translateCreateEntity(entity, option);
|
|
311
399
|
for (const sql of sqls) {
|
|
312
400
|
await this.connector.exec(sql);
|
|
313
401
|
}
|
|
314
402
|
}
|
|
315
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
|
+
}
|
|
316
447
|
// 从数据库中读取当前schema
|
|
317
448
|
readSchema() {
|
|
318
|
-
return this.
|
|
449
|
+
return this.connector.readSchema(this.translator);
|
|
319
450
|
}
|
|
320
451
|
/**
|
|
321
452
|
* 根据载入的dataSchema,和数据库中原来的schema,决定如何来upgrade
|
|
322
453
|
* 制订出来的plan分为两阶段:增加阶段和削减阶段,在两个阶段之间,由用户来修正数据
|
|
323
454
|
*/
|
|
324
|
-
async makeUpgradePlan() {
|
|
325
|
-
const
|
|
326
|
-
|
|
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
|
+
});
|
|
327
468
|
return plan;
|
|
328
469
|
}
|
|
329
470
|
/**
|
|
@@ -331,109 +472,8 @@ class MysqlStore extends CascadeStore_1.CascadeStore {
|
|
|
331
472
|
* @param schemaOld
|
|
332
473
|
* @param SchemaNew
|
|
333
474
|
*/
|
|
334
|
-
diffSchema(schemaOld, schemaNew) {
|
|
335
|
-
|
|
336
|
-
newTables: {},
|
|
337
|
-
newIndexes: {},
|
|
338
|
-
updatedIndexes: {},
|
|
339
|
-
updatedTables: {},
|
|
340
|
-
};
|
|
341
|
-
for (const table in schemaNew) {
|
|
342
|
-
// mysql数据字典不分大小写的
|
|
343
|
-
if (schemaOld[table] || schemaOld[table.toLowerCase()]) {
|
|
344
|
-
const { attributes, indexes } = schemaOld[table] || schemaOld[table.toLowerCase()];
|
|
345
|
-
const { attributes: attributesNew, indexes: indexesNew } = schemaNew[table];
|
|
346
|
-
const assignToUpdateTables = (attr, isNew) => {
|
|
347
|
-
if (!plan.updatedTables[table]) {
|
|
348
|
-
plan.updatedTables[table] = {
|
|
349
|
-
attributes: {
|
|
350
|
-
[attr]: {
|
|
351
|
-
...attributesNew[attr],
|
|
352
|
-
isNew,
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
};
|
|
356
|
-
}
|
|
357
|
-
else {
|
|
358
|
-
plan.updatedTables[table].attributes[attr] = {
|
|
359
|
-
...attributesNew[attr],
|
|
360
|
-
isNew,
|
|
361
|
-
};
|
|
362
|
-
}
|
|
363
|
-
};
|
|
364
|
-
for (const attr in attributesNew) {
|
|
365
|
-
if (attributes[attr]) {
|
|
366
|
-
// 因为反向无法复原原来定义的attribute类型,这里就比较两次创建的sql是不是一致,不是太好的设计。
|
|
367
|
-
const sql1 = this.translator.translateAttributeDef(attr, attributesNew[attr]);
|
|
368
|
-
const sql2 = this.translator.translateAttributeDef(attr, attributes[attr]);
|
|
369
|
-
if (!this.translator.compareSql(sql1, sql2)) {
|
|
370
|
-
assignToUpdateTables(attr, false);
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
else {
|
|
374
|
-
assignToUpdateTables(attr, true);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
if (indexesNew) {
|
|
378
|
-
const assignToIndexes = (index, isNew) => {
|
|
379
|
-
if (isNew) {
|
|
380
|
-
if (plan.newIndexes[table]) {
|
|
381
|
-
plan.newIndexes[table].push(index);
|
|
382
|
-
}
|
|
383
|
-
else {
|
|
384
|
-
plan.newIndexes[table] = [index];
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
else {
|
|
388
|
-
if (plan.updatedIndexes[table]) {
|
|
389
|
-
plan.updatedIndexes[table].push(index);
|
|
390
|
-
}
|
|
391
|
-
else {
|
|
392
|
-
plan.updatedIndexes[table] = [index];
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
};
|
|
396
|
-
const compareConfig = (config1, config2) => {
|
|
397
|
-
const unique1 = config1?.unique || false;
|
|
398
|
-
const unique2 = config2?.unique || false;
|
|
399
|
-
if (unique1 !== unique2) {
|
|
400
|
-
return false;
|
|
401
|
-
}
|
|
402
|
-
const type1 = config1?.type || 'btree';
|
|
403
|
-
const type2 = config2?.type || 'btree';
|
|
404
|
-
// parser目前无法从mysql中读出来,所以不比了
|
|
405
|
-
return type1 === type2;
|
|
406
|
-
};
|
|
407
|
-
for (const index of indexesNew) {
|
|
408
|
-
const { name, config, attributes } = index;
|
|
409
|
-
const origin = indexes?.find(ele => ele.name === name);
|
|
410
|
-
if (origin) {
|
|
411
|
-
if (JSON.stringify(attributes) !== JSON.stringify(origin.attributes)) {
|
|
412
|
-
// todo,这里要细致比较,不能用json.stringify
|
|
413
|
-
assignToIndexes(index, false);
|
|
414
|
-
}
|
|
415
|
-
else {
|
|
416
|
-
if (!compareConfig(config, origin.config)) {
|
|
417
|
-
assignToIndexes(index, false);
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
else {
|
|
422
|
-
assignToIndexes(index, true);
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
else {
|
|
428
|
-
plan.newTables[table] = {
|
|
429
|
-
attributes: schemaNew[table].attributes,
|
|
430
|
-
};
|
|
431
|
-
if (schemaNew[table].indexes) {
|
|
432
|
-
plan.newIndexes[table] = schemaNew[table].indexes;
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
return plan;
|
|
475
|
+
diffSchema(schemaOld, schemaNew, options) {
|
|
476
|
+
return this.connector.diffSchema(schemaOld, schemaNew, this.translator, options);
|
|
437
477
|
}
|
|
438
478
|
}
|
|
439
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
|
}
|
|
@@ -1136,7 +1160,7 @@ class MySqlTranslator extends sqlTranslator_1.SqlTranslator {
|
|
|
1136
1160
|
};
|
|
1137
1161
|
}
|
|
1138
1162
|
const withPrecisionDataTypes = MySqlTranslator.withPrecisionDataTypes.join('|');
|
|
1139
|
-
result = (new RegExp(`^(${withPrecisionDataTypes})\\((\\d+),(d+)\\)$`)).exec(type);
|
|
1163
|
+
result = (new RegExp(`^(${withPrecisionDataTypes})\\((\\d+),(\\d+)\\)$`)).exec(type);
|
|
1140
1164
|
if (result) {
|
|
1141
1165
|
return {
|
|
1142
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
|
/**
|
|
@@ -5,13 +5,16 @@ const tslib_1 = require("tslib");
|
|
|
5
5
|
const pg_1 = require("pg");
|
|
6
6
|
const uuid_1 = require("uuid");
|
|
7
7
|
const assert_1 = tslib_1.__importDefault(require("assert"));
|
|
8
|
+
const migration_1 = require("./migration");
|
|
8
9
|
class PostgreSQLConnector {
|
|
9
10
|
pool;
|
|
10
11
|
configuration;
|
|
11
12
|
txnDict;
|
|
13
|
+
txnExecQueueDict;
|
|
12
14
|
constructor(configuration) {
|
|
13
15
|
this.configuration = configuration;
|
|
14
16
|
this.txnDict = {};
|
|
17
|
+
this.txnExecQueueDict = {};
|
|
15
18
|
this.pool = new pg_1.Pool(configuration);
|
|
16
19
|
// 错误处理
|
|
17
20
|
this.pool.on('error', (err) => {
|
|
@@ -49,12 +52,14 @@ class PostgreSQLConnector {
|
|
|
49
52
|
}
|
|
50
53
|
await connection.query(beginStmt);
|
|
51
54
|
this.txnDict[id] = connection;
|
|
55
|
+
this.txnExecQueueDict[id] = Promise.resolve();
|
|
52
56
|
return id;
|
|
53
57
|
}
|
|
54
58
|
catch (error) {
|
|
55
59
|
// 如果启动事务失败,释放连接
|
|
56
60
|
connection.release();
|
|
57
61
|
delete this.txnDict[id];
|
|
62
|
+
delete this.txnExecQueueDict[id];
|
|
58
63
|
throw error;
|
|
59
64
|
}
|
|
60
65
|
}
|
|
@@ -80,37 +85,58 @@ class PostgreSQLConnector {
|
|
|
80
85
|
if (process.env.NODE_ENV === 'development') {
|
|
81
86
|
// console.log(`SQL: ${sql}; \n`);
|
|
82
87
|
}
|
|
83
|
-
let result;
|
|
84
88
|
if (txn) {
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
result
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
result = await this.pool.query(sql);
|
|
89
|
+
const result = await this.enqueueTxnQuery(txn, (connection) => connection.query(sql));
|
|
90
|
+
// 返回格式与 mysql2 兼容: [rows, fields/result]
|
|
91
|
+
return [result.rows, result];
|
|
91
92
|
}
|
|
93
|
+
const result = await this.pool.query(sql);
|
|
92
94
|
// 返回格式与 mysql2 兼容: [rows, fields/result]
|
|
93
95
|
return [result.rows, result];
|
|
94
96
|
}
|
|
97
|
+
async enqueueTxnQuery(txn, executor) {
|
|
98
|
+
const connection = this.txnDict[txn];
|
|
99
|
+
(0, assert_1.default)(connection, `Transaction ${txn} not found`);
|
|
100
|
+
return this.enqueueTxnTask(txn, () => executor(connection));
|
|
101
|
+
}
|
|
102
|
+
async enqueueTxnTask(txn, executor) {
|
|
103
|
+
const queued = this.txnExecQueueDict[txn] || Promise.resolve();
|
|
104
|
+
const task = queued
|
|
105
|
+
.catch(() => undefined)
|
|
106
|
+
.then(() => executor());
|
|
107
|
+
this.txnExecQueueDict[txn] = task.then(() => undefined, () => undefined);
|
|
108
|
+
return task;
|
|
109
|
+
}
|
|
110
|
+
async readSchema(translator) {
|
|
111
|
+
return (0, migration_1.readPostgreSqlSchema)(this, translator);
|
|
112
|
+
}
|
|
113
|
+
async inspectSchema(translator) {
|
|
114
|
+
return (0, migration_1.inspectPostgreSqlSchema)(this, translator);
|
|
115
|
+
}
|
|
116
|
+
diffSchema(currentSchema, targetSchema, translator, options) {
|
|
117
|
+
return (0, migration_1.buildPostgreSqlMigrationPlan)(currentSchema, targetSchema, translator, options);
|
|
118
|
+
}
|
|
95
119
|
async commitTransaction(txn) {
|
|
96
120
|
const connection = this.txnDict[txn];
|
|
97
121
|
(0, assert_1.default)(connection, `Transaction ${txn} not found`);
|
|
122
|
+
delete this.txnDict[txn];
|
|
98
123
|
try {
|
|
99
|
-
await connection.query('COMMIT');
|
|
124
|
+
await this.enqueueTxnTask(txn, () => connection.query('COMMIT'));
|
|
100
125
|
}
|
|
101
126
|
finally {
|
|
102
|
-
delete this.
|
|
127
|
+
delete this.txnExecQueueDict[txn];
|
|
103
128
|
connection.release();
|
|
104
129
|
}
|
|
105
130
|
}
|
|
106
131
|
async rollbackTransaction(txn) {
|
|
107
132
|
const connection = this.txnDict[txn];
|
|
108
133
|
(0, assert_1.default)(connection, `Transaction ${txn} not found`);
|
|
134
|
+
delete this.txnDict[txn];
|
|
109
135
|
try {
|
|
110
|
-
await connection.query('ROLLBACK');
|
|
136
|
+
await this.enqueueTxnTask(txn, () => connection.query('ROLLBACK'));
|
|
111
137
|
}
|
|
112
138
|
finally {
|
|
113
|
-
delete this.
|
|
139
|
+
delete this.txnExecQueueDict[txn];
|
|
114
140
|
connection.release();
|
|
115
141
|
}
|
|
116
142
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { EntityDict } from 'oak-domain/lib/types';
|
|
2
|
+
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
|
3
|
+
import { MigrationSchema, MigrationPlanningOptions, Plan, SchemaInspectionResult } from '../types/migration';
|
|
4
|
+
import { PostgreSQLConnector } from './connector';
|
|
5
|
+
import { PostgreSQLTranslator } from './translator';
|
|
6
|
+
type PostgreSqlMigrationEntityDict = EntityDict & BaseEntityDict;
|
|
7
|
+
export declare function readPostgreSqlSchema<ED extends PostgreSqlMigrationEntityDict>(connector: PostgreSQLConnector, translator: PostgreSQLTranslator<ED>): Promise<MigrationSchema<ED>>;
|
|
8
|
+
export declare function inspectPostgreSqlSchema<ED extends PostgreSqlMigrationEntityDict>(connector: PostgreSQLConnector, translator: PostgreSQLTranslator<ED>): Promise<SchemaInspectionResult<ED>>;
|
|
9
|
+
export declare function buildPostgreSqlMigrationPlan<ED extends PostgreSqlMigrationEntityDict>(currentSchema: MigrationSchema<ED>, targetSchema: MigrationSchema<ED>, translator: PostgreSQLTranslator<ED>, options?: MigrationPlanningOptions): Plan<ED>;
|
|
10
|
+
export {};
|