oak-db 3.3.11 → 3.3.13
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.d.ts +15 -15
- package/lib/MySQL/connector.js +77 -77
- package/lib/MySQL/store.d.ts +50 -37
- package/lib/MySQL/store.js +430 -308
- package/lib/MySQL/translator.d.ts +114 -107
- package/lib/MySQL/translator.js +307 -67
- package/lib/MySQL/types/Configuration.d.ts +12 -12
- package/lib/MySQL/types/Configuration.js +2 -2
- package/lib/PostgreSQL/connector.d.ts +27 -0
- package/lib/PostgreSQL/connector.js +147 -0
- package/lib/PostgreSQL/store.d.ts +50 -0
- package/lib/PostgreSQL/store.js +527 -0
- package/lib/PostgreSQL/translator.d.ts +103 -0
- package/lib/PostgreSQL/translator.js +2159 -0
- package/lib/PostgreSQL/types/Configuration.d.ts +13 -0
- package/lib/PostgreSQL/types/Configuration.js +2 -0
- package/lib/index.d.ts +4 -2
- package/lib/index.js +5 -4
- package/lib/sqlTranslator.d.ts +68 -55
- package/lib/sqlTranslator.js +1034 -999
- package/lib/types/Translator.d.ts +3 -3
- package/lib/types/Translator.js +2 -2
- package/lib/types/configuration.d.ts +7 -0
- package/lib/types/configuration.js +2 -0
- package/lib/types/dbStore.d.ts +32 -0
- package/lib/types/dbStore.js +3 -0
- package/package.json +6 -5
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PostgreSQLStore = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const CascadeStore_1 = require("oak-domain/lib/store/CascadeStore");
|
|
6
|
+
const connector_1 = require("./connector");
|
|
7
|
+
const translator_1 = require("./translator");
|
|
8
|
+
const lodash_1 = require("lodash");
|
|
9
|
+
const assert_1 = tslib_1.__importDefault(require("assert"));
|
|
10
|
+
const relation_1 = require("oak-domain/lib/store/relation");
|
|
11
|
+
const ToNumberAttrs = new Set([
|
|
12
|
+
'$$seq$$',
|
|
13
|
+
'$$createAt$$',
|
|
14
|
+
'$$updateAt$$',
|
|
15
|
+
'$$deleteAt$$',
|
|
16
|
+
]);
|
|
17
|
+
function convertGeoTextToObject(geoText) {
|
|
18
|
+
if (geoText.startsWith('POINT')) {
|
|
19
|
+
const coord = geoText.match(/(-?\d+\.?\d*)/g);
|
|
20
|
+
(0, assert_1.default)(coord && coord.length === 2);
|
|
21
|
+
return {
|
|
22
|
+
type: 'point',
|
|
23
|
+
coordinate: coord.map(ele => parseFloat(ele)),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
else if (geoText.startsWith('LINESTRING')) {
|
|
27
|
+
const coordsMatch = geoText.match(/\(([^)]+)\)/);
|
|
28
|
+
if (coordsMatch) {
|
|
29
|
+
const points = coordsMatch[1].split(',').map(p => {
|
|
30
|
+
const [x, y] = p.trim().split(/\s+/).map(parseFloat);
|
|
31
|
+
return [x, y];
|
|
32
|
+
});
|
|
33
|
+
return {
|
|
34
|
+
type: 'path',
|
|
35
|
+
coordinate: points,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else if (geoText.startsWith('POLYGON')) {
|
|
40
|
+
const ringsMatch = geoText.match(/\(\(([^)]+)\)\)/g);
|
|
41
|
+
if (ringsMatch) {
|
|
42
|
+
const rings = ringsMatch.map(ring => {
|
|
43
|
+
const coordStr = ring.replace(/[()]/g, '');
|
|
44
|
+
return coordStr.split(',').map(p => {
|
|
45
|
+
const [x, y] = p.trim().split(/\s+/).map(parseFloat);
|
|
46
|
+
return [x, y];
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
return {
|
|
50
|
+
type: 'polygon',
|
|
51
|
+
coordinate: rings,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
throw new Error(`Unsupported geometry type: ${geoText.slice(0, 50)}`);
|
|
56
|
+
}
|
|
57
|
+
class PostgreSQLStore extends CascadeStore_1.CascadeStore {
|
|
58
|
+
countAbjointRow(entity, selection, context, option) {
|
|
59
|
+
throw new Error('PostgreSQL store 不支持同步取数据');
|
|
60
|
+
}
|
|
61
|
+
aggregateAbjointRowSync(entity, aggregation, context, option) {
|
|
62
|
+
throw new Error('PostgreSQL store 不支持同步取数据');
|
|
63
|
+
}
|
|
64
|
+
selectAbjointRow(entity, selection, context, option) {
|
|
65
|
+
throw new Error('PostgreSQL store 不支持同步取数据');
|
|
66
|
+
}
|
|
67
|
+
updateAbjointRow(entity, operation, context, option) {
|
|
68
|
+
throw new Error('PostgreSQL store 不支持同步更新数据');
|
|
69
|
+
}
|
|
70
|
+
async exec(script, txnId) {
|
|
71
|
+
await this.connector.exec(script, txnId);
|
|
72
|
+
}
|
|
73
|
+
connector;
|
|
74
|
+
translator;
|
|
75
|
+
constructor(storageSchema, configuration) {
|
|
76
|
+
super(storageSchema);
|
|
77
|
+
this.connector = new connector_1.PostgreSQLConnector(configuration);
|
|
78
|
+
this.translator = new translator_1.PostgreSQLTranslator(storageSchema);
|
|
79
|
+
}
|
|
80
|
+
checkRelationAsync(entity, operation, context) {
|
|
81
|
+
throw new Error('Method not implemented.');
|
|
82
|
+
}
|
|
83
|
+
async aggregateAbjointRowAsync(entity, aggregation, context, option) {
|
|
84
|
+
const sql = this.translator.translateAggregate(entity, aggregation, option);
|
|
85
|
+
const result = await this.connector.exec(sql, context.getCurrentTxnId());
|
|
86
|
+
return this.formResult(entity, result[0]);
|
|
87
|
+
}
|
|
88
|
+
aggregate(entity, aggregation, context, option) {
|
|
89
|
+
return this.aggregateAsync(entity, aggregation, context, option);
|
|
90
|
+
}
|
|
91
|
+
supportManyToOneJoin() {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
supportMultipleCreate() {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
formResult(entity, result) {
|
|
98
|
+
const schema = this.getSchema();
|
|
99
|
+
function resolveAttribute(entity2, r, attr, value) {
|
|
100
|
+
const { attributes, view } = schema[entity2];
|
|
101
|
+
if (!view) {
|
|
102
|
+
const i = attr.indexOf(".");
|
|
103
|
+
if (i !== -1) {
|
|
104
|
+
const attrHead = attr.slice(0, i);
|
|
105
|
+
const attrTail = attr.slice(i + 1);
|
|
106
|
+
const rel = (0, relation_1.judgeRelation)(schema, entity2, attrHead);
|
|
107
|
+
if (rel === 1) {
|
|
108
|
+
(0, lodash_1.set)(r, attr, value);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
if (!r[attrHead]) {
|
|
112
|
+
r[attrHead] = {};
|
|
113
|
+
}
|
|
114
|
+
if (rel === 0) {
|
|
115
|
+
resolveAttribute(entity2, r[attrHead], attrTail, value);
|
|
116
|
+
}
|
|
117
|
+
else if (rel === 2) {
|
|
118
|
+
resolveAttribute(attrHead, r[attrHead], attrTail, value);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
(0, assert_1.default)(typeof rel === 'string');
|
|
122
|
+
resolveAttribute(rel, r[attrHead], attrTail, value);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
else if (attributes[attr]) {
|
|
127
|
+
const { type } = attributes[attr];
|
|
128
|
+
switch (type) {
|
|
129
|
+
case 'date':
|
|
130
|
+
case 'time':
|
|
131
|
+
case 'datetime': {
|
|
132
|
+
if (value instanceof Date) {
|
|
133
|
+
r[attr] = value.valueOf();
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
if (typeof value === 'string') {
|
|
137
|
+
r[attr] = parseInt(value, 10);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
(0, assert_1.default)(typeof value === 'number' || value === null);
|
|
141
|
+
r[attr] = value;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
case 'geometry': {
|
|
147
|
+
if (typeof value === 'string') {
|
|
148
|
+
r[attr] = convertGeoTextToObject(value);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
r[attr] = value;
|
|
152
|
+
}
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
case 'object':
|
|
156
|
+
case 'array': {
|
|
157
|
+
// PostgreSQL jsonb 直接返回对象,不需要 parse
|
|
158
|
+
if (typeof value === 'string') {
|
|
159
|
+
r[attr] = JSON.parse(value);
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
r[attr] = value;
|
|
163
|
+
}
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
case 'function': {
|
|
167
|
+
if (typeof value === 'string') {
|
|
168
|
+
r[attr] = `return ${Buffer.from(value, 'base64').toString()}`;
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
r[attr] = value;
|
|
172
|
+
}
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
case 'bool':
|
|
176
|
+
case 'boolean': {
|
|
177
|
+
// PostgreSQL 直接返回 boolean 类型
|
|
178
|
+
r[attr] = value;
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
case 'decimal': {
|
|
182
|
+
// PostgreSQL numeric 类型可能返回字符串
|
|
183
|
+
if (typeof value === 'string') {
|
|
184
|
+
r[attr] = parseFloat(value);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
(0, assert_1.default)(value === null || typeof value === 'number');
|
|
188
|
+
r[attr] = value;
|
|
189
|
+
}
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
// TODO: 这里和mysql统一行为,ref类型的字符串去除前后空格
|
|
193
|
+
case "char":
|
|
194
|
+
case "ref": {
|
|
195
|
+
if (value) {
|
|
196
|
+
(0, assert_1.default)(typeof value === 'string');
|
|
197
|
+
r[attr] = value.trim();
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
r[attr] = value;
|
|
201
|
+
}
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
default: {
|
|
205
|
+
r[attr] = value;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
// TODO: 这里和mysql统一行为,id字段为char类型时,去除后面的空格
|
|
211
|
+
if (value && typeof value === 'string') {
|
|
212
|
+
if (attr === 'id') {
|
|
213
|
+
r[attr] = value.trim();
|
|
214
|
+
}
|
|
215
|
+
else if (attr.startsWith("#count")) {
|
|
216
|
+
// PostgreSQL count 返回字符串
|
|
217
|
+
r[attr] = parseInt(value, 10);
|
|
218
|
+
}
|
|
219
|
+
else if (attr.startsWith("#sum") || attr.startsWith("#avg") || attr.startsWith("#min") || attr.startsWith("#max")) {
|
|
220
|
+
// PostgreSQL sum/avg/min/max 返回字符串
|
|
221
|
+
r[attr] = parseFloat(value);
|
|
222
|
+
}
|
|
223
|
+
else if (ToNumberAttrs.has(attr)) {
|
|
224
|
+
// PostgreSQL sum/avg/min/max 返回字符串
|
|
225
|
+
r[attr] = parseInt(value, 10);
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
r[attr] = value;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
r[attr] = value;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
(0, lodash_1.assign)(r, { [attr]: value });
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function removeNullObjects(r, e) {
|
|
241
|
+
for (let attr in r) {
|
|
242
|
+
const rel = (0, relation_1.judgeRelation)(schema, e, attr);
|
|
243
|
+
if (rel === 2) {
|
|
244
|
+
if (r[attr].id === null) {
|
|
245
|
+
(0, assert_1.default)(schema[e].toModi || r.entity !== attr);
|
|
246
|
+
delete r[attr];
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
removeNullObjects(r[attr], attr);
|
|
250
|
+
}
|
|
251
|
+
else if (typeof rel === 'string') {
|
|
252
|
+
if (r[attr].id === null) {
|
|
253
|
+
(0, assert_1.default)(schema[e].toModi || r[`${attr}Id`] === null, `对象${String(e)}取数据时,发现其外键找不到目标对象,rowId是${r.id},其外键${attr}Id值为${r[`${attr}Id`]}`);
|
|
254
|
+
delete r[attr];
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
removeNullObjects(r[attr], rel);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
function formSingleRow(r) {
|
|
262
|
+
let result2 = {};
|
|
263
|
+
for (let attr in r) {
|
|
264
|
+
const value = r[attr];
|
|
265
|
+
resolveAttribute(entity, result2, attr, value);
|
|
266
|
+
}
|
|
267
|
+
removeNullObjects(result2, entity);
|
|
268
|
+
return result2;
|
|
269
|
+
}
|
|
270
|
+
if (result instanceof Array) {
|
|
271
|
+
return result.map(r => formSingleRow(r));
|
|
272
|
+
}
|
|
273
|
+
return formSingleRow(result);
|
|
274
|
+
}
|
|
275
|
+
async selectAbjointRowAsync(entity, selection, context, option) {
|
|
276
|
+
const sql = this.translator.translateSelect(entity, selection, option);
|
|
277
|
+
const result = await this.connector.exec(sql, context.getCurrentTxnId());
|
|
278
|
+
return this.formResult(entity, result[0]);
|
|
279
|
+
}
|
|
280
|
+
async updateAbjointRowAsync(entity, operation, context, option) {
|
|
281
|
+
const { translator, connector } = this;
|
|
282
|
+
const { action } = operation;
|
|
283
|
+
const txn = context.getCurrentTxnId();
|
|
284
|
+
switch (action) {
|
|
285
|
+
case 'create': {
|
|
286
|
+
const { data } = operation;
|
|
287
|
+
const sql = translator.translateInsert(entity, data instanceof Array ? data : [data]);
|
|
288
|
+
const result = await connector.exec(sql, txn);
|
|
289
|
+
// PostgreSQL QueryResult.rowCount
|
|
290
|
+
return result[1].rowCount || 0;
|
|
291
|
+
}
|
|
292
|
+
case 'remove': {
|
|
293
|
+
const sql = translator.translateRemove(entity, operation, option);
|
|
294
|
+
const result = await connector.exec(sql, txn);
|
|
295
|
+
return result[1].rowCount || 0;
|
|
296
|
+
}
|
|
297
|
+
default: {
|
|
298
|
+
(0, assert_1.default)(!['select', 'download', 'stat'].includes(action));
|
|
299
|
+
const sql = translator.translateUpdate(entity, operation, option);
|
|
300
|
+
const result = await connector.exec(sql, txn);
|
|
301
|
+
return result[1].rowCount || 0;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
async operate(entity, operation, context, option) {
|
|
306
|
+
const { action } = operation;
|
|
307
|
+
(0, assert_1.default)(!['select', 'download', 'stat'].includes(action), '不支持使用 select operation');
|
|
308
|
+
return await super.operateAsync(entity, operation, context, option);
|
|
309
|
+
}
|
|
310
|
+
async select(entity, selection, context, option) {
|
|
311
|
+
return await super.selectAsync(entity, selection, context, option);
|
|
312
|
+
}
|
|
313
|
+
async countAbjointRowAsync(entity, selection, context, option) {
|
|
314
|
+
const sql = this.translator.translateCount(entity, selection, option);
|
|
315
|
+
const result = await this.connector.exec(sql, context.getCurrentTxnId());
|
|
316
|
+
// PostgreSQL 返回的 count 是 string 类型(bigint)
|
|
317
|
+
const cnt = result[0][0]?.cnt;
|
|
318
|
+
return typeof cnt === 'string' ? parseInt(cnt, 10) : (cnt || 0);
|
|
319
|
+
}
|
|
320
|
+
async count(entity, selection, context, option) {
|
|
321
|
+
return this.countAsync(entity, selection, context, option);
|
|
322
|
+
}
|
|
323
|
+
async begin(option) {
|
|
324
|
+
return await this.connector.startTransaction(option);
|
|
325
|
+
}
|
|
326
|
+
async commit(txnId) {
|
|
327
|
+
await this.connector.commitTransaction(txnId);
|
|
328
|
+
}
|
|
329
|
+
async rollback(txnId) {
|
|
330
|
+
await this.connector.rollbackTransaction(txnId);
|
|
331
|
+
}
|
|
332
|
+
async connect() {
|
|
333
|
+
await this.connector.connect();
|
|
334
|
+
}
|
|
335
|
+
async disconnect() {
|
|
336
|
+
await this.connector.disconnect();
|
|
337
|
+
}
|
|
338
|
+
async initialize(option) {
|
|
339
|
+
// PG的DDL支持事务,所以这里直接用一个事务包裹所有的初始化操作
|
|
340
|
+
const txn = await this.connector.startTransaction({
|
|
341
|
+
isolationLevel: 'serializable',
|
|
342
|
+
});
|
|
343
|
+
try {
|
|
344
|
+
const schema = this.getSchema();
|
|
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
|
+
}
|
|
365
|
+
if (hasChineseTsConfig) {
|
|
366
|
+
console.log('Initializing Chinese text search configuration...');
|
|
367
|
+
const checkChineseConfigSql = `
|
|
368
|
+
SELECT COUNT(*) as cnt
|
|
369
|
+
FROM pg_catalog.pg_ts_config
|
|
370
|
+
WHERE cfgname = 'chinese';
|
|
371
|
+
`;
|
|
372
|
+
const result = await this.connector.exec(checkChineseConfigSql);
|
|
373
|
+
const count = parseInt(result[0][0]?.cnt || '0', 10);
|
|
374
|
+
if (count === 0) {
|
|
375
|
+
const createChineseConfigSql = `
|
|
376
|
+
CREATE EXTENSION IF NOT EXISTS zhparser;
|
|
377
|
+
CREATE TEXT SEARCH CONFIGURATION chinese (PARSER = zhparser);
|
|
378
|
+
ALTER TEXT SEARCH CONFIGURATION chinese ADD MAPPING FOR n,v,a,i,e,l WITH simple;
|
|
379
|
+
`;
|
|
380
|
+
await this.connector.exec(createChineseConfigSql);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
for (const entity in schema) {
|
|
384
|
+
const sqls = this.translator.translateCreateEntity(entity, option);
|
|
385
|
+
for (const sql of sqls) {
|
|
386
|
+
await this.connector.exec(sql, txn);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
await this.connector.commitTransaction(txn);
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
await this.connector.rollbackTransaction(txn);
|
|
393
|
+
throw error;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
// 从数据库中读取当前schema
|
|
397
|
+
readSchema() {
|
|
398
|
+
return this.translator.readSchema((sql) => this.connector.exec(sql));
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* 根据载入的dataSchema,和数据库中原来的schema,决定如何来upgrade
|
|
402
|
+
* 制订出来的plan分为两阶段:增加阶段和削减阶段,在两个阶段之间,由用户来修正数据
|
|
403
|
+
*/
|
|
404
|
+
async makeUpgradePlan() {
|
|
405
|
+
const originSchema = await this.readSchema();
|
|
406
|
+
const plan = this.diffSchema(originSchema, this.translator.schema);
|
|
407
|
+
return plan;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* 比较两个schema的不同,这里计算的是new对old的增量
|
|
411
|
+
* @param schemaOld
|
|
412
|
+
* @param schemaNew
|
|
413
|
+
*/
|
|
414
|
+
diffSchema(schemaOld, schemaNew) {
|
|
415
|
+
const plan = {
|
|
416
|
+
newTables: {},
|
|
417
|
+
newIndexes: {},
|
|
418
|
+
updatedIndexes: {},
|
|
419
|
+
updatedTables: {},
|
|
420
|
+
};
|
|
421
|
+
for (const table in schemaNew) {
|
|
422
|
+
// PostgreSQL 表名区分大小写(使用双引号时)
|
|
423
|
+
if (schemaOld[table]) {
|
|
424
|
+
const { attributes, indexes } = schemaOld[table];
|
|
425
|
+
const { attributes: attributesNew, indexes: indexesNew } = schemaNew[table];
|
|
426
|
+
const assignToUpdateTables = (attr, isNew) => {
|
|
427
|
+
const skipAttrs = ['$$seq$$', '$$createAt$$', '$$updateAt$$', '$$deleteAt$$', 'id'];
|
|
428
|
+
if (skipAttrs.includes(attr)) {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (!plan.updatedTables[table]) {
|
|
432
|
+
plan.updatedTables[table] = {
|
|
433
|
+
attributes: {
|
|
434
|
+
[attr]: {
|
|
435
|
+
...attributesNew[attr],
|
|
436
|
+
isNew,
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
plan.updatedTables[table].attributes[attr] = {
|
|
443
|
+
...attributesNew[attr],
|
|
444
|
+
isNew,
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
for (const attr in attributesNew) {
|
|
449
|
+
if (attributes[attr]) {
|
|
450
|
+
// 比较两次创建的属性定义是否一致
|
|
451
|
+
const sql1 = this.translator.translateAttributeDef(attr, attributesNew[attr]);
|
|
452
|
+
const sql2 = this.translator.translateAttributeDef(attr, attributes[attr]);
|
|
453
|
+
if (!this.translator.compareSql(sql1, sql2)) {
|
|
454
|
+
assignToUpdateTables(attr, false);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
assignToUpdateTables(attr, true);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
if (indexesNew) {
|
|
462
|
+
const assignToIndexes = (index, isNew) => {
|
|
463
|
+
if (isNew) {
|
|
464
|
+
if (plan.newIndexes[table]) {
|
|
465
|
+
plan.newIndexes[table].push(index);
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
plan.newIndexes[table] = [index];
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
if (plan.updatedIndexes[table]) {
|
|
473
|
+
plan.updatedIndexes[table].push(index);
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
plan.updatedIndexes[table] = [index];
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
const compareConfig = (config1, config2) => {
|
|
481
|
+
const unique1 = config1?.unique || false;
|
|
482
|
+
const unique2 = config2?.unique || false;
|
|
483
|
+
if (unique1 !== unique2) {
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
const type1 = config1?.type || 'btree';
|
|
487
|
+
const type2 = config2?.type || 'btree';
|
|
488
|
+
// tsConfig 比较
|
|
489
|
+
const tsConfig1 = config1?.tsConfig;
|
|
490
|
+
const tsConfig2 = config2?.tsConfig;
|
|
491
|
+
if (JSON.stringify(tsConfig1) !== JSON.stringify(tsConfig2)) {
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
return type1 === type2;
|
|
495
|
+
};
|
|
496
|
+
for (const index of indexesNew) {
|
|
497
|
+
const { name, config, attributes: indexAttrs } = index;
|
|
498
|
+
const origin = indexes?.find(ele => ele.name === name);
|
|
499
|
+
if (origin) {
|
|
500
|
+
if (JSON.stringify(indexAttrs) !== JSON.stringify(origin.attributes)) {
|
|
501
|
+
assignToIndexes(index, false);
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
if (!compareConfig(config, origin.config)) {
|
|
505
|
+
assignToIndexes(index, false);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
assignToIndexes(index, true);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
else {
|
|
516
|
+
plan.newTables[table] = {
|
|
517
|
+
attributes: schemaNew[table].attributes,
|
|
518
|
+
};
|
|
519
|
+
if (schemaNew[table].indexes) {
|
|
520
|
+
plan.newIndexes[table] = schemaNew[table].indexes;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return plan;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
exports.PostgreSQLStore = PostgreSQLStore;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { EntityDict, Q_FullTextValue, RefOrExpression, Ref, StorageSchema, Attribute } from "oak-domain/lib/types";
|
|
2
|
+
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
|
3
|
+
import { DataType } from "oak-domain/lib/types/schema/DataTypes";
|
|
4
|
+
import { SqlOperateOption, SqlSelectOption, SqlTranslator } from "../sqlTranslator";
|
|
5
|
+
import { CreateEntityOption } from '../types/Translator';
|
|
6
|
+
export interface PostgreSQLSelectOption extends SqlSelectOption {
|
|
7
|
+
}
|
|
8
|
+
export interface PostgreSQLOperateOption extends SqlOperateOption {
|
|
9
|
+
}
|
|
10
|
+
export declare class PostgreSQLTranslator<ED extends EntityDict & BaseEntityDict> extends SqlTranslator<ED> {
|
|
11
|
+
private getEnumTypeName;
|
|
12
|
+
/**
|
|
13
|
+
* 将 MySQL 风格的 JSON 路径转换为 PostgreSQL 路径数组格式
|
|
14
|
+
* 例如: ".foo.bar[0].baz" -> '{foo,bar,0,baz}'
|
|
15
|
+
*/
|
|
16
|
+
private convertJsonPath;
|
|
17
|
+
/**
|
|
18
|
+
* 生成 PostgreSQL JSON 访问表达式
|
|
19
|
+
* @param column 列名(包含别名)
|
|
20
|
+
* @param path JSON 路径
|
|
21
|
+
* @param asText 是否返回文本(使用 #>> 而不是 #>)
|
|
22
|
+
*/
|
|
23
|
+
private buildJsonAccessor;
|
|
24
|
+
protected getDefaultSelectFilter(alias: string, option?: PostgreSQLSelectOption): string;
|
|
25
|
+
private makeUpSchema;
|
|
26
|
+
constructor(schema: StorageSchema<ED>);
|
|
27
|
+
static supportedDataTypes: DataType[];
|
|
28
|
+
static spatialTypes: DataType[];
|
|
29
|
+
static withLengthDataTypes: DataType[];
|
|
30
|
+
static withPrecisionDataTypes: DataType[];
|
|
31
|
+
static withScaleDataTypes: DataType[];
|
|
32
|
+
static dataTypeDefaults: Record<string, any>;
|
|
33
|
+
maxAliasLength: number;
|
|
34
|
+
private populateDataTypeDef;
|
|
35
|
+
/**
|
|
36
|
+
* PostgreSQL 字符串值转义
|
|
37
|
+
* 防御SQL注入
|
|
38
|
+
*/
|
|
39
|
+
escapeStringValue(value: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* LIKE 模式转义
|
|
42
|
+
* 转义 LIKE 语句中的特殊字符
|
|
43
|
+
*/
|
|
44
|
+
escapeLikePattern(value: string): string;
|
|
45
|
+
/**
|
|
46
|
+
* PostgreSQL 标识符转义
|
|
47
|
+
* 用于表名、列名等
|
|
48
|
+
*/
|
|
49
|
+
escapeIdentifier(identifier: string): string;
|
|
50
|
+
/**
|
|
51
|
+
* tsquery 搜索词转义
|
|
52
|
+
*/
|
|
53
|
+
escapeTsQueryValue(value: string): string;
|
|
54
|
+
quoteIdentifier(identifier: string): string;
|
|
55
|
+
protected translateAttrProjection(dataType: DataType, alias: string, attr: string): string;
|
|
56
|
+
protected translateObjectPredicate(predicate: Record<string, any>, alias: string, attr: string): string;
|
|
57
|
+
protected translateObjectProjection(projection: Record<string, any>, alias: string, attr: string, prefix: string): string;
|
|
58
|
+
protected translateAttrValue(dataType: DataType | Ref, value: any): string;
|
|
59
|
+
protected translateFullTextSearch<T extends keyof ED>(value: Q_FullTextValue, entity: T, alias: string): string;
|
|
60
|
+
translateCreateEntity<T extends keyof ED>(entity: T, options?: CreateEntityOption): string[];
|
|
61
|
+
private translateFnName;
|
|
62
|
+
private translateAttrInExpression;
|
|
63
|
+
protected translateExpression<T extends keyof ED>(entity: T, alias: string, expression: RefOrExpression<keyof ED[T]["OpSchema"]>, refDict: Record<string, [string, keyof ED]>): string;
|
|
64
|
+
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
|
+
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
|
+
/**
|
|
68
|
+
* PostgreSQL专用的结构化JOIN分析
|
|
69
|
+
* 返回结构化的JOIN信息,而不是拼接好的FROM字符串
|
|
70
|
+
*/
|
|
71
|
+
private analyzeJoinStructured;
|
|
72
|
+
/**
|
|
73
|
+
* 构建JOIN条件(用于UPDATE/DELETE的WHERE子句)
|
|
74
|
+
*/
|
|
75
|
+
private buildJoinConditions;
|
|
76
|
+
/**
|
|
77
|
+
* 生成 PostgreSQL UPSERT 语句
|
|
78
|
+
* INSERT ... ON CONFLICT (key) DO UPDATE SET ...
|
|
79
|
+
*/
|
|
80
|
+
translateUpsert<T extends keyof ED>(entity: T, data: ED[T]['CreateMulti']['data'], conflictKeys: string[], updateAttrs?: string[]): string;
|
|
81
|
+
protected populateUpdateStmt(updateText: string, fromText: string, aliasDict: Record<string, string>, filterText: string, sorterText?: string, indexFrom?: number, count?: number, option?: PostgreSQLOperateOption): string;
|
|
82
|
+
protected populateRemoveStmt(updateText: string, fromText: string, aliasDict: Record<string, string>, filterText: string, sorterText?: string, indexFrom?: number, count?: number, option?: PostgreSQLOperateOption): string;
|
|
83
|
+
/**
|
|
84
|
+
* 将 PostgreSQL 返回的 Type 回译成 oak 的类型,是 populateDataTypeDef 的反函数
|
|
85
|
+
* @param type PostgreSQL 类型字符串
|
|
86
|
+
*/
|
|
87
|
+
private reTranslateToAttribute;
|
|
88
|
+
/**
|
|
89
|
+
* 从 PostgreSQL 数据库读取当前的 schema 结构
|
|
90
|
+
*/
|
|
91
|
+
readSchema(execFn: (sql: string) => Promise<any>): Promise<StorageSchema<ED>>;
|
|
92
|
+
/**
|
|
93
|
+
* 将属性定义转换为 PostgreSQL DDL 语句
|
|
94
|
+
* @param attr 属性名
|
|
95
|
+
* @param attrDef 属性定义
|
|
96
|
+
*/
|
|
97
|
+
translateAttributeDef(attr: string, attrDef: Attribute): string;
|
|
98
|
+
/**
|
|
99
|
+
* 比较两个 SQL 语句是否等价(用于 schema diff)
|
|
100
|
+
* 忽略空格、大小写等格式差异
|
|
101
|
+
*/
|
|
102
|
+
compareSql(sql1: string, sql2: string): boolean;
|
|
103
|
+
}
|