oak-db 3.3.13 → 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.
- package/lib/MySQL/connector.js +9 -14
- package/lib/MySQL/store.d.ts +3 -0
- package/lib/MySQL/store.js +10 -1
- package/lib/MySQL/translator.js +18 -9
- package/lib/PostgreSQL/connector.js +29 -48
- package/lib/PostgreSQL/store.d.ts +3 -0
- package/lib/PostgreSQL/store.js +72 -30
- package/lib/PostgreSQL/translator.d.ts +18 -6
- package/lib/PostgreSQL/translator.js +83 -217
- package/lib/sqlTranslator.d.ts +5 -1
- package/lib/sqlTranslator.js +12 -4
- package/lib/types/dbStore.d.ts +1 -0
- package/package.json +2 -2
package/lib/MySQL/connector.js
CHANGED
|
@@ -20,27 +20,22 @@ class MySqlConnector {
|
|
|
20
20
|
return this.pool.end();
|
|
21
21
|
}
|
|
22
22
|
async startTransaction(option) {
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
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
|
-
|
|
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') {
|
package/lib/MySQL/store.d.ts
CHANGED
|
@@ -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>;
|
package/lib/MySQL/store.js
CHANGED
|
@@ -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].
|
|
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');
|
package/lib/MySQL/translator.js
CHANGED
|
@@ -1069,9 +1069,12 @@ class MySqlTranslator extends sqlTranslator_1.SqlTranslator {
|
|
|
1069
1069
|
if (sorterText) {
|
|
1070
1070
|
sql += ` order by ${sorterText}`;
|
|
1071
1071
|
}
|
|
1072
|
-
if (typeof
|
|
1073
|
-
|
|
1074
|
-
|
|
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}`;
|
|
1075
1078
|
}
|
|
1076
1079
|
return sql;
|
|
1077
1080
|
}
|
|
@@ -1087,9 +1090,12 @@ class MySqlTranslator extends sqlTranslator_1.SqlTranslator {
|
|
|
1087
1090
|
if (sorterText) {
|
|
1088
1091
|
sql += ` order by ${sorterText}`;
|
|
1089
1092
|
}
|
|
1090
|
-
if (typeof
|
|
1091
|
-
|
|
1092
|
-
|
|
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}`;
|
|
1093
1099
|
}
|
|
1094
1100
|
return sql;
|
|
1095
1101
|
}
|
|
@@ -1105,9 +1111,12 @@ class MySqlTranslator extends sqlTranslator_1.SqlTranslator {
|
|
|
1105
1111
|
if (sorterText) {
|
|
1106
1112
|
sql += ` order by ${sorterText}`;
|
|
1107
1113
|
}
|
|
1108
|
-
if (typeof
|
|
1109
|
-
|
|
1110
|
-
|
|
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}`;
|
|
1111
1120
|
}
|
|
1112
1121
|
return sql;
|
|
1113
1122
|
}
|
|
@@ -36,37 +36,27 @@ class PostgreSQLConnector {
|
|
|
36
36
|
await this.pool.end();
|
|
37
37
|
}
|
|
38
38
|
async startTransaction(option) {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
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];
|
|
@@ -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>;
|
package/lib/PostgreSQL/store.js
CHANGED
|
@@ -94,6 +94,9 @@ class PostgreSQLStore extends CascadeStore_1.CascadeStore {
|
|
|
94
94
|
supportMultipleCreate() {
|
|
95
95
|
return true;
|
|
96
96
|
}
|
|
97
|
+
supportUpdateReturning() {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
97
100
|
formResult(entity, result) {
|
|
98
101
|
const schema = this.getSchema();
|
|
99
102
|
function resolveAttribute(entity2, r, attr, value) {
|
|
@@ -302,6 +305,33 @@ class PostgreSQLStore extends CascadeStore_1.CascadeStore {
|
|
|
302
305
|
}
|
|
303
306
|
}
|
|
304
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
|
+
}
|
|
305
335
|
async operate(entity, operation, context, option) {
|
|
306
336
|
const { action } = operation;
|
|
307
337
|
(0, assert_1.default)(!['select', 'download', 'stat'].includes(action), '不支持使用 select operation');
|
|
@@ -336,50 +366,62 @@ class PostgreSQLStore extends CascadeStore_1.CascadeStore {
|
|
|
336
366
|
await this.connector.disconnect();
|
|
337
367
|
}
|
|
338
368
|
async initialize(option) {
|
|
339
|
-
|
|
369
|
+
const schema = this.getSchema();
|
|
370
|
+
// ===== 第一阶段:事务外创建扩展 =====
|
|
371
|
+
let hasGeoType = false;
|
|
372
|
+
let hasChineseTsConfig = false;
|
|
373
|
+
let chineseParser = null;
|
|
374
|
+
// 扫描 schema
|
|
375
|
+
for (const entity in schema) {
|
|
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
|
+
// ===== 第二阶段:事务内创建配置和表 =====
|
|
340
402
|
const txn = await this.connector.startTransaction({
|
|
341
403
|
isolationLevel: 'serializable',
|
|
342
404
|
});
|
|
343
405
|
try {
|
|
344
|
-
|
|
345
|
-
let hasGeoType = false;
|
|
346
|
-
let hasChineseTsConfig = false;
|
|
347
|
-
for (const entity in schema) {
|
|
348
|
-
const { attributes, indexes } = schema[entity];
|
|
349
|
-
for (const attr in attributes) {
|
|
350
|
-
const { type } = attributes[attr];
|
|
351
|
-
if (type === 'geometry') {
|
|
352
|
-
hasGeoType = true;
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
for (const index of indexes || []) {
|
|
356
|
-
if (index.config?.tsConfig === 'chinese' || index.config?.tsConfig?.includes('chinese')) {
|
|
357
|
-
hasChineseTsConfig = true;
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
if (hasGeoType) {
|
|
362
|
-
console.log('Initializing PostGIS extension for geometry support...');
|
|
363
|
-
await this.connector.exec('CREATE EXTENSION IF NOT EXISTS postgis;');
|
|
364
|
-
}
|
|
406
|
+
// 创建中文文本搜索配置
|
|
365
407
|
if (hasChineseTsConfig) {
|
|
366
408
|
console.log('Initializing Chinese text search configuration...');
|
|
367
409
|
const checkChineseConfigSql = `
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
410
|
+
SELECT COUNT(*) as cnt
|
|
411
|
+
FROM pg_catalog.pg_ts_config
|
|
412
|
+
WHERE cfgname = 'chinese';
|
|
371
413
|
`;
|
|
372
|
-
const result = await this.connector.exec(checkChineseConfigSql);
|
|
414
|
+
const result = await this.connector.exec(checkChineseConfigSql, txn);
|
|
373
415
|
const count = parseInt(result[0][0]?.cnt || '0', 10);
|
|
374
416
|
if (count === 0) {
|
|
375
417
|
const createChineseConfigSql = `
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
ALTER TEXT SEARCH CONFIGURATION chinese ADD MAPPING FOR n,v,a,i,e,l WITH simple;
|
|
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;
|
|
379
420
|
`;
|
|
380
|
-
await this.connector.exec(createChineseConfigSql);
|
|
421
|
+
await this.connector.exec(createChineseConfigSql, txn);
|
|
381
422
|
}
|
|
382
423
|
}
|
|
424
|
+
// 创建实体表
|
|
383
425
|
for (const entity in schema) {
|
|
384
426
|
const sqls = this.translator.translateCreateEntity(entity, option);
|
|
385
427
|
for (const sql of sqls) {
|
|
@@ -6,6 +6,12 @@ import { CreateEntityOption } from '../types/Translator';
|
|
|
6
6
|
export interface PostgreSQLSelectOption extends SqlSelectOption {
|
|
7
7
|
}
|
|
8
8
|
export interface PostgreSQLOperateOption extends SqlOperateOption {
|
|
9
|
+
/**
|
|
10
|
+
* PostgreSQL RETURNING 子句的投影
|
|
11
|
+
* 仅在 update/remove 时有效
|
|
12
|
+
* 返回受影响的行
|
|
13
|
+
*/
|
|
14
|
+
returning?: Record<string, any>;
|
|
9
15
|
}
|
|
10
16
|
export declare class PostgreSQLTranslator<ED extends EntityDict & BaseEntityDict> extends SqlTranslator<ED> {
|
|
11
17
|
private getEnumTypeName;
|
|
@@ -63,16 +69,22 @@ export declare class PostgreSQLTranslator<ED extends EntityDict & BaseEntityDict
|
|
|
63
69
|
protected translateExpression<T extends keyof ED>(entity: T, alias: string, expression: RefOrExpression<keyof ED[T]["OpSchema"]>, refDict: Record<string, [string, keyof ED]>): string;
|
|
64
70
|
protected populateSelectStmt<T extends keyof ED>(projectionText: string, fromText: string, aliasDict: Record<string, string>, filterText: string, sorterText?: string, groupByText?: string, indexFrom?: number, count?: number, option?: PostgreSQLSelectOption): string;
|
|
65
71
|
translateUpdate<T extends keyof ED, OP extends SqlOperateOption>(entity: T, operation: ED[T]['Update'], option?: OP): string;
|
|
66
|
-
translateRemove<T extends keyof ED, OP extends SqlOperateOption>(entity: T, operation: ED[T]['Remove'], option?: OP): string;
|
|
67
72
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
73
|
+
* 将 projection 转换为 RETURNING 子句的列列表
|
|
74
|
+
* @param entity 实体名
|
|
75
|
+
* @param alias 表别名
|
|
76
|
+
* @param projection 投影定义
|
|
70
77
|
*/
|
|
71
|
-
private
|
|
78
|
+
private buildReturningClause;
|
|
72
79
|
/**
|
|
73
|
-
*
|
|
80
|
+
* 验证操作参数的合法性
|
|
74
81
|
*/
|
|
75
|
-
private
|
|
82
|
+
private validateOperationParams;
|
|
83
|
+
/**
|
|
84
|
+
* 添加RETURNING子句
|
|
85
|
+
*/
|
|
86
|
+
private appendReturningClause;
|
|
87
|
+
translateRemove<T extends keyof ED, OP extends SqlOperateOption>(entity: T, operation: ED[T]['Remove'], option?: OP): string;
|
|
76
88
|
/**
|
|
77
89
|
* 生成 PostgreSQL UPSERT 语句
|
|
78
90
|
* INSERT ... ON CONFLICT (key) DO UPDATE SET ...
|
|
@@ -7,7 +7,6 @@ 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 relation_1 = require("oak-domain/lib/store/relation");
|
|
11
10
|
const GeoTypes = [
|
|
12
11
|
{
|
|
13
12
|
type: 'point',
|
|
@@ -1483,10 +1482,11 @@ class PostgreSQLTranslator extends sqlTranslator_1.SqlTranslator {
|
|
|
1483
1482
|
translateUpdate(entity, operation, option) {
|
|
1484
1483
|
const { attributes } = this.schema[entity];
|
|
1485
1484
|
const { filter, sorter, indexFrom, count, data } = operation;
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
const
|
|
1489
|
-
|
|
1485
|
+
// 参数验证
|
|
1486
|
+
this.validateOperationParams(sorter, indexFrom, count);
|
|
1487
|
+
const mainTable = this.getStorageName(entity);
|
|
1488
|
+
const mainAlias = `${entity}_1`;
|
|
1489
|
+
// 构建 SET 子句
|
|
1490
1490
|
const setClauses = [];
|
|
1491
1491
|
for (const attr in data) {
|
|
1492
1492
|
(0, assert_1.default)(attributes.hasOwnProperty(attr));
|
|
@@ -1494,73 +1494,93 @@ class PostgreSQLTranslator extends sqlTranslator_1.SqlTranslator {
|
|
|
1494
1494
|
setClauses.push(`"${attr}" = ${value}`);
|
|
1495
1495
|
}
|
|
1496
1496
|
const setClause = setClauses.join(', ');
|
|
1497
|
-
//
|
|
1498
|
-
const
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1497
|
+
// 统一使用子查询方案
|
|
1498
|
+
const subqueryOperation = {
|
|
1499
|
+
data: { [types_1.PrimaryKeyAttribute]: 1 },
|
|
1500
|
+
filter: filter,
|
|
1501
|
+
indexFrom: indexFrom,
|
|
1502
|
+
count: count
|
|
1503
|
+
};
|
|
1504
|
+
const subquery = this.translateSelect(entity, subqueryOperation, { includedDeleted: option?.includedDeleted });
|
|
1505
|
+
let sql = `UPDATE "${mainTable}" AS "${mainAlias}" SET ${setClause} WHERE "${types_1.PrimaryKeyAttribute}" IN (${subquery})`;
|
|
1506
|
+
// 添加 RETURNING 子句
|
|
1507
|
+
return this.appendReturningClause(sql, entity, mainAlias, option);
|
|
1508
|
+
}
|
|
1509
|
+
/**
|
|
1510
|
+
* 将 projection 转换为 RETURNING 子句的列列表
|
|
1511
|
+
* @param entity 实体名
|
|
1512
|
+
* @param alias 表别名
|
|
1513
|
+
* @param projection 投影定义
|
|
1514
|
+
*/
|
|
1515
|
+
buildReturningClause(entity, alias, projection) {
|
|
1516
|
+
const columns = [];
|
|
1517
|
+
const { attributes } = this.schema[entity];
|
|
1518
|
+
for (const attr in projection) {
|
|
1519
|
+
// if (!attributes[attr]) {
|
|
1520
|
+
// // 如果存在entity这个attr
|
|
1521
|
+
// const entityDesc = attributes['entity']
|
|
1522
|
+
// const entityIdDesc = attributes['entityId']
|
|
1523
|
+
// if (entityDesc && entityIdDesc && ((entityDesc.ref as string[])?.includes(attr))) {
|
|
1524
|
+
// // 特殊处理 entity 和 entityId 属性
|
|
1525
|
+
// }
|
|
1526
|
+
// continue;
|
|
1527
|
+
// }
|
|
1528
|
+
// 只能返回更新列的相关字段,所以直接assert
|
|
1529
|
+
(0, assert_1.default)(attributes.hasOwnProperty(attr), `RETURNING语法只能返回更新列的字段,但在实体 ${String(entity)} 中未找到原生属性「${attr}」的定义`);
|
|
1530
|
+
const dataType = attributes[attr].type;
|
|
1531
|
+
// 处理特殊类型的投影
|
|
1532
|
+
const columnExpr = this.translateAttrProjection(dataType, alias, attr);
|
|
1533
|
+
// RETURNING 需要明确的别名(不带表前缀)
|
|
1534
|
+
columns.push(`${columnExpr} AS "${attr}"`);
|
|
1535
|
+
}
|
|
1536
|
+
if (columns.length === 0) {
|
|
1537
|
+
throw new Error(`No valid columns in RETURNING clause for entity ${String(entity)}`);
|
|
1538
|
+
}
|
|
1539
|
+
return columns.join(', ');
|
|
1540
|
+
}
|
|
1541
|
+
/**
|
|
1542
|
+
* 验证操作参数的合法性
|
|
1543
|
+
*/
|
|
1544
|
+
validateOperationParams(sorter, indexFrom, count) {
|
|
1545
|
+
(0, assert_1.default)(!sorter, '当前update/remove不支持sorter行为');
|
|
1546
|
+
// 对于limit,如果有indexFrom则一定有count
|
|
1547
|
+
if (typeof indexFrom === 'number') {
|
|
1548
|
+
(0, assert_1.default)(typeof count === 'number' && count > 0);
|
|
1506
1549
|
}
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
let whereClause = joinConditions;
|
|
1516
|
-
if (filterText) {
|
|
1517
|
-
whereClause = whereClause ? `${whereClause} AND ${filterText}` : filterText;
|
|
1518
|
-
}
|
|
1519
|
-
if (whereClause) {
|
|
1520
|
-
sql += ` WHERE ${whereClause}`;
|
|
1521
|
-
}
|
|
1550
|
+
}
|
|
1551
|
+
/**
|
|
1552
|
+
* 添加RETURNING子句
|
|
1553
|
+
*/
|
|
1554
|
+
appendReturningClause(sql, entity, mainAlias, option) {
|
|
1555
|
+
if (option?.returning) {
|
|
1556
|
+
const returningClause = this.buildReturningClause(entity, mainAlias, option.returning);
|
|
1557
|
+
return `${sql} RETURNING ${returningClause}`;
|
|
1522
1558
|
}
|
|
1523
1559
|
return sql;
|
|
1524
1560
|
}
|
|
1525
1561
|
translateRemove(entity, operation, option) {
|
|
1526
1562
|
const { data, filter, sorter, indexFrom, count } = operation;
|
|
1527
|
-
(0, assert_1.default)(!sorter, '当前remove不支持sorter行为');
|
|
1528
|
-
// 使用结构化的JOIN分析
|
|
1529
|
-
const { aliasDict, filterRefAlias, mainTable, mainAlias, joinInfos, currentNumber } = this.analyzeJoinStructured(entity, { filter, sorter });
|
|
1530
|
-
// 构建过滤条件
|
|
1531
|
-
const { stmt: filterText } = this.translateFilter(entity, filter, aliasDict, filterRefAlias, currentNumber, { includedDeleted: option?.includedDeleted });
|
|
1532
1563
|
const { attributes } = this.schema[entity];
|
|
1564
|
+
// 参数验证
|
|
1565
|
+
this.validateOperationParams(sorter, indexFrom, count);
|
|
1566
|
+
const mainTable = this.getStorageName(entity);
|
|
1567
|
+
const mainAlias = `${entity}_1`;
|
|
1568
|
+
// 构建子查询
|
|
1569
|
+
const subqueryOperation = {
|
|
1570
|
+
data: { [types_1.PrimaryKeyAttribute]: 1 },
|
|
1571
|
+
filter: filter,
|
|
1572
|
+
indexFrom: indexFrom,
|
|
1573
|
+
count: count
|
|
1574
|
+
};
|
|
1575
|
+
const subquery = this.translateSelect(entity, subqueryOperation, { includedDeleted: option?.includedDeleted });
|
|
1576
|
+
let sql;
|
|
1533
1577
|
if (option?.deletePhysically) {
|
|
1534
1578
|
// 物理删除
|
|
1535
1579
|
(0, assert_1.default)((0, lodash_1.difference)(Object.keys(data), [types_1.UpdateAtAttribute, types_1.DeleteAtAttribute]).length === 0);
|
|
1536
|
-
|
|
1537
|
-
if (joinInfos.length === 0) {
|
|
1538
|
-
// 单表删除
|
|
1539
|
-
sql = `DELETE FROM "${mainTable}" AS "${mainAlias}"`;
|
|
1540
|
-
if (filterText) {
|
|
1541
|
-
sql += ` WHERE ${filterText}`;
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
else {
|
|
1545
|
-
// 多表删除 - PostgreSQL语法: DELETE FROM main USING other_tables WHERE join_conditions AND filter
|
|
1546
|
-
sql = `DELETE FROM "${mainTable}" AS "${mainAlias}"`;
|
|
1547
|
-
// USING子句包含所有JOIN的表
|
|
1548
|
-
const usingTables = joinInfos.map(j => `"${j.table}" AS "${j.alias}"`).join(', ');
|
|
1549
|
-
sql += ` USING ${usingTables}`;
|
|
1550
|
-
// WHERE子句包含JOIN条件和过滤条件
|
|
1551
|
-
const joinConditions = this.buildJoinConditions(joinInfos);
|
|
1552
|
-
let whereClause = joinConditions;
|
|
1553
|
-
if (filterText) {
|
|
1554
|
-
whereClause = whereClause ? `${whereClause} AND ${filterText}` : filterText;
|
|
1555
|
-
}
|
|
1556
|
-
if (whereClause) {
|
|
1557
|
-
sql += ` WHERE ${whereClause}`;
|
|
1558
|
-
}
|
|
1559
|
-
}
|
|
1560
|
-
return sql;
|
|
1580
|
+
sql = `DELETE FROM "${mainTable}" AS "${mainAlias}" WHERE "${types_1.PrimaryKeyAttribute}" IN (${subquery})`;
|
|
1561
1581
|
}
|
|
1562
1582
|
else {
|
|
1563
|
-
//
|
|
1583
|
+
// 软删除- 实际是 UPDATE
|
|
1564
1584
|
const setClauses = [];
|
|
1565
1585
|
for (const attr in data) {
|
|
1566
1586
|
(0, assert_1.default)([types_1.TriggerDataAttribute, types_1.TriggerUuidAttribute, types_1.DeleteAtAttribute, types_1.UpdateAtAttribute].includes(attr));
|
|
@@ -1568,164 +1588,10 @@ class PostgreSQLTranslator extends sqlTranslator_1.SqlTranslator {
|
|
|
1568
1588
|
setClauses.push(`"${attr}" = ${value}`);
|
|
1569
1589
|
}
|
|
1570
1590
|
const setClause = setClauses.join(', ');
|
|
1571
|
-
|
|
1572
|
-
if (joinInfos.length === 0) {
|
|
1573
|
-
// 单表更新
|
|
1574
|
-
sql = `UPDATE "${mainTable}" AS "${mainAlias}" SET ${setClause}`;
|
|
1575
|
-
if (filterText) {
|
|
1576
|
-
sql += ` WHERE ${filterText}`;
|
|
1577
|
-
}
|
|
1578
|
-
}
|
|
1579
|
-
else {
|
|
1580
|
-
// 多表更新
|
|
1581
|
-
sql = `UPDATE "${mainTable}" AS "${mainAlias}" SET ${setClause}`;
|
|
1582
|
-
const fromTables = joinInfos.map(j => `"${j.table}" AS "${j.alias}"`).join(', ');
|
|
1583
|
-
sql += ` FROM ${fromTables}`;
|
|
1584
|
-
const joinConditions = this.buildJoinConditions(joinInfos);
|
|
1585
|
-
let whereClause = joinConditions;
|
|
1586
|
-
if (filterText) {
|
|
1587
|
-
whereClause = whereClause ? `${whereClause} AND ${filterText}` : filterText;
|
|
1588
|
-
}
|
|
1589
|
-
if (whereClause) {
|
|
1590
|
-
sql += ` WHERE ${whereClause}`;
|
|
1591
|
-
}
|
|
1592
|
-
}
|
|
1593
|
-
return sql;
|
|
1594
|
-
}
|
|
1595
|
-
}
|
|
1596
|
-
/**
|
|
1597
|
-
* PostgreSQL专用的结构化JOIN分析
|
|
1598
|
-
* 返回结构化的JOIN信息,而不是拼接好的FROM字符串
|
|
1599
|
-
*/
|
|
1600
|
-
analyzeJoinStructured(entity, { filter, sorter }, initialNumber) {
|
|
1601
|
-
const { schema } = this;
|
|
1602
|
-
let number = initialNumber || 1;
|
|
1603
|
-
const filterRefAlias = {};
|
|
1604
|
-
const mainAlias = `${entity}_${number++}`;
|
|
1605
|
-
const mainTable = this.getStorageName(entity);
|
|
1606
|
-
const aliasDict = {
|
|
1607
|
-
'./': mainAlias,
|
|
1608
|
-
};
|
|
1609
|
-
const joinInfos = [];
|
|
1610
|
-
const analyzeFilterNode = ({ node, path, entityName, alias }) => {
|
|
1611
|
-
Object.keys(node).forEach((op) => {
|
|
1612
|
-
if (['$and', '$or'].includes(op)) {
|
|
1613
|
-
node[op].forEach((subNode) => analyzeFilterNode({
|
|
1614
|
-
node: subNode,
|
|
1615
|
-
path,
|
|
1616
|
-
entityName,
|
|
1617
|
-
alias,
|
|
1618
|
-
}));
|
|
1619
|
-
}
|
|
1620
|
-
else if (['$not'].includes(op)) {
|
|
1621
|
-
analyzeFilterNode({
|
|
1622
|
-
node: node[op],
|
|
1623
|
-
path,
|
|
1624
|
-
entityName,
|
|
1625
|
-
alias,
|
|
1626
|
-
});
|
|
1627
|
-
}
|
|
1628
|
-
else if (['$text'].includes(op)) {
|
|
1629
|
-
// 全文搜索,不需要JOIN
|
|
1630
|
-
}
|
|
1631
|
-
else {
|
|
1632
|
-
const rel = (0, relation_1.judgeRelation)(this.schema, entityName, op);
|
|
1633
|
-
if (typeof rel === 'string') {
|
|
1634
|
-
const pathAttr = `${path}${op}/`;
|
|
1635
|
-
if (!aliasDict.hasOwnProperty(pathAttr)) {
|
|
1636
|
-
const alias2 = `${rel}_${number++}`;
|
|
1637
|
-
aliasDict[pathAttr] = alias2;
|
|
1638
|
-
joinInfos.push({
|
|
1639
|
-
table: this.getStorageName(rel),
|
|
1640
|
-
alias: alias2,
|
|
1641
|
-
leftAlias: alias,
|
|
1642
|
-
leftKey: op + 'Id',
|
|
1643
|
-
rightKey: 'id',
|
|
1644
|
-
});
|
|
1645
|
-
analyzeFilterNode({
|
|
1646
|
-
node: node[op],
|
|
1647
|
-
path: pathAttr,
|
|
1648
|
-
entityName: rel,
|
|
1649
|
-
alias: alias2,
|
|
1650
|
-
});
|
|
1651
|
-
}
|
|
1652
|
-
else {
|
|
1653
|
-
analyzeFilterNode({
|
|
1654
|
-
node: node[op],
|
|
1655
|
-
path: pathAttr,
|
|
1656
|
-
entityName: rel,
|
|
1657
|
-
alias: aliasDict[pathAttr],
|
|
1658
|
-
});
|
|
1659
|
-
}
|
|
1660
|
-
}
|
|
1661
|
-
else if (rel === 2) {
|
|
1662
|
-
const pathAttr = `${path}${op}/`;
|
|
1663
|
-
if (!aliasDict.hasOwnProperty(pathAttr)) {
|
|
1664
|
-
const alias2 = `${op}_${number++}`;
|
|
1665
|
-
aliasDict[pathAttr] = alias2;
|
|
1666
|
-
joinInfos.push({
|
|
1667
|
-
table: this.getStorageName(op),
|
|
1668
|
-
alias: alias2,
|
|
1669
|
-
leftAlias: alias,
|
|
1670
|
-
leftKey: 'entityId',
|
|
1671
|
-
rightKey: 'id',
|
|
1672
|
-
extraCondition: `"${alias}"."entity" = '${op}'`,
|
|
1673
|
-
});
|
|
1674
|
-
analyzeFilterNode({
|
|
1675
|
-
node: node[op],
|
|
1676
|
-
path: pathAttr,
|
|
1677
|
-
entityName: op,
|
|
1678
|
-
alias: alias2,
|
|
1679
|
-
});
|
|
1680
|
-
}
|
|
1681
|
-
else {
|
|
1682
|
-
analyzeFilterNode({
|
|
1683
|
-
node: node[op],
|
|
1684
|
-
path: pathAttr,
|
|
1685
|
-
entityName: op,
|
|
1686
|
-
alias: aliasDict[pathAttr],
|
|
1687
|
-
});
|
|
1688
|
-
}
|
|
1689
|
-
}
|
|
1690
|
-
}
|
|
1691
|
-
});
|
|
1692
|
-
if (node['#id']) {
|
|
1693
|
-
(0, assert_1.default)(!filterRefAlias[node['#id']]);
|
|
1694
|
-
filterRefAlias[node['#id']] = [alias, entityName];
|
|
1695
|
-
}
|
|
1696
|
-
};
|
|
1697
|
-
if (filter) {
|
|
1698
|
-
analyzeFilterNode({
|
|
1699
|
-
node: filter,
|
|
1700
|
-
path: './',
|
|
1701
|
-
entityName: entity,
|
|
1702
|
-
alias: mainAlias,
|
|
1703
|
-
});
|
|
1704
|
-
}
|
|
1705
|
-
// TODO: sorter的分析类似,这里省略(UPDATE/DELETE通常不需要sorter)
|
|
1706
|
-
(0, assert_1.default)(!sorter, '当前analyzeJoinStructured不支持sorter行为');
|
|
1707
|
-
return {
|
|
1708
|
-
aliasDict,
|
|
1709
|
-
filterRefAlias,
|
|
1710
|
-
mainTable,
|
|
1711
|
-
mainAlias,
|
|
1712
|
-
joinInfos,
|
|
1713
|
-
currentNumber: number,
|
|
1714
|
-
};
|
|
1715
|
-
}
|
|
1716
|
-
/**
|
|
1717
|
-
* 构建JOIN条件(用于UPDATE/DELETE的WHERE子句)
|
|
1718
|
-
*/
|
|
1719
|
-
buildJoinConditions(joinInfos) {
|
|
1720
|
-
const conditions = [];
|
|
1721
|
-
for (const join of joinInfos) {
|
|
1722
|
-
let condition = `"${join.leftAlias}"."${join.leftKey}" = "${join.alias}"."${join.rightKey}"`;
|
|
1723
|
-
if (join.extraCondition) {
|
|
1724
|
-
condition = `(${condition} AND ${join.extraCondition})`;
|
|
1725
|
-
}
|
|
1726
|
-
conditions.push(condition);
|
|
1591
|
+
sql = `UPDATE "${mainTable}" AS "${mainAlias}" SET ${setClause} WHERE "${types_1.PrimaryKeyAttribute}" IN (${subquery})`;
|
|
1727
1592
|
}
|
|
1728
|
-
|
|
1593
|
+
// 添加 RETURNING 子句
|
|
1594
|
+
return this.appendReturningClause(sql, entity, mainAlias, option);
|
|
1729
1595
|
}
|
|
1730
1596
|
/**
|
|
1731
1597
|
* 生成 PostgreSQL UPSERT 语句
|
package/lib/sqlTranslator.d.ts
CHANGED
|
@@ -53,7 +53,11 @@ export declare abstract class SqlTranslator<ED extends EntityDict & BaseEntityDi
|
|
|
53
53
|
};
|
|
54
54
|
private translateSorter;
|
|
55
55
|
private translateProjection;
|
|
56
|
-
|
|
56
|
+
protected translateSelectInner<T extends keyof ED, OP extends SqlSelectOption>(entity: T, selection: ED[T]['Selection'], initialNumber: number, refAlias: Record<string, [string, keyof ED]>, option?: OP): {
|
|
57
|
+
filterStmt: string;
|
|
58
|
+
stmt: string;
|
|
59
|
+
currentNumber: number;
|
|
60
|
+
};
|
|
57
61
|
translateSelect<T extends keyof ED, OP extends SqlSelectOption>(entity: T, selection: ED[T]['Selection'], option?: OP): string;
|
|
58
62
|
translateWhere<T extends keyof ED, OP extends SqlSelectOption>(entity: T, selection: ED[T]['Selection'], option?: OP): string;
|
|
59
63
|
translateAggregate<T extends keyof ED, OP extends SqlSelectOption>(entity: T, aggregation: ED[T]['Aggregation'], option?: OP): string;
|
package/lib/sqlTranslator.js
CHANGED
|
@@ -564,10 +564,10 @@ class SqlTranslator {
|
|
|
564
564
|
translateFilter(entity, filter, aliasDict, filterRefAlias, initialNumber, option) {
|
|
565
565
|
const { schema } = this;
|
|
566
566
|
let currentNumber = initialNumber;
|
|
567
|
-
const translateInner = (entity2, path, filter2,
|
|
567
|
+
const translateInner = (entity2, path, filter2, skipDeletePhy) => {
|
|
568
568
|
const alias = aliasDict[path];
|
|
569
569
|
const { attributes } = schema[entity2];
|
|
570
|
-
let whereText =
|
|
570
|
+
let whereText = skipDeletePhy ? '' : this.getDefaultSelectFilter(alias, option);
|
|
571
571
|
if (filter2) {
|
|
572
572
|
const attrs = Object.keys(filter2).filter(ele => !ele.startsWith('#'));
|
|
573
573
|
attrs.forEach((attr) => {
|
|
@@ -582,7 +582,7 @@ class SqlTranslator {
|
|
|
582
582
|
case '$xor': {
|
|
583
583
|
const logicQueries = filter2[attr];
|
|
584
584
|
logicQueries.forEach((logicQuery, index) => {
|
|
585
|
-
const sql = translateInner(entity2, path, logicQuery,
|
|
585
|
+
const sql = translateInner(entity2, path, logicQuery, true); // 只要传个值就行了,应该无所谓
|
|
586
586
|
if (sql) {
|
|
587
587
|
whereText += ` (${sql})`;
|
|
588
588
|
if (index < logicQueries.length - 1) {
|
|
@@ -595,7 +595,7 @@ class SqlTranslator {
|
|
|
595
595
|
default: {
|
|
596
596
|
(0, assert_1.default)(attr === '$not');
|
|
597
597
|
const logicQuery = filter2[attr];
|
|
598
|
-
const sql = translateInner(entity2, path, logicQuery,
|
|
598
|
+
const sql = translateInner(entity2, path, logicQuery, true); // 只要传个值就行了,应该无所谓
|
|
599
599
|
if (sql) {
|
|
600
600
|
whereText += ` not (${sql})`;
|
|
601
601
|
break;
|
|
@@ -965,6 +965,10 @@ class SqlTranslator {
|
|
|
965
965
|
const { data, filter, sorter, indexFrom, count } = operation;
|
|
966
966
|
(0, assert_1.default)(!sorter, '当前remove不支持sorter行为');
|
|
967
967
|
const { aliasDict, filterRefAlias, from: fromText, currentNumber } = this.analyzeJoin(entity, { filter, sorter });
|
|
968
|
+
// 对于limit,如果有indexForm则一定有count
|
|
969
|
+
if (typeof indexFrom === 'number') {
|
|
970
|
+
(0, assert_1.default)(typeof count === 'number' && count > 0);
|
|
971
|
+
}
|
|
968
972
|
const alias = aliasDict['./'];
|
|
969
973
|
// 这里原来includeDeleted传的是true,不知道原因,但不合理
|
|
970
974
|
const { stmt: filterText } = this.translateFilter(entity, filter, aliasDict, filterRefAlias, currentNumber, { includedDeleted: option?.includedDeleted });
|
|
@@ -992,6 +996,10 @@ class SqlTranslator {
|
|
|
992
996
|
const { filter, sorter, indexFrom, count, data } = operation;
|
|
993
997
|
(0, assert_1.default)(!sorter, '当前update不支持sorter行为');
|
|
994
998
|
const { aliasDict, filterRefAlias, from: fromText, currentNumber } = this.analyzeJoin(entity, { filter, sorter });
|
|
999
|
+
// 对于limit,如果有indexForm则一定有count
|
|
1000
|
+
if (typeof indexFrom === 'number') {
|
|
1001
|
+
(0, assert_1.default)(typeof count === 'number' && count > 0);
|
|
1002
|
+
}
|
|
995
1003
|
const alias = aliasDict['./'];
|
|
996
1004
|
let updateText = '';
|
|
997
1005
|
for (const attr in data) {
|
package/lib/types/dbStore.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export type Plan = {
|
|
|
16
16
|
updatedIndexes: Record<string, Index<any>[]>;
|
|
17
17
|
};
|
|
18
18
|
export interface DbStore<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED>> extends AsyncRowStore<ED, Cxt> {
|
|
19
|
+
checkRelationAsync<T extends keyof ED, Cxt extends AsyncContext<ED>>(entity: T, operation: Omit<ED[T]['Operation'] | ED[T]['Selection'], 'id'>, context: Cxt): Promise<void>;
|
|
19
20
|
connect: () => Promise<void>;
|
|
20
21
|
disconnect: () => Promise<void>;
|
|
21
22
|
initialize(options: CreateEntityOption): Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oak-db",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.14",
|
|
4
4
|
"description": "oak-db",
|
|
5
5
|
"main": "lib/index",
|
|
6
6
|
"author": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"lodash": "^4.17.21",
|
|
19
19
|
"mysql": "^2.18.1",
|
|
20
20
|
"mysql2": "^2.3.3",
|
|
21
|
-
"oak-domain": "^5.1.
|
|
21
|
+
"oak-domain": "^5.1.36",
|
|
22
22
|
"pg": "^8.16.3",
|
|
23
23
|
"uuid": "^8.3.2"
|
|
24
24
|
},
|