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.
@@ -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<any>, schemaNew: StorageSchema<any>): Plan;
66
+ diffSchema(schemaOld: StorageSchema<ED>, schemaNew: StorageSchema<ED>, options?: MigrationPlanningOptions): Plan<ED>;
50
67
  }
@@ -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].changedRows;
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
- async initialize(option) {
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
- for (const entity in schema) {
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.translator.readSchema((sql) => this.connector.exec(sql));
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 originSchema = await this.readSchema();
317
- const plan = this.diffSchema(originSchema, this.translator.schema);
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
- const plan = {
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
- private reTranslateToAttribute;
116
+ reTranslateToAttribute(type: string): Attribute;
113
117
  readSchema(execFn: (sql: string) => Promise<any>): Promise<StorageSchema<ED>>;
114
118
  }
@@ -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
- // 因为有deleteAt的存在,这里的unique没意义,只能框架自己去建立checker来处理
623
- /* if (unique) {
646
+ const physicalName = this.getPhysicalIndexName(String(entity), storageName || entity, name);
647
+ if (unique) {
624
648
  sql += ' unique ';
625
649
  }
626
- else */ if (type === 'fulltext') {
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 \`${name}\` `;
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 indexFrom === 'number') {
1073
- (0, assert_1.default)(typeof count === 'number');
1074
- sql += ` limit ${indexFrom}, ${count}`;
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 indexFrom === 'number') {
1091
- (0, assert_1.default)(typeof count === 'number');
1092
- sql += ` limit ${indexFrom}, ${count}`;
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 indexFrom === 'number') {
1109
- (0, assert_1.default)(typeof count === 'number');
1110
- sql += ` limit ${indexFrom}, ${count}`;
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
  /**