oak-db 3.3.12 → 3.3.14

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.
@@ -20,27 +20,22 @@ class MySqlConnector {
20
20
  return this.pool.end();
21
21
  }
22
22
  async startTransaction(option) {
23
- // 防止用户配置connection可复用
24
- const startInner = async () => {
25
- const connection = await this.pool.getConnection();
26
- // 分配出来的connection不能被别的事务占据
27
- for (const txn2 in this.txnDict) {
28
- if (this.txnDict[txn2] === connection) {
29
- return new Promise((resolve) => {
30
- this.pool.on('release', () => resolve(startInner()));
31
- });
32
- }
33
- }
23
+ const connection = await this.pool.getConnection();
24
+ const id = (0, uuid_1.v4)();
25
+ try {
34
26
  await connection.beginTransaction();
35
- const id = (0, uuid_1.v4)();
36
27
  // console.log('start_txn', id, connection.threadId);
37
28
  this.txnDict[id] = connection;
38
29
  if (option?.isolationLevel) {
39
30
  await connection.query(`SET TRANSACTION ISOLATION LEVEL ${option.isolationLevel};`);
40
31
  }
41
32
  return id;
42
- };
43
- return startInner();
33
+ }
34
+ catch (err) { // 出错时释放连接
35
+ connection.release();
36
+ delete this.txnDict[id];
37
+ throw err;
38
+ }
44
39
  }
45
40
  async exec(sql, txn) {
46
41
  if (process.env.NODE_ENV === 'development') {
@@ -1,4 +1,4 @@
1
- import { EntityDict, OperateOption, OperationResult, TxnOption, StorageSchema, SelectOption, AggregationResult, Attribute, Index } from 'oak-domain/lib/types';
1
+ import { EntityDict, OperateOption, OperationResult, TxnOption, StorageSchema, SelectOption, AggregationResult } from 'oak-domain/lib/types';
2
2
  import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
3
3
  import { CascadeStore } from 'oak-domain/lib/store/CascadeStore';
4
4
  import { MySQLConfiguration } from './types/Configuration';
@@ -7,7 +7,7 @@ import { MySqlTranslator, MySqlSelectOption, MysqlOperateOption } from './transl
7
7
  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
- import { DbStore } from '../types/dbStore';
10
+ import { DbStore, Plan } from '../types/dbStore';
11
11
  export declare class MysqlStore<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED>> extends CascadeStore<ED> implements DbStore<ED, Cxt> {
12
12
  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
13
  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']>;
@@ -22,9 +22,12 @@ export declare class MysqlStore<ED extends EntityDict & BaseEntityDict, Cxt exte
22
22
  aggregate<T extends keyof ED, OP extends SelectOption>(entity: T, aggregation: ED[T]['Aggregation'], context: Cxt, option: OP): Promise<AggregationResult<ED[T]['Schema']>>;
23
23
  protected supportManyToOneJoin(): boolean;
24
24
  protected supportMultipleCreate(): boolean;
25
+ protected supportUpdateReturning(): boolean;
25
26
  private formResult;
26
27
  protected selectAbjointRowAsync<T extends keyof ED>(entity: T, selection: ED[T]['Selection'], context: AsyncContext<ED>, option?: MySqlSelectOption): Promise<Partial<ED[T]['Schema']>[]>;
28
+ 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
29
  protected updateAbjointRowAsync<T extends keyof ED>(entity: T, operation: ED[T]['Operation'], context: AsyncContext<ED>, option?: MysqlOperateOption): Promise<number>;
30
+ 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
31
  operate<T extends keyof ED>(entity: T, operation: ED[T]['Operation'], context: Cxt, option: OperateOption): Promise<OperationResult<ED>>;
29
32
  select<T extends keyof ED>(entity: T, selection: ED[T]['Selection'], context: Cxt, option: SelectOption): Promise<Partial<ED[T]['Schema']>[]>;
30
33
  protected countAbjointRowAsync<T extends keyof ED>(entity: T, selection: Pick<ED[T]['Selection'], 'filter' | 'count'>, context: AsyncContext<ED>, option: SelectOption): Promise<number>;
@@ -48,16 +51,3 @@ export declare class MysqlStore<ED extends EntityDict & BaseEntityDict, Cxt exte
48
51
  */
49
52
  diffSchema(schemaOld: StorageSchema<any>, schemaNew: StorageSchema<any>): Plan;
50
53
  }
51
- type Plan = {
52
- newTables: Record<string, {
53
- attributes: Record<string, Attribute>;
54
- }>;
55
- newIndexes: Record<string, Index<any>[]>;
56
- updatedTables: Record<string, {
57
- attributes: Record<string, Attribute & {
58
- isNew: boolean;
59
- }>;
60
- }>;
61
- updatedIndexes: Record<string, Index<any>[]>;
62
- };
63
- export {};
@@ -61,6 +61,9 @@ class MysqlStore extends CascadeStore_1.CascadeStore {
61
61
  supportMultipleCreate() {
62
62
  return true;
63
63
  }
64
+ supportUpdateReturning() {
65
+ return false;
66
+ }
64
67
  formResult(entity, result) {
65
68
  const schema = this.getSchema();
66
69
  /* function resolveObject(r: Record<string, any>, path: string, value: any) {
@@ -236,6 +239,9 @@ class MysqlStore extends CascadeStore_1.CascadeStore {
236
239
  const result = await this.connector.exec(sql, context.getCurrentTxnId());
237
240
  return this.formResult(entity, result[0]);
238
241
  }
242
+ updateAbjointRowReturningSync(entity, operation, context, returning, option) {
243
+ throw new Error('Mysql store 不支持同步更新数据');
244
+ }
239
245
  async updateAbjointRowAsync(entity, operation, context, option) {
240
246
  const { translator, connector } = this;
241
247
  const { action } = operation;
@@ -251,7 +257,7 @@ class MysqlStore extends CascadeStore_1.CascadeStore {
251
257
  const sql = translator.translateRemove(entity, operation, option);
252
258
  const result = await connector.exec(sql, txn);
253
259
  // todo 这里对sorter和indexfrom/count的支持不完整
254
- return result[0].changedRows;
260
+ return result[0].affectedRows;
255
261
  }
256
262
  default: {
257
263
  (0, assert_1.default)(!['select', 'download', 'stat'].includes(action));
@@ -262,6 +268,9 @@ class MysqlStore extends CascadeStore_1.CascadeStore {
262
268
  }
263
269
  }
264
270
  }
271
+ async updateAbjointRowReturningAsync(entity, operation, context, returning, option) {
272
+ throw new Error('MySQL store不支持返回更新数据');
273
+ }
265
274
  async operate(entity, operation, context, option) {
266
275
  const { action } = operation;
267
276
  (0, assert_1.default)(!['select', 'download', 'stat'].includes(action), '现在不支持使用select operation');
@@ -354,7 +363,7 @@ class MysqlStore extends CascadeStore_1.CascadeStore {
354
363
  };
355
364
  for (const attr in attributesNew) {
356
365
  if (attributes[attr]) {
357
- // 因为反向无法复原原来定义的attribute类型,这里就比较两次创建的sql是不是一致。
366
+ // 因为反向无法复原原来定义的attribute类型,这里就比较两次创建的sql是不是一致,不是太好的设计。
358
367
  const sql1 = this.translator.translateAttributeDef(attr, attributesNew[attr]);
359
368
  const sql2 = this.translator.translateAttributeDef(attr, attributes[attr]);
360
369
  if (!this.translator.compareSql(sql1, sql2)) {
@@ -872,8 +872,10 @@ class MySqlTranslator extends sqlTranslator_1.SqlTranslator {
872
872
  const refId = (expr)['#refId'];
873
873
  const refAttr = (expr)['#refAttr'];
874
874
  (0, assert_1.default)(refDict[refId]);
875
- const attrText = `\`${refDict[refId][0]}\`.\`${refAttr}\``;
876
- result = this.translateAttrInExpression(entity, (expr)['#refAttr'], attrText);
875
+ const [refAlias, refEntity] = refDict[refId];
876
+ const attrText = `\`${refAlias}\`.\`${refAttr}\``;
877
+ // 这里必须使用refEntity,否则在filter深层嵌套节点表达式时会出现entity不对应
878
+ result = this.translateAttrInExpression(refEntity, (expr)['#refAttr'], attrText);
877
879
  }
878
880
  else {
879
881
  (0, assert_1.default)(k.length === 1);
@@ -1067,9 +1069,12 @@ class MySqlTranslator extends sqlTranslator_1.SqlTranslator {
1067
1069
  if (sorterText) {
1068
1070
  sql += ` order by ${sorterText}`;
1069
1071
  }
1070
- if (typeof indexFrom === 'number') {
1071
- (0, assert_1.default)(typeof count === 'number');
1072
- sql += ` limit ${indexFrom}, ${count}`;
1072
+ if (typeof count === 'number') {
1073
+ // MySQL UPDATE 不支持 LIMIT offset, count 语法
1074
+ if (typeof indexFrom === 'number' && indexFrom > 0) {
1075
+ throw new Error('MySQL does not support LIMIT with OFFSET in UPDATE statements. Use indexFrom=0 or omit indexFrom.');
1076
+ }
1077
+ sql += ` limit ${count}`;
1073
1078
  }
1074
1079
  return sql;
1075
1080
  }
@@ -1085,9 +1090,12 @@ class MySqlTranslator extends sqlTranslator_1.SqlTranslator {
1085
1090
  if (sorterText) {
1086
1091
  sql += ` order by ${sorterText}`;
1087
1092
  }
1088
- if (typeof indexFrom === 'number') {
1089
- (0, assert_1.default)(typeof count === 'number');
1090
- sql += ` limit ${indexFrom}, ${count}`;
1093
+ if (typeof count === 'number') {
1094
+ // MySQL DELETE 不支持 LIMIT offset, count 语法
1095
+ if (typeof indexFrom === 'number' && indexFrom > 0) {
1096
+ throw new Error('MySQL does not support LIMIT with OFFSET in DELETE statements. Use indexFrom=0 or omit indexFrom.');
1097
+ }
1098
+ sql += ` limit ${count}`;
1091
1099
  }
1092
1100
  return sql;
1093
1101
  }
@@ -1103,9 +1111,12 @@ class MySqlTranslator extends sqlTranslator_1.SqlTranslator {
1103
1111
  if (sorterText) {
1104
1112
  sql += ` order by ${sorterText}`;
1105
1113
  }
1106
- if (typeof indexFrom === 'number') {
1107
- (0, assert_1.default)(typeof count === 'number');
1108
- sql += ` limit ${indexFrom}, ${count}`;
1114
+ if (typeof count === 'number') {
1115
+ // MySQL UPDATE 不支持 LIMIT offset, count 语法(软删除使用UPDATE)
1116
+ if (typeof indexFrom === 'number' && indexFrom > 0) {
1117
+ throw new Error('MySQL does not support LIMIT with OFFSET in UPDATE statements (soft delete). Use indexFrom=0 or omit indexFrom.');
1118
+ }
1119
+ sql += ` limit ${count}`;
1109
1120
  }
1110
1121
  return sql;
1111
1122
  }
@@ -16,10 +16,6 @@ export declare class PostgreSQLConnector {
16
16
  exec(sql: string, txn?: string): Promise<[QueryResultRow[], QueryResult]>;
17
17
  commitTransaction(txn: string): Promise<void>;
18
18
  rollbackTransaction(txn: string): Promise<void>;
19
- /**
20
- * 执行多条 SQL 语句(用于初始化等场景)
21
- */
22
- execBatch(sqls: string[], txn?: string): Promise<void>;
23
19
  /**
24
20
  * 获取连接池状态
25
21
  */
@@ -36,37 +36,27 @@ class PostgreSQLConnector {
36
36
  await this.pool.end();
37
37
  }
38
38
  async startTransaction(option) {
39
- const startInner = async () => {
40
- const connection = await this.pool.connect();
41
- // 添加:检测连接是否已被其他事务占用
42
- for (const txn2 in this.txnDict) {
43
- if (this.txnDict[txn2] === connection) {
44
- return new Promise((resolve) => {
45
- this.pool.on('release', () => resolve(startInner()));
46
- });
47
- }
48
- }
49
- try {
50
- let beginStmt = 'BEGIN';
51
- if (option?.isolationLevel) {
52
- // PostgreSQL 隔离级别:
53
- // READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE
54
- // 注意: PostgreSQL 的 READ UNCOMMITTED 行为等同于 READ COMMITTED
55
- const level = this.mapIsolationLevel(option.isolationLevel);
56
- beginStmt = `BEGIN ISOLATION LEVEL ${level}`;
57
- }
58
- await connection.query(beginStmt);
59
- const id = (0, uuid_1.v4)();
60
- this.txnDict[id] = connection;
61
- return id;
62
- }
63
- catch (error) {
64
- // 如果启动事务失败,释放连接
65
- connection.release();
66
- throw error;
39
+ const connection = await this.pool.connect();
40
+ const id = (0, uuid_1.v4)();
41
+ try {
42
+ let beginStmt = 'BEGIN';
43
+ if (option?.isolationLevel) {
44
+ // PostgreSQL 隔离级别:
45
+ // READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE
46
+ // 注意: PostgreSQL 的 READ UNCOMMITTED 行为等同于 READ COMMITTED
47
+ const level = this.mapIsolationLevel(option.isolationLevel);
48
+ beginStmt = `BEGIN ISOLATION LEVEL ${level}`;
67
49
  }
68
- };
69
- return startInner();
50
+ await connection.query(beginStmt);
51
+ this.txnDict[id] = connection;
52
+ return id;
53
+ }
54
+ catch (error) {
55
+ // 如果启动事务失败,释放连接
56
+ connection.release();
57
+ delete this.txnDict[id];
58
+ throw error;
59
+ }
70
60
  }
71
61
  /**
72
62
  * 映射隔离级别到 PostgreSQL 语法
@@ -90,26 +80,17 @@ class PostgreSQLConnector {
90
80
  if (process.env.NODE_ENV === 'development') {
91
81
  // console.log(`SQL: ${sql}; \n`);
92
82
  }
93
- try {
94
- let result;
95
- if (txn) {
96
- const connection = this.txnDict[txn];
97
- (0, assert_1.default)(connection, `Transaction ${txn} not found`);
98
- result = await connection.query(sql);
99
- }
100
- else {
101
- result = await this.pool.query(sql);
102
- }
103
- // 返回格式与 mysql2 兼容: [rows, fields/result]
104
- return [result.rows, result];
83
+ let result;
84
+ if (txn) {
85
+ const connection = this.txnDict[txn];
86
+ (0, assert_1.default)(connection, `Transaction ${txn} not found`);
87
+ result = await connection.query(sql);
105
88
  }
106
- catch (error) {
107
- // 增强错误信息
108
- const enhancedError = new Error(`PostgreSQL query failed: ${error.message}\nSQL: ${sql.slice(0, 500)}${sql.length > 500 ? '...' : ''}`);
109
- enhancedError.originalError = error;
110
- enhancedError.sql = sql;
111
- throw enhancedError;
89
+ else {
90
+ result = await this.pool.query(sql);
112
91
  }
92
+ // 返回格式与 mysql2 兼容: [rows, fields/result]
93
+ return [result.rows, result];
113
94
  }
114
95
  async commitTransaction(txn) {
115
96
  const connection = this.txnDict[txn];
@@ -133,16 +114,6 @@ class PostgreSQLConnector {
133
114
  connection.release();
134
115
  }
135
116
  }
136
- /**
137
- * 执行多条 SQL 语句(用于初始化等场景)
138
- */
139
- async execBatch(sqls, txn) {
140
- for (const sql of sqls) {
141
- if (sql.trim()) {
142
- await this.exec(sql, txn);
143
- }
144
- }
145
- }
146
117
  /**
147
118
  * 获取连接池状态
148
119
  */
@@ -7,7 +7,7 @@ import { PostgreSQLTranslator, PostgreSQLSelectOption, PostgreSQLOperateOption }
7
7
  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
- import { DbStore } from '../types/dbStore';
10
+ import { DbStore, Plan } from '../types/dbStore';
11
11
  export declare class PostgreSQLStore<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED>> extends CascadeStore<ED> implements DbStore<ED, Cxt> {
12
12
  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
13
  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']>;
@@ -22,9 +22,12 @@ export declare class PostgreSQLStore<ED extends EntityDict & BaseEntityDict, Cxt
22
22
  aggregate<T extends keyof ED, OP extends SelectOption>(entity: T, aggregation: ED[T]['Aggregation'], context: Cxt, option: OP): Promise<AggregationResult<ED[T]['Schema']>>;
23
23
  protected supportManyToOneJoin(): boolean;
24
24
  protected supportMultipleCreate(): boolean;
25
+ protected supportUpdateReturning(): boolean;
25
26
  private formResult;
26
27
  protected selectAbjointRowAsync<T extends keyof ED>(entity: T, selection: ED[T]['Selection'], context: AsyncContext<ED>, option?: PostgreSQLSelectOption): Promise<Partial<ED[T]['Schema']>[]>;
27
28
  protected updateAbjointRowAsync<T extends keyof ED>(entity: T, operation: ED[T]['Operation'], context: AsyncContext<ED>, option?: PostgreSQLOperateOption): Promise<number>;
29
+ protected updateAbjointRowReturningSync<T extends keyof ED>(entity: T, operation: ED[T]['Operation'], context: AsyncContext<ED>, returning: Record<string, any>, option?: PostgreSQLOperateOption): [number, Partial<ED[T]['Schema']>[]];
30
+ protected updateAbjointRowReturningAsync<T extends keyof ED>(entity: T, operation: ED[T]['Operation'], context: AsyncContext<ED>, returning: Record<string, any>, option?: PostgreSQLOperateOption): Promise<[number, Partial<ED[T]['Schema']>[]]>;
28
31
  operate<T extends keyof ED>(entity: T, operation: ED[T]['Operation'], context: Cxt, option: OperateOption): Promise<OperationResult<ED>>;
29
32
  select<T extends keyof ED>(entity: T, selection: ED[T]['Selection'], context: Cxt, option: SelectOption): Promise<Partial<ED[T]['Schema']>[]>;
30
33
  protected countAbjointRowAsync<T extends keyof ED>(entity: T, selection: Pick<ED[T]['Selection'], 'filter' | 'count'>, context: AsyncContext<ED>, option: SelectOption): Promise<number>;
@@ -35,4 +38,16 @@ export declare class PostgreSQLStore<ED extends EntityDict & BaseEntityDict, Cxt
35
38
  connect(): Promise<void>;
36
39
  disconnect(): Promise<void>;
37
40
  initialize(option: CreateEntityOption): Promise<void>;
41
+ readSchema(): Promise<StorageSchema<ED>>;
42
+ /**
43
+ * 根据载入的dataSchema,和数据库中原来的schema,决定如何来upgrade
44
+ * 制订出来的plan分为两阶段:增加阶段和削减阶段,在两个阶段之间,由用户来修正数据
45
+ */
46
+ makeUpgradePlan(): Promise<Plan>;
47
+ /**
48
+ * 比较两个schema的不同,这里计算的是new对old的增量
49
+ * @param schemaOld
50
+ * @param schemaNew
51
+ */
52
+ diffSchema(schemaOld: StorageSchema<any>, schemaNew: StorageSchema<any>): Plan;
38
53
  }
@@ -8,6 +8,12 @@ const translator_1 = require("./translator");
8
8
  const lodash_1 = require("lodash");
9
9
  const assert_1 = tslib_1.__importDefault(require("assert"));
10
10
  const relation_1 = require("oak-domain/lib/store/relation");
11
+ const ToNumberAttrs = new Set([
12
+ '$$seq$$',
13
+ '$$createAt$$',
14
+ '$$updateAt$$',
15
+ '$$deleteAt$$',
16
+ ]);
11
17
  function convertGeoTextToObject(geoText) {
12
18
  if (geoText.startsWith('POINT')) {
13
19
  const coord = geoText.match(/(-?\d+\.?\d*)/g);
@@ -88,6 +94,9 @@ class PostgreSQLStore extends CascadeStore_1.CascadeStore {
88
94
  supportMultipleCreate() {
89
95
  return true;
90
96
  }
97
+ supportUpdateReturning() {
98
+ return true;
99
+ }
91
100
  formResult(entity, result) {
92
101
  const schema = this.getSchema();
93
102
  function resolveAttribute(entity2, r, attr, value) {
@@ -121,12 +130,19 @@ class PostgreSQLStore extends CascadeStore_1.CascadeStore {
121
130
  const { type } = attributes[attr];
122
131
  switch (type) {
123
132
  case 'date':
124
- case 'time': {
133
+ case 'time':
134
+ case 'datetime': {
125
135
  if (value instanceof Date) {
126
136
  r[attr] = value.valueOf();
127
137
  }
128
138
  else {
129
- r[attr] = value;
139
+ if (typeof value === 'string') {
140
+ r[attr] = parseInt(value, 10);
141
+ }
142
+ else {
143
+ (0, assert_1.default)(typeof value === 'number' || value === null);
144
+ r[attr] = value;
145
+ }
130
146
  }
131
147
  break;
132
148
  }
@@ -203,6 +219,14 @@ class PostgreSQLStore extends CascadeStore_1.CascadeStore {
203
219
  // PostgreSQL count 返回字符串
204
220
  r[attr] = parseInt(value, 10);
205
221
  }
222
+ else if (attr.startsWith("#sum") || attr.startsWith("#avg") || attr.startsWith("#min") || attr.startsWith("#max")) {
223
+ // PostgreSQL sum/avg/min/max 返回字符串
224
+ r[attr] = parseFloat(value);
225
+ }
226
+ else if (ToNumberAttrs.has(attr)) {
227
+ // PostgreSQL sum/avg/min/max 返回字符串
228
+ r[attr] = parseInt(value, 10);
229
+ }
206
230
  else {
207
231
  r[attr] = value;
208
232
  }
@@ -281,6 +305,33 @@ class PostgreSQLStore extends CascadeStore_1.CascadeStore {
281
305
  }
282
306
  }
283
307
  }
308
+ updateAbjointRowReturningSync(entity, operation, context, returning, option) {
309
+ throw new Error('PostgreSQL store 不支持同步更新数据');
310
+ }
311
+ async updateAbjointRowReturningAsync(entity, operation, context, returning, option) {
312
+ const { translator, connector } = this;
313
+ const { action } = operation;
314
+ const txn = context.getCurrentTxnId();
315
+ switch (action) {
316
+ case 'create': {
317
+ const { data } = operation;
318
+ const sql = translator.translateInsert(entity, data instanceof Array ? data : [data]);
319
+ const result = await connector.exec(sql, txn);
320
+ return [result[1].rowCount || 0, []];
321
+ }
322
+ case 'remove': {
323
+ const sql = translator.translateRemove(entity, operation, Object.assign({}, option, { returning }));
324
+ const result = await connector.exec(sql, txn);
325
+ return [result[1].rowCount || 0, this.formResult(entity, result[0])];
326
+ }
327
+ default: {
328
+ (0, assert_1.default)(!['select', 'download', 'stat'].includes(action));
329
+ const sql = translator.translateUpdate(entity, operation, Object.assign({}, option, { returning }));
330
+ const result = await connector.exec(sql, txn);
331
+ return [result[1].rowCount || 0, this.formResult(entity, result[0])];
332
+ }
333
+ }
334
+ }
284
335
  async operate(entity, operation, context, option) {
285
336
  const { action } = operation;
286
337
  (0, assert_1.default)(!['select', 'download', 'stat'].includes(action), '不支持使用 select operation');
@@ -316,14 +367,203 @@ class PostgreSQLStore extends CascadeStore_1.CascadeStore {
316
367
  }
317
368
  async initialize(option) {
318
369
  const schema = this.getSchema();
319
- // 可选:先创建 PostGIS 扩展
320
- // await this.connector.exec('CREATE EXTENSION IF NOT EXISTS postgis;');
370
+ // ===== 第一阶段:事务外创建扩展 =====
371
+ let hasGeoType = false;
372
+ let hasChineseTsConfig = false;
373
+ let chineseParser = null;
374
+ // 扫描 schema
321
375
  for (const entity in schema) {
322
- const sqls = this.translator.translateCreateEntity(entity, option);
323
- for (const sql of sqls) {
324
- await this.connector.exec(sql);
376
+ const { attributes, indexes } = schema[entity];
377
+ for (const attr in attributes) {
378
+ if (attributes[attr].type === 'geometry') {
379
+ hasGeoType = true;
380
+ }
381
+ }
382
+ for (const index of indexes || []) {
383
+ if (index.config?.tsConfig === 'chinese' || index.config?.tsConfig?.includes('chinese')) {
384
+ hasChineseTsConfig = true;
385
+ }
386
+ if (index.config?.chineseParser) {
387
+ (0, assert_1.default)(!chineseParser || chineseParser === index.config.chineseParser, '当前定义了多个中文分词器,请保持一致');
388
+ chineseParser = index.config.chineseParser;
389
+ }
390
+ }
391
+ }
392
+ // 在事务外创建扩展
393
+ if (hasGeoType) {
394
+ console.log('Initializing PostGIS extension for geometry support...');
395
+ await this.connector.exec('CREATE EXTENSION IF NOT EXISTS postgis;');
396
+ }
397
+ if (hasChineseTsConfig) {
398
+ console.log('Initializing Chinese parser extension...');
399
+ await this.connector.exec(`CREATE EXTENSION IF NOT EXISTS ${chineseParser || 'zhparser'};`);
400
+ }
401
+ // ===== 第二阶段:事务内创建配置和表 =====
402
+ const txn = await this.connector.startTransaction({
403
+ isolationLevel: 'serializable',
404
+ });
405
+ try {
406
+ // 创建中文文本搜索配置
407
+ if (hasChineseTsConfig) {
408
+ console.log('Initializing Chinese text search configuration...');
409
+ const checkChineseConfigSql = `
410
+ SELECT COUNT(*) as cnt
411
+ FROM pg_catalog.pg_ts_config
412
+ WHERE cfgname = 'chinese';
413
+ `;
414
+ const result = await this.connector.exec(checkChineseConfigSql, txn);
415
+ const count = parseInt(result[0][0]?.cnt || '0', 10);
416
+ if (count === 0) {
417
+ const createChineseConfigSql = `
418
+ CREATE TEXT SEARCH CONFIGURATION chinese (PARSER = ${chineseParser || 'zhparser'});
419
+ ALTER TEXT SEARCH CONFIGURATION chinese ADD MAPPING FOR n,v,a,i,e,l WITH simple;
420
+ `;
421
+ await this.connector.exec(createChineseConfigSql, txn);
422
+ }
423
+ }
424
+ // 创建实体表
425
+ for (const entity in schema) {
426
+ const sqls = this.translator.translateCreateEntity(entity, option);
427
+ for (const sql of sqls) {
428
+ await this.connector.exec(sql, txn);
429
+ }
430
+ }
431
+ await this.connector.commitTransaction(txn);
432
+ }
433
+ catch (error) {
434
+ await this.connector.rollbackTransaction(txn);
435
+ throw error;
436
+ }
437
+ }
438
+ // 从数据库中读取当前schema
439
+ readSchema() {
440
+ return this.translator.readSchema((sql) => this.connector.exec(sql));
441
+ }
442
+ /**
443
+ * 根据载入的dataSchema,和数据库中原来的schema,决定如何来upgrade
444
+ * 制订出来的plan分为两阶段:增加阶段和削减阶段,在两个阶段之间,由用户来修正数据
445
+ */
446
+ async makeUpgradePlan() {
447
+ const originSchema = await this.readSchema();
448
+ const plan = this.diffSchema(originSchema, this.translator.schema);
449
+ return plan;
450
+ }
451
+ /**
452
+ * 比较两个schema的不同,这里计算的是new对old的增量
453
+ * @param schemaOld
454
+ * @param schemaNew
455
+ */
456
+ diffSchema(schemaOld, schemaNew) {
457
+ const plan = {
458
+ newTables: {},
459
+ newIndexes: {},
460
+ updatedIndexes: {},
461
+ updatedTables: {},
462
+ };
463
+ for (const table in schemaNew) {
464
+ // PostgreSQL 表名区分大小写(使用双引号时)
465
+ if (schemaOld[table]) {
466
+ const { attributes, indexes } = schemaOld[table];
467
+ const { attributes: attributesNew, indexes: indexesNew } = schemaNew[table];
468
+ const assignToUpdateTables = (attr, isNew) => {
469
+ const skipAttrs = ['$$seq$$', '$$createAt$$', '$$updateAt$$', '$$deleteAt$$', 'id'];
470
+ if (skipAttrs.includes(attr)) {
471
+ return;
472
+ }
473
+ if (!plan.updatedTables[table]) {
474
+ plan.updatedTables[table] = {
475
+ attributes: {
476
+ [attr]: {
477
+ ...attributesNew[attr],
478
+ isNew,
479
+ }
480
+ }
481
+ };
482
+ }
483
+ else {
484
+ plan.updatedTables[table].attributes[attr] = {
485
+ ...attributesNew[attr],
486
+ isNew,
487
+ };
488
+ }
489
+ };
490
+ for (const attr in attributesNew) {
491
+ if (attributes[attr]) {
492
+ // 比较两次创建的属性定义是否一致
493
+ const sql1 = this.translator.translateAttributeDef(attr, attributesNew[attr]);
494
+ const sql2 = this.translator.translateAttributeDef(attr, attributes[attr]);
495
+ if (!this.translator.compareSql(sql1, sql2)) {
496
+ assignToUpdateTables(attr, false);
497
+ }
498
+ }
499
+ else {
500
+ assignToUpdateTables(attr, true);
501
+ }
502
+ }
503
+ if (indexesNew) {
504
+ const assignToIndexes = (index, isNew) => {
505
+ if (isNew) {
506
+ if (plan.newIndexes[table]) {
507
+ plan.newIndexes[table].push(index);
508
+ }
509
+ else {
510
+ plan.newIndexes[table] = [index];
511
+ }
512
+ }
513
+ else {
514
+ if (plan.updatedIndexes[table]) {
515
+ plan.updatedIndexes[table].push(index);
516
+ }
517
+ else {
518
+ plan.updatedIndexes[table] = [index];
519
+ }
520
+ }
521
+ };
522
+ const compareConfig = (config1, config2) => {
523
+ const unique1 = config1?.unique || false;
524
+ const unique2 = config2?.unique || false;
525
+ if (unique1 !== unique2) {
526
+ return false;
527
+ }
528
+ const type1 = config1?.type || 'btree';
529
+ const type2 = config2?.type || 'btree';
530
+ // tsConfig 比较
531
+ const tsConfig1 = config1?.tsConfig;
532
+ const tsConfig2 = config2?.tsConfig;
533
+ if (JSON.stringify(tsConfig1) !== JSON.stringify(tsConfig2)) {
534
+ return false;
535
+ }
536
+ return type1 === type2;
537
+ };
538
+ for (const index of indexesNew) {
539
+ const { name, config, attributes: indexAttrs } = index;
540
+ const origin = indexes?.find(ele => ele.name === name);
541
+ if (origin) {
542
+ if (JSON.stringify(indexAttrs) !== JSON.stringify(origin.attributes)) {
543
+ assignToIndexes(index, false);
544
+ }
545
+ else {
546
+ if (!compareConfig(config, origin.config)) {
547
+ assignToIndexes(index, false);
548
+ }
549
+ }
550
+ }
551
+ else {
552
+ assignToIndexes(index, true);
553
+ }
554
+ }
555
+ }
556
+ }
557
+ else {
558
+ plan.newTables[table] = {
559
+ attributes: schemaNew[table].attributes,
560
+ };
561
+ if (schemaNew[table].indexes) {
562
+ plan.newIndexes[table] = schemaNew[table].indexes;
563
+ }
325
564
  }
326
565
  }
566
+ return plan;
327
567
  }
328
568
  }
329
569
  exports.PostgreSQLStore = PostgreSQLStore;