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.
@@ -0,0 +1,2159 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PostgreSQLTranslator = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const assert_1 = tslib_1.__importDefault(require("assert"));
6
+ const util_1 = require("util");
7
+ const lodash_1 = require("lodash");
8
+ const types_1 = require("oak-domain/lib/types");
9
+ const sqlTranslator_1 = require("../sqlTranslator");
10
+ const relation_1 = require("oak-domain/lib/store/relation");
11
+ const GeoTypes = [
12
+ {
13
+ type: 'point',
14
+ name: "Point"
15
+ },
16
+ {
17
+ type: 'path',
18
+ name: "LineString",
19
+ element: 'point',
20
+ },
21
+ {
22
+ name: "MultiLineString",
23
+ element: "path",
24
+ multiple: true,
25
+ },
26
+ {
27
+ type: 'polygon',
28
+ name: "Polygon",
29
+ element: "path"
30
+ },
31
+ {
32
+ name: "MultiPoint",
33
+ element: "point",
34
+ multiple: true,
35
+ },
36
+ {
37
+ name: "MultiPolygon",
38
+ element: "polygon",
39
+ multiple: true,
40
+ }
41
+ ];
42
+ /**
43
+ * 将 Geo 对象转换为 PostGIS 的 WKT 格式
44
+ */
45
+ function transformGeoData(data) {
46
+ if (data instanceof Array) {
47
+ const element = data[0];
48
+ if (element instanceof Array) {
49
+ // GeometryCollection
50
+ return `ST_GeomFromText('GEOMETRYCOLLECTION(${data.map(ele => transformGeoData(ele).replace(/ST_GeomFromText\('|'\)/g, '')).join(',')})')`;
51
+ }
52
+ else {
53
+ // Multi 类型
54
+ const geoType = GeoTypes.find(ele => ele.type === element.type);
55
+ if (!geoType) {
56
+ throw new Error(`${element.type} is not supported in PostgreSQL`);
57
+ }
58
+ const multiGeoType = GeoTypes.find(ele => ele.element === geoType.type && ele.multiple);
59
+ if (!multiGeoType) {
60
+ throw new Error(`Multi type for ${element.type} not found`);
61
+ }
62
+ const innerWkt = data.map(ele => {
63
+ const wkt = transformGeoData(ele);
64
+ // 提取括号内的坐标部分
65
+ const match = wkt.match(/\(([^)]+)\)/);
66
+ return match ? match[0] : '';
67
+ }).join(',');
68
+ return `ST_GeomFromText('${multiGeoType.name.toUpperCase()}(${innerWkt})')`;
69
+ }
70
+ }
71
+ else {
72
+ const { type, coordinate } = data;
73
+ const geoType = GeoTypes.find(ele => ele.type === type);
74
+ if (!geoType) {
75
+ throw new Error(`${type} is not supported in PostgreSQL`);
76
+ }
77
+ const { element, name } = geoType;
78
+ if (!element) {
79
+ // Point: coordinate 是 [x, y]
80
+ return `ST_GeomFromText('POINT(${coordinate.join(' ')})')`;
81
+ }
82
+ if (type === 'path') {
83
+ // LineString: coordinate 是 [[x1,y1], [x2,y2], ...]
84
+ const points = coordinate
85
+ .map(p => p.join(' '))
86
+ .join(',');
87
+ return `ST_GeomFromText('LINESTRING(${points})')`;
88
+ }
89
+ if (type === 'polygon') {
90
+ // Polygon: coordinate 是 [[[x1,y1], [x2,y2], ...], [...]](外环和内环)
91
+ const rings = coordinate
92
+ .map(ring => `(${ring.map(p => p.join(' ')).join(',')})`)
93
+ .join(',');
94
+ return `ST_GeomFromText('POLYGON(${rings})')`;
95
+ }
96
+ throw new Error(`Unsupported geometry type: ${type}`);
97
+ }
98
+ }
99
+ class PostgreSQLTranslator extends sqlTranslator_1.SqlTranslator {
100
+ // 生成 enum 类型名称
101
+ getEnumTypeName(entity, attr) {
102
+ return `${entity}_${attr}_enum`.toLowerCase();
103
+ }
104
+ /**
105
+ * 将 MySQL 风格的 JSON 路径转换为 PostgreSQL 路径数组格式
106
+ * 例如: ".foo.bar[0].baz" -> '{foo,bar,0,baz}'
107
+ */
108
+ convertJsonPath(mysqlPath) {
109
+ if (!mysqlPath || mysqlPath === '') {
110
+ return '';
111
+ }
112
+ // 移除开头的点
113
+ let path = mysqlPath.startsWith('.') ? mysqlPath.slice(1) : mysqlPath;
114
+ // 解析路径组件
115
+ const components = [];
116
+ let current = '';
117
+ let i = 0;
118
+ while (i < path.length) {
119
+ const char = path[i];
120
+ if (char === '.') {
121
+ if (current) {
122
+ components.push(current);
123
+ current = '';
124
+ }
125
+ i++;
126
+ }
127
+ else if (char === '[') {
128
+ if (current) {
129
+ components.push(current);
130
+ current = '';
131
+ }
132
+ // 找到对应的 ]
133
+ const endBracket = path.indexOf(']', i);
134
+ if (endBracket === -1) {
135
+ throw new Error(`Invalid JSON path: unmatched [ at position ${i}`);
136
+ }
137
+ const index = path.slice(i + 1, endBracket);
138
+ components.push(index);
139
+ i = endBracket + 1;
140
+ }
141
+ else {
142
+ current += char;
143
+ i++;
144
+ }
145
+ }
146
+ if (current) {
147
+ components.push(current);
148
+ }
149
+ return `'{${components.join(',')}}'`;
150
+ }
151
+ /**
152
+ * 生成 PostgreSQL JSON 访问表达式
153
+ * @param column 列名(包含别名)
154
+ * @param path JSON 路径
155
+ * @param asText 是否返回文本(使用 #>> 而不是 #>)
156
+ */
157
+ buildJsonAccessor(column, path, asText = true) {
158
+ if (!path || path === '') {
159
+ return column;
160
+ }
161
+ const pgPath = this.convertJsonPath(path);
162
+ const operator = asText ? '#>>' : '#>';
163
+ return `${column}${operator}${pgPath}`;
164
+ }
165
+ getDefaultSelectFilter(alias, option) {
166
+ if (option?.includedDeleted) {
167
+ return '';
168
+ }
169
+ return ` ("${alias}"."$$deleteAt$$" is null)`;
170
+ }
171
+ makeUpSchema() {
172
+ for (const entity in this.schema) {
173
+ const { attributes, indexes } = this.schema[entity];
174
+ const geoIndexes = [];
175
+ for (const attr in attributes) {
176
+ if (attributes[attr].type === 'geometry') {
177
+ const geoIndex = indexes?.find((idx) => idx.config?.type === 'spatial' && idx.attributes.find((attrDef) => attrDef.name === attr));
178
+ if (!geoIndex) {
179
+ geoIndexes.push({
180
+ name: `${entity}_geo_${attr}`,
181
+ attributes: [{
182
+ name: attr,
183
+ }],
184
+ config: {
185
+ type: 'spatial',
186
+ }
187
+ });
188
+ }
189
+ }
190
+ }
191
+ if (geoIndexes.length > 0) {
192
+ if (indexes) {
193
+ indexes.push(...geoIndexes);
194
+ }
195
+ else {
196
+ (0, lodash_1.assign)(this.schema[entity], {
197
+ indexes: geoIndexes,
198
+ });
199
+ }
200
+ }
201
+ }
202
+ }
203
+ constructor(schema) {
204
+ super(schema);
205
+ // PostgreSQL为geometry属性默认创建索引
206
+ this.makeUpSchema();
207
+ }
208
+ static supportedDataTypes = [
209
+ // numeric types
210
+ "smallint",
211
+ "integer",
212
+ "bigint",
213
+ "decimal",
214
+ "numeric",
215
+ "real",
216
+ "double precision",
217
+ // TODO: 下面三种类型暂不支持(在oak-domain中未定义)
218
+ // "serial",
219
+ // "bigserial",
220
+ // "smallserial",
221
+ // boolean
222
+ "boolean",
223
+ // date and time types
224
+ "date",
225
+ "time",
226
+ "timestamp",
227
+ "timestamptz",
228
+ "interval",
229
+ // string types
230
+ "char",
231
+ "varchar",
232
+ "text",
233
+ // binary
234
+ "bytea",
235
+ // json data types
236
+ "json",
237
+ "jsonb",
238
+ // uuid
239
+ "uuid",
240
+ // spatial data types (PostGIS)
241
+ "geometry",
242
+ "geography",
243
+ "point",
244
+ "line",
245
+ "lseg",
246
+ "box",
247
+ "path",
248
+ "polygon",
249
+ "circle"
250
+ ];
251
+ static spatialTypes = [
252
+ "geometry",
253
+ "geography",
254
+ "point",
255
+ "line",
256
+ "lseg",
257
+ "box",
258
+ "path",
259
+ "polygon",
260
+ "circle"
261
+ ];
262
+ static withLengthDataTypes = [
263
+ "char",
264
+ "varchar",
265
+ "bit",
266
+ "varbit"
267
+ ];
268
+ static withPrecisionDataTypes = [
269
+ "decimal",
270
+ "numeric",
271
+ "time",
272
+ "timestamp",
273
+ "timestamptz"
274
+ ];
275
+ static withScaleDataTypes = [
276
+ "decimal",
277
+ "numeric"
278
+ ];
279
+ // PostgreSQL 不支持 unsigned,移除此列表
280
+ // static unsignedAndZerofillTypes - 删除
281
+ // PostgreSQL 不使用 width 概念
282
+ // static withWidthDataTypes - 删除
283
+ static dataTypeDefaults = {
284
+ "varchar": { length: 255 },
285
+ "char": { length: 1 },
286
+ "decimal": { precision: 10, scale: 0 },
287
+ "numeric": { precision: 10, scale: 0 },
288
+ "time": { precision: 6 },
289
+ "timestamp": { precision: 6 },
290
+ "timestamptz": { precision: 6 },
291
+ "bit": { length: 1 },
292
+ "varbit": { length: 255 }
293
+ };
294
+ maxAliasLength = 63;
295
+ populateDataTypeDef(type, params, enumeration) {
296
+ // 时间类型 - 框架内部用 bigint 存储 Unix 时间戳
297
+ if (['date', 'datetime', 'time', 'sequence'].includes(type)) {
298
+ return 'bigint';
299
+ }
300
+ // JSON 类型 - PostgreSQL 推荐使用 jsonb
301
+ if (['object', 'array'].includes(type)) {
302
+ return 'jsonb';
303
+ }
304
+ // 文本类型
305
+ if (['image', 'function'].includes(type)) {
306
+ return 'text';
307
+ }
308
+ // 引用类型 - UUID 字符串
309
+ if (type === 'ref') {
310
+ return 'char(36)';
311
+ }
312
+ // 金额类型
313
+ if (type === 'money') {
314
+ return 'bigint';
315
+ }
316
+ // 布尔类型
317
+ if (type === 'bool' || type === 'boolean') {
318
+ return 'boolean';
319
+ }
320
+ // 枚举类型 - PostgreSQL 需要使用预创建的 TYPE
321
+ // 这里返回类型名称,实际创建在 translateCreateEntity 中处理
322
+ if (type === 'enum') {
323
+ (0, assert_1.default)(enumeration);
324
+ // 返回一个占位符,实际类型名会在创建表时确定
325
+ return '__ENUM_PLACEHOLDER__';
326
+ }
327
+ // int 类型映射
328
+ if (type === 'int' || type === 'integer') {
329
+ if (params?.width) {
330
+ const { width } = params;
331
+ if (width <= 2) {
332
+ return 'smallint';
333
+ }
334
+ else if (width <= 4) {
335
+ return 'integer';
336
+ }
337
+ else {
338
+ return 'bigint';
339
+ }
340
+ }
341
+ return 'integer';
342
+ }
343
+ // MySQL 特有类型映射到 PostgreSQL
344
+ if (type === 'tinyint') {
345
+ return 'smallint';
346
+ }
347
+ if (type === 'mediumint') {
348
+ return 'integer';
349
+ }
350
+ if (type === 'double') {
351
+ return 'double precision';
352
+ }
353
+ if (type === 'float') {
354
+ return 'real';
355
+ }
356
+ if (type === 'datetime') {
357
+ return 'timestamp';
358
+ }
359
+ if (type === 'blob' || type === 'tinyblob' || type === 'mediumblob' || type === 'longblob') {
360
+ return 'bytea';
361
+ }
362
+ if (type === 'tinytext' || type === 'mediumtext' || type === 'longtext') {
363
+ return 'text';
364
+ }
365
+ // 带长度的类型
366
+ if (PostgreSQLTranslator.withLengthDataTypes.includes(type)) {
367
+ const defaults = PostgreSQLTranslator.dataTypeDefaults[type];
368
+ const length = params?.length ?? defaults?.length;
369
+ if (length) {
370
+ return `${type}(${length})`;
371
+ }
372
+ return type;
373
+ }
374
+ // 带精度的类型
375
+ if (PostgreSQLTranslator.withPrecisionDataTypes.includes(type)) {
376
+ const defaults = PostgreSQLTranslator.dataTypeDefaults[type];
377
+ const precision = params?.precision ?? defaults?.precision;
378
+ const scale = params?.scale ?? defaults?.scale;
379
+ if (precision !== undefined) {
380
+ if (scale !== undefined && PostgreSQLTranslator.withScaleDataTypes.includes(type)) {
381
+ return `${type}(${precision}, ${scale})`;
382
+ }
383
+ return `${type}(${precision})`;
384
+ }
385
+ return type;
386
+ }
387
+ // geometry 类型 - 需要 PostGIS 扩展
388
+ if (type === 'geometry') {
389
+ return 'geometry';
390
+ }
391
+ return type;
392
+ }
393
+ /**
394
+ * PostgreSQL 字符串值转义
395
+ * 防御SQL注入
396
+ */
397
+ escapeStringValue(value) {
398
+ if (value === null || value === undefined) {
399
+ return 'NULL';
400
+ }
401
+ // PostgreSQL 标准 SQL 转义
402
+ // 1. 单引号转义为两个单引号
403
+ // 2. 反斜杠在 standard_conforming_strings=on(默认)时不需要特殊处理
404
+ const escaped = String(value).replace(/'/g, "''");
405
+ return `'${escaped}'`;
406
+ }
407
+ /**
408
+ * LIKE 模式转义
409
+ * 转义 LIKE 语句中的特殊字符
410
+ */
411
+ escapeLikePattern(value) {
412
+ // 先转义 LIKE 的特殊字符
413
+ const escaped = String(value)
414
+ .replace(/\\/g, '\\\\') // 反斜杠必须先处理
415
+ .replace(/%/g, '\\%') // 百分号
416
+ .replace(/_/g, '\\_'); // 下划线
417
+ // 再进行字符串值转义
418
+ return escaped.replace(/'/g, "''");
419
+ }
420
+ /**
421
+ * PostgreSQL 标识符转义
422
+ * 用于表名、列名等
423
+ */
424
+ escapeIdentifier(identifier) {
425
+ // 标识符中的双引号转义为两个双引号
426
+ const escaped = String(identifier).replace(/"/g, '""');
427
+ return `"${escaped}"`;
428
+ }
429
+ /**
430
+ * tsquery 搜索词转义
431
+ */
432
+ escapeTsQueryValue(value) {
433
+ // 移除或转义 tsquery 的特殊字符
434
+ return String(value)
435
+ .replace(/'/g, "''") // SQL 单引号
436
+ .replace(/[&|!():<>\\]/g, ' ') // tsquery 特殊字符替换为空格
437
+ .replace(/\s+/g, ' ') // 合并多个空格
438
+ .trim();
439
+ }
440
+ quoteIdentifier(identifier) {
441
+ return `"${identifier}"`;
442
+ }
443
+ translateAttrProjection(dataType, alias, attr) {
444
+ switch (dataType) {
445
+ case 'geometry': {
446
+ return ` st_astext("${alias}"."${attr}")`;
447
+ }
448
+ default: {
449
+ return ` "${alias}"."${attr}"`;
450
+ }
451
+ }
452
+ }
453
+ translateObjectPredicate(predicate, alias, attr) {
454
+ const columnRef = `"${alias}"."${attr}"`;
455
+ const translateInner = (o, p) => {
456
+ let stmt2 = '';
457
+ if (o instanceof Array) {
458
+ o.forEach((ele, idx) => {
459
+ if (ele !== undefined && ele !== null) {
460
+ const part = translateInner(ele, `${p}[${idx}]`);
461
+ if (stmt2) {
462
+ stmt2 += ' AND ';
463
+ }
464
+ stmt2 += part;
465
+ }
466
+ });
467
+ }
468
+ else if (typeof o === 'object' && o !== null) {
469
+ for (const attr2 in o) {
470
+ if (attr2 === '$and') {
471
+ const andParts = [];
472
+ o[attr2].forEach((ele) => {
473
+ const part = translateInner(ele, p);
474
+ if (part) {
475
+ andParts.push(part);
476
+ }
477
+ });
478
+ if (andParts.length > 0) {
479
+ if (stmt2) {
480
+ stmt2 += ' AND ';
481
+ }
482
+ stmt2 += `(${andParts.join(' AND ')})`;
483
+ }
484
+ }
485
+ else if (attr2 === '$or') {
486
+ const orParts = [];
487
+ o[attr2].forEach((ele) => {
488
+ const part = translateInner(ele, p);
489
+ if (part) {
490
+ orParts.push(part);
491
+ }
492
+ });
493
+ if (orParts.length > 0) {
494
+ if (stmt2) {
495
+ stmt2 += ' AND ';
496
+ }
497
+ stmt2 += `(${orParts.join(' OR ')})`;
498
+ }
499
+ }
500
+ else if (attr2 === '$contains') {
501
+ // PostgreSQL 使用 @> 操作符检查 JSON 包含关系
502
+ const targetArr = Array.isArray(o[attr2]) ? o[attr2] : [o[attr2]];
503
+ const value = JSON.stringify(targetArr);
504
+ if (stmt2) {
505
+ stmt2 += ' AND ';
506
+ }
507
+ if (p) {
508
+ // 先提取子路径,再检查包含
509
+ const accessor = this.buildJsonAccessor(columnRef, p, false);
510
+ stmt2 += `(${accessor} @> '${value}'::jsonb)`;
511
+ }
512
+ else {
513
+ stmt2 += `(${columnRef} @> '${value}'::jsonb)`;
514
+ }
515
+ }
516
+ else if (attr2 === '$overlaps') {
517
+ // PostgreSQL 使用 ?| 检查数组键重叠,或使用自定义逻辑
518
+ const value = o[attr2];
519
+ if (stmt2) {
520
+ stmt2 += ' AND ';
521
+ }
522
+ if (Array.isArray(value)) {
523
+ // 如果是数组,检查是否有任意元素匹配
524
+ const accessor = p ? this.buildJsonAccessor(columnRef, p, false) : columnRef;
525
+ // 使用 @> 操作符检查数组是否包含任意一个值(支持数字和字符串)
526
+ const conditions = value.map(v => `${accessor} @> '${JSON.stringify(v)}'::jsonb`);
527
+ stmt2 += `(${conditions.join(' OR ')})`;
528
+ }
529
+ else if (typeof value === 'object' && value !== null) {
530
+ // 对象重叠检查 - 检查是否有共同的键
531
+ const keys = Object.keys(value);
532
+ if (p) {
533
+ const accessor = this.buildJsonAccessor(columnRef, p, false);
534
+ stmt2 += `(${accessor} ?| array[${keys.map(k => `'${k}'`).join(', ')}])`;
535
+ }
536
+ else {
537
+ stmt2 += `(${columnRef} ?| array[${keys.map(k => `'${k}'`).join(', ')}])`;
538
+ }
539
+ }
540
+ else {
541
+ (0, assert_1.default)(typeof value === 'string');
542
+ // 单个键的重叠检查
543
+ if (p) {
544
+ const accessor = this.buildJsonAccessor(columnRef, p, false);
545
+ stmt2 += `(${accessor} ? '${value}')`;
546
+ }
547
+ else {
548
+ stmt2 += `(${columnRef} ? '${value}')`;
549
+ }
550
+ }
551
+ }
552
+ else if (attr2 === '$length') {
553
+ // PostgreSQL 使用 jsonb_array_length
554
+ const length = o[attr2];
555
+ if (stmt2) {
556
+ stmt2 += ' AND ';
557
+ }
558
+ let lengthExpr;
559
+ if (p) {
560
+ const accessor = this.buildJsonAccessor(columnRef, p, false);
561
+ lengthExpr = `jsonb_array_length(${accessor})`;
562
+ }
563
+ else {
564
+ lengthExpr = `jsonb_array_length(${columnRef})`;
565
+ }
566
+ if (typeof length === 'number') {
567
+ stmt2 += `(${lengthExpr} = ${length})`;
568
+ }
569
+ else {
570
+ (0, assert_1.default)(typeof length === 'object');
571
+ const op = Object.keys(length)[0];
572
+ (0, assert_1.default)(op.startsWith('$'));
573
+ stmt2 += `(${lengthExpr} ${this.translatePredicate(op, length[op])})`;
574
+ }
575
+ }
576
+ else if (attr2 === '$exists') {
577
+ if (stmt2) {
578
+ stmt2 += ' AND ';
579
+ }
580
+ const existsValue = o[attr2];
581
+ if (typeof existsValue === 'boolean') {
582
+ // $exists: true/false - 检查当前路径的值是否存在(不为 null)
583
+ if (p) {
584
+ const accessor = this.buildJsonAccessor(columnRef, p, true);
585
+ if (existsValue) {
586
+ stmt2 += `(${accessor} IS NOT NULL)`;
587
+ }
588
+ else {
589
+ stmt2 += `(${accessor} IS NULL)`;
590
+ }
591
+ }
592
+ else {
593
+ if (existsValue) {
594
+ stmt2 += `(${columnRef} IS NOT NULL)`;
595
+ }
596
+ else {
597
+ stmt2 += `(${columnRef} IS NULL)`;
598
+ }
599
+ }
600
+ }
601
+ else {
602
+ // $exists: 'keyName' - 检查 JSON 对象是否包含指定的键
603
+ const keyToCheck = existsValue;
604
+ if (p) {
605
+ const accessor = this.buildJsonAccessor(columnRef, p, false);
606
+ stmt2 += `(${accessor} ? '${keyToCheck}')`;
607
+ }
608
+ else {
609
+ stmt2 += `(${columnRef} ? '${keyToCheck}')`;
610
+ }
611
+ }
612
+ }
613
+ else if (attr2.startsWith('$')) {
614
+ // 其他操作符:$gt, $lt, $eq 等
615
+ if (stmt2) {
616
+ stmt2 += ' AND ';
617
+ }
618
+ if (p) {
619
+ const accessor = this.buildJsonAccessor(columnRef, p, true);
620
+ // JSON 提取的文本需要转换类型进行比较
621
+ const predicate = this.translatePredicate(attr2, o[attr2]);
622
+ if (typeof o[attr2] === 'number') {
623
+ stmt2 += `((${accessor})::numeric ${predicate})`;
624
+ }
625
+ else if (typeof o[attr2] === 'boolean') {
626
+ stmt2 += `((${accessor})::boolean ${predicate})`;
627
+ }
628
+ else {
629
+ stmt2 += `(${accessor} ${predicate})`;
630
+ }
631
+ }
632
+ else {
633
+ stmt2 += `(${columnRef} ${this.translatePredicate(attr2, o[attr2])})`;
634
+ }
635
+ }
636
+ else {
637
+ // 继续子对象解构
638
+ const attr3 = attr2.startsWith('.') ? attr2.slice(1) : attr2;
639
+ const part = translateInner(o[attr2], p ? `${p}.${attr3}` : `.${attr3}`);
640
+ if (part) {
641
+ if (stmt2) {
642
+ stmt2 += ' AND ';
643
+ }
644
+ stmt2 += part;
645
+ }
646
+ }
647
+ }
648
+ }
649
+ else {
650
+ // 直接的值比较
651
+ if (stmt2) {
652
+ stmt2 += ' AND ';
653
+ }
654
+ if (p) {
655
+ const accessor = this.buildJsonAccessor(columnRef, p, true);
656
+ if (typeof o === 'string') {
657
+ stmt2 += `(${accessor} = '${o}')`;
658
+ }
659
+ else if (typeof o === 'number') {
660
+ stmt2 += `((${accessor})::numeric = ${o})`;
661
+ }
662
+ else if (typeof o === 'boolean') {
663
+ stmt2 += `((${accessor})::boolean = ${o})`;
664
+ }
665
+ else {
666
+ stmt2 += `(${accessor} = ${o})`;
667
+ }
668
+ }
669
+ else {
670
+ // 对根对象的比较
671
+ if (typeof o === 'string') {
672
+ stmt2 += `(${columnRef}::text = '${o}')`;
673
+ }
674
+ else {
675
+ stmt2 += `(${columnRef} = '${JSON.stringify(o)}'::jsonb)`;
676
+ }
677
+ }
678
+ }
679
+ return stmt2;
680
+ };
681
+ return translateInner(predicate, '');
682
+ }
683
+ translateObjectProjection(projection, alias, attr, prefix) {
684
+ const columnRef = `"${alias}"."${attr}"`;
685
+ let stmt = '';
686
+ const translateInner = (o, p) => {
687
+ if (o instanceof Array) {
688
+ o.forEach((item, idx) => {
689
+ const p2 = `${p}[${idx}]`;
690
+ if (typeof item === 'number') {
691
+ // 数字表示选择该位置的值
692
+ if (stmt) {
693
+ stmt += ', ';
694
+ }
695
+ const accessor = this.buildJsonAccessor(columnRef, p2, true);
696
+ stmt += accessor;
697
+ // 生成别名,将路径中的特殊字符替换
698
+ stmt += prefix ? ` AS "${prefix}.${attr}${p2}"` : ` AS "${attr}${p2}"`;
699
+ }
700
+ else if (typeof item === 'object') {
701
+ translateInner(item, p2);
702
+ }
703
+ });
704
+ }
705
+ else {
706
+ for (const key in o) {
707
+ const p2 = p ? `${p}.${key}` : `.${key}`;
708
+ if (typeof o[key] === 'number') {
709
+ // 数字表示选择该字段
710
+ if (stmt) {
711
+ stmt += ', ';
712
+ }
713
+ const accessor = this.buildJsonAccessor(columnRef, p2, true);
714
+ stmt += accessor;
715
+ // 生成别名
716
+ stmt += prefix ? ` AS "${prefix}.${attr}${p2}"` : ` AS "${attr}${p2}"`;
717
+ }
718
+ else {
719
+ translateInner(o[key], p2);
720
+ }
721
+ }
722
+ }
723
+ };
724
+ translateInner(projection, '');
725
+ return stmt;
726
+ }
727
+ translateAttrValue(dataType, value) {
728
+ if (value === null || value === undefined) {
729
+ return 'null';
730
+ }
731
+ switch (dataType) {
732
+ case 'geometry': {
733
+ return transformGeoData(value);
734
+ }
735
+ case 'datetime':
736
+ case 'time':
737
+ case 'date': {
738
+ if (value instanceof Date) {
739
+ return `${value.valueOf()}`;
740
+ }
741
+ else if (typeof value === 'number') {
742
+ return `${value}`;
743
+ }
744
+ (0, assert_1.default)(typeof value === 'string', 'Invalid date/time value');
745
+ return `'${(new Date(value)).valueOf()}'`;
746
+ }
747
+ case 'object':
748
+ case 'array': {
749
+ // PostgreSQL JSONB 需要显式类型转换
750
+ const jsonStr = JSON.stringify(value).replace(/'/g, "''");
751
+ return `'${jsonStr}'::jsonb`;
752
+ }
753
+ /* case 'function': {
754
+ return `'${Buffer.from(value.toString()).toString('base64')}'`;
755
+ } */
756
+ default: {
757
+ if (typeof value === 'string') {
758
+ return this.escapeStringValue(value);
759
+ }
760
+ return value;
761
+ }
762
+ }
763
+ }
764
+ translateFullTextSearch(value, entity, alias) {
765
+ const { $search, $ts } = value;
766
+ const { indexes } = this.schema[entity];
767
+ const ftIndex = indexes && indexes.find((ele) => {
768
+ const { config } = ele;
769
+ return config && config.type === 'fulltext';
770
+ });
771
+ (0, assert_1.default)(ftIndex, `Entity ${String(entity)} does not have a fulltext index`);
772
+ const { attributes } = ftIndex;
773
+ // PostgreSQL 全文搜索使用 to_tsvector 和 to_tsquery
774
+ // 将多个列合并成一个文档
775
+ const columns = attributes.map(({ name }) => `COALESCE("${alias}"."${name}", '')`).join(" || ' ' || ");
776
+ // 处理搜索词:将空格分隔的词转换为 & 连接
777
+ const searchTerms = $search
778
+ .trim()
779
+ .split(/\s+/)
780
+ .filter(term => term.length > 0)
781
+ .map(term => this.escapeTsQueryValue(term))
782
+ .filter(term => term.length > 0) // 过滤掉转义后为空的词
783
+ .join(' & ');
784
+ if (!searchTerms) {
785
+ // 如果没有有效的搜索词,返回一个始终为假的条件,以避免匹配任何记录
786
+ console.error('Full-text search: no valid search terms after escaping, returning FALSE condition');
787
+ return 'FALSE';
788
+ }
789
+ const indexTsConfig = ftIndex.config?.tsConfig;
790
+ let searchTsConfig;
791
+ if (Array.isArray(indexTsConfig)) {
792
+ // 如果是Array的情况,则需要去判断一下在不在索引定义中,没定义的话要警告
793
+ if (!$ts) {
794
+ if (indexTsConfig.length === 0) {
795
+ // 没有$ts也没配置索引的情况,和定义的时候一样默认走simple
796
+ searchTsConfig = 'simple';
797
+ }
798
+ else {
799
+ // 如果没提供$ts,则无法确定唯一的索引方法,目前先走索引定义时的第一个
800
+ console.warn(`Full-text search: Entity: ${String(entity)} has multi fulltext Index: ${indexTsConfig.join(', ')} please provide a specific "$ts" value! now using: ${indexTsConfig[0]}`);
801
+ return `to_tsvector('${indexTsConfig[0]}', ${columns}) @@ to_tsquery('simple', '${searchTerms}')`; // 使用simple去查询,这里就可能不走索引
802
+ }
803
+ }
804
+ else {
805
+ // 提供了ts,找indexTsConfig中符合$ts的存在
806
+ const find = indexTsConfig.findIndex(item => item === $ts);
807
+ if (find === -1) {
808
+ // 如果$ts 在indexTsConfig里面没定义,说明大概率查询的时候不会走索引,需要给个警告
809
+ console.warn(`Full-text search: provided "$ts" didnt match any tsConfig in the fulltext Index: ${ftIndex.name} on Entity: ${String(entity)}`);
810
+ }
811
+ // 提供了就用
812
+ searchTsConfig = $ts;
813
+ }
814
+ }
815
+ else {
816
+ searchTsConfig = $ts || indexTsConfig || 'simple'; // 这里的使用顺序是,$ts > indexTsConfig > default('simple')
817
+ }
818
+ return `to_tsvector('${searchTsConfig}', ${columns}) @@ to_tsquery('${searchTsConfig}', '${searchTerms}')`;
819
+ }
820
+ translateCreateEntity(entity, options) {
821
+ const ifExists = options?.ifExists || 'drop';
822
+ const { schema } = this;
823
+ const entityDef = schema[entity];
824
+ const { storageName, attributes, indexes, view, static: _static } = entityDef;
825
+ const tableName = storageName || entity;
826
+ const sqls = [];
827
+ // 收集需要创建的 enum 类型
828
+ const enumTypesToCreate = [];
829
+ // 处理删除逻辑
830
+ if (ifExists === 'drop' || (!_static && ifExists === 'dropIfNotStatic')) {
831
+ // 先删除表
832
+ sqls.push(`DROP TABLE IF EXISTS "${tableName}" CASCADE;`);
833
+ }
834
+ // 预处理:检查所有 enum 类型
835
+ for (const attr in attributes) {
836
+ const attrDef = attributes[attr];
837
+ if (attrDef.type === 'enum' && attrDef.enumeration) {
838
+ const enumTypeName = this.getEnumTypeName(tableName, attr);
839
+ enumTypesToCreate.push({
840
+ name: enumTypeName,
841
+ values: attrDef.enumeration
842
+ });
843
+ }
844
+ }
845
+ // 删除并重建 enum 类型
846
+ for (const enumType of enumTypesToCreate) {
847
+ if (ifExists === 'drop' || (!_static && ifExists === 'dropIfNotStatic')) {
848
+ sqls.push(`DROP TYPE IF EXISTS "${enumType.name}" CASCADE;`);
849
+ }
850
+ const createEnumSql = `CREATE TYPE "${enumType.name}" AS ENUM (${enumType.values.map(v => `'${v}'`).join(', ')});`;
851
+ if (ifExists === 'omit' || (_static && ifExists === 'dropIfNotStatic')) {
852
+ sqls.push(`DO $$ BEGIN ${createEnumSql} EXCEPTION WHEN duplicate_object THEN NULL; END $$;`);
853
+ }
854
+ else {
855
+ sqls.push(createEnumSql);
856
+ }
857
+ }
858
+ // todo view 暂还不支持
859
+ if (view) {
860
+ throw new Error('view unsupported yet');
861
+ }
862
+ // 构建 CREATE TABLE 语句
863
+ let createTableSql = 'CREATE TABLE ';
864
+ if (ifExists === 'omit' || (_static && ifExists === 'dropIfNotStatic')) {
865
+ createTableSql += 'IF NOT EXISTS ';
866
+ }
867
+ createTableSql += `"${tableName}" (\n`;
868
+ // 翻译所有的属性
869
+ const columnDefs = [];
870
+ let hasSequence = null;
871
+ for (const attr in attributes) {
872
+ const attrDef = attributes[attr];
873
+ const { type, params, default: defaultValue, unique, notNull, sequenceStart, enumeration, } = attrDef;
874
+ let columnDef = ` "${attr}" `;
875
+ // 处理 enum 类型
876
+ if (type === 'enum') {
877
+ const enumTypeName = this.getEnumTypeName(tableName, attr);
878
+ columnDef += `"${enumTypeName}"`;
879
+ }
880
+ else {
881
+ columnDef += this.populateDataTypeDef(type, params, enumeration);
882
+ }
883
+ // NOT NULL 约束
884
+ if (notNull || type === 'geometry') {
885
+ columnDef += ' NOT NULL';
886
+ }
887
+ // UNIQUE 约束
888
+ if (unique) {
889
+ columnDef += ' UNIQUE';
890
+ }
891
+ // 序列处理 - PostgreSQL 使用 GENERATED AS IDENTITY
892
+ if (sequenceStart) {
893
+ if (hasSequence) {
894
+ throw new Error(`「${entity}」只能有一个 sequence 列`);
895
+ }
896
+ hasSequence = attr;
897
+ // 替换类型为支持自增的类型
898
+ columnDef = ` "${attr}" bigint GENERATED BY DEFAULT AS IDENTITY (START WITH ${sequenceStart}) UNIQUE`;
899
+ }
900
+ // 默认值
901
+ if (defaultValue !== undefined && !sequenceStart) {
902
+ (0, assert_1.default)(type !== 'ref');
903
+ columnDef += ` DEFAULT ${this.translateAttrValue(type, defaultValue)}`;
904
+ }
905
+ // 主键
906
+ if (attr === 'id') {
907
+ columnDef += ' PRIMARY KEY';
908
+ }
909
+ columnDefs.push(columnDef);
910
+ }
911
+ createTableSql += columnDefs.join(',\n');
912
+ createTableSql += '\n);';
913
+ sqls.push(createTableSql);
914
+ // 单独创建索引 - PostgreSQL 索引必须在表外定义
915
+ if (indexes) {
916
+ for (const indexDef of indexes) {
917
+ const { name, attributes: indexAttrs, config } = indexDef;
918
+ const { unique, type: indexType, tsConfig } = config || {};
919
+ // 确定需要创建的索引配置列表
920
+ const indexConfigs = [];
921
+ if (indexType === 'fulltext') {
922
+ // 全文索引:根据 tsConfig 确定要创建的索引
923
+ if (Array.isArray(tsConfig)) {
924
+ if (tsConfig.length === 0) {
925
+ console.warn(`entity: ${String(entity)} has empty tsConfig array on fulltext index: ${name}, using 'simple' for default.`);
926
+ indexConfigs.push({ suffix: '', tsLang: 'simple' });
927
+ }
928
+ else if (tsConfig.length === 1) {
929
+ indexConfigs.push({ suffix: '', tsLang: tsConfig[0] });
930
+ }
931
+ else {
932
+ // 多语言:为每个语言创建独立索引
933
+ for (const lang of tsConfig) {
934
+ indexConfigs.push({ suffix: `_${lang}`, tsLang: lang });
935
+ }
936
+ }
937
+ }
938
+ else {
939
+ indexConfigs.push({ suffix: '', tsLang: tsConfig || 'simple' });
940
+ }
941
+ }
942
+ else {
943
+ // 非全文索引:只创建一个索引
944
+ indexConfigs.push({ suffix: '' });
945
+ }
946
+ // 为每个配置创建索引
947
+ for (const indexConfig of indexConfigs) {
948
+ const { suffix, tsLang } = indexConfig;
949
+ let indexSql = 'CREATE ';
950
+ if (unique) {
951
+ indexSql += 'UNIQUE ';
952
+ }
953
+ indexSql += 'INDEX ';
954
+ if (ifExists === 'omit' || (_static && ifExists === 'dropIfNotStatic')) {
955
+ indexSql += 'IF NOT EXISTS ';
956
+ }
957
+ // 索引名称(多语言时添加后缀)
958
+ indexSql += `"${String(entity)}_${name}${suffix}" ON "${tableName}" `;
959
+ // 索引方法
960
+ if (indexType === 'hash') {
961
+ indexSql += 'USING HASH ';
962
+ }
963
+ else if (indexType === 'spatial') {
964
+ indexSql += 'USING GIST ';
965
+ }
966
+ else if (indexType === 'fulltext') {
967
+ indexSql += 'USING GIN ';
968
+ }
969
+ indexSql += '(';
970
+ const indexColumns = [];
971
+ for (const indexAttr of indexAttrs) {
972
+ const { name: attrName, direction } = indexAttr;
973
+ if (indexType === 'fulltext') {
974
+ // 全文索引:使用 to_tsvector
975
+ indexColumns.push(`to_tsvector('${tsLang}', COALESCE("${attrName}", ''))`);
976
+ }
977
+ else {
978
+ // 普通索引:直接使用列名
979
+ let col = `"${attrName}"`;
980
+ if (direction) {
981
+ col += ` ${direction}`;
982
+ }
983
+ indexColumns.push(col);
984
+ }
985
+ }
986
+ indexSql += indexColumns.join(', ');
987
+ indexSql += ');';
988
+ sqls.push(indexSql);
989
+ }
990
+ }
991
+ }
992
+ return sqls;
993
+ }
994
+ translateFnName(fnName, argumentNumber) {
995
+ switch (fnName) {
996
+ // ========== 数学运算 ==========
997
+ case '$add': {
998
+ let result = '%s';
999
+ while (--argumentNumber > 0) {
1000
+ result += ' + %s';
1001
+ }
1002
+ return result;
1003
+ }
1004
+ case '$subtract': {
1005
+ (0, assert_1.default)(argumentNumber === 2);
1006
+ return '%s - %s';
1007
+ }
1008
+ case '$multiply': {
1009
+ let result = '%s';
1010
+ while (--argumentNumber > 0) {
1011
+ result += ' * %s';
1012
+ }
1013
+ return result;
1014
+ }
1015
+ case '$divide': {
1016
+ (0, assert_1.default)(argumentNumber === 2);
1017
+ return '%s / %s';
1018
+ }
1019
+ case '$abs': {
1020
+ return 'ABS(%s)';
1021
+ }
1022
+ case '$round': {
1023
+ (0, assert_1.default)(argumentNumber === 2);
1024
+ return 'ROUND(%s, %s)';
1025
+ }
1026
+ case '$ceil': {
1027
+ return 'CEIL(%s)';
1028
+ }
1029
+ case '$floor': {
1030
+ return 'FLOOR(%s)';
1031
+ }
1032
+ case '$mod': {
1033
+ // PostgreSQL 使用 MOD 函数或 % 操作符
1034
+ return 'MOD(%s, %s)';
1035
+ }
1036
+ case '$pow': {
1037
+ (0, assert_1.default)(argumentNumber === 2);
1038
+ // PostgreSQL 使用 POWER
1039
+ return 'POWER(%s, %s)';
1040
+ }
1041
+ // ========== 比较运算 ==========
1042
+ case '$gt': {
1043
+ (0, assert_1.default)(argumentNumber === 2);
1044
+ return '%s > %s';
1045
+ }
1046
+ case '$gte': {
1047
+ (0, assert_1.default)(argumentNumber === 2);
1048
+ return '%s >= %s';
1049
+ }
1050
+ case '$lt': {
1051
+ (0, assert_1.default)(argumentNumber === 2);
1052
+ return '%s < %s';
1053
+ }
1054
+ case '$lte': {
1055
+ (0, assert_1.default)(argumentNumber === 2);
1056
+ return '%s <= %s';
1057
+ }
1058
+ case '$eq': {
1059
+ (0, assert_1.default)(argumentNumber === 2);
1060
+ return '%s = %s';
1061
+ }
1062
+ case '$ne': {
1063
+ (0, assert_1.default)(argumentNumber === 2);
1064
+ return '%s <> %s';
1065
+ }
1066
+ // ========== 字符串操作 ==========
1067
+ case '$startsWith': {
1068
+ (0, assert_1.default)(argumentNumber === 2);
1069
+ // PostgreSQL 使用 || 进行字符串连接
1070
+ return '%s LIKE %s || \'%%\'';
1071
+ }
1072
+ case '$endsWith': {
1073
+ (0, assert_1.default)(argumentNumber === 2);
1074
+ return '%s LIKE \'%%\' || %s';
1075
+ }
1076
+ case '$includes': {
1077
+ (0, assert_1.default)(argumentNumber === 2);
1078
+ return '%s LIKE \'%%\' || %s || \'%%\'';
1079
+ }
1080
+ case '$concat': {
1081
+ // PostgreSQL 支持 CONCAT 函数
1082
+ let result = 'CONCAT(%s';
1083
+ while (--argumentNumber > 0) {
1084
+ result += ', %s';
1085
+ }
1086
+ result += ')';
1087
+ return result;
1088
+ }
1089
+ // ========== 布尔运算 ==========
1090
+ case '$true': {
1091
+ return '( %s ) = TRUE';
1092
+ }
1093
+ case '$false': {
1094
+ return '( %s ) = FALSE';
1095
+ }
1096
+ case '$and': {
1097
+ let result = '';
1098
+ for (let iter = 0; iter < argumentNumber; iter++) {
1099
+ result += '%s';
1100
+ if (iter < argumentNumber - 1) {
1101
+ result += ' AND ';
1102
+ }
1103
+ }
1104
+ return result;
1105
+ }
1106
+ case '$or': {
1107
+ let result = '';
1108
+ for (let iter = 0; iter < argumentNumber; iter++) {
1109
+ result += '%s';
1110
+ if (iter < argumentNumber - 1) {
1111
+ result += ' OR ';
1112
+ }
1113
+ }
1114
+ return result;
1115
+ }
1116
+ case '$not': {
1117
+ return 'NOT %s';
1118
+ }
1119
+ // ========== 日期时间函数 ==========
1120
+ case '$year': {
1121
+ return 'EXTRACT(YEAR FROM %s)::integer';
1122
+ }
1123
+ case '$month': {
1124
+ return 'EXTRACT(MONTH FROM %s)::integer';
1125
+ }
1126
+ case '$day': {
1127
+ return 'EXTRACT(DAY FROM %s)::integer';
1128
+ }
1129
+ case '$hour': {
1130
+ return 'EXTRACT(HOUR FROM %s)::integer';
1131
+ }
1132
+ case '$minute': {
1133
+ return 'EXTRACT(MINUTE FROM %s)::integer';
1134
+ }
1135
+ case '$second': {
1136
+ return 'EXTRACT(SECOND FROM %s)::integer';
1137
+ }
1138
+ case '$weekday': {
1139
+ // PostgreSQL ISODOW: 1(周一) - 7(周日)
1140
+ // 转换为 0(周一) - 6(周日) 以兼容 MySQL
1141
+ return '(EXTRACT(ISODOW FROM %s)::integer - 1)';
1142
+ }
1143
+ case '$weekOfYear': {
1144
+ return 'EXTRACT(WEEK FROM %s)::integer';
1145
+ }
1146
+ case '$dayOfMonth': {
1147
+ return 'EXTRACT(DAY FROM %s)::integer';
1148
+ }
1149
+ case '$dayOfWeek': {
1150
+ // PostgreSQL DOW: 0(周日) - 6(周六)
1151
+ // MySQL DAYOFWEEK: 1(周日) - 7(周六)
1152
+ // 转换以兼容 MySQL
1153
+ return '(EXTRACT(DOW FROM %s)::integer + 1)';
1154
+ }
1155
+ case '$dayOfYear': {
1156
+ return 'EXTRACT(DOY FROM %s)::integer';
1157
+ }
1158
+ // 下面两个函数需要特殊处理,放在 translateExpression 里实现
1159
+ // case '$dateDiff': {
1160
+ // // PostgreSQL 日期差值计算
1161
+ // // 参数: unit, date1, date2
1162
+ // // 返回 date1 - date2 的差值
1163
+ // assert(argumentNumber === 3);
1164
+ // // 注意:这里需要特殊处理,因为 PostgreSQL 的日期差异计算方式不同
1165
+ // return 'EXTRACT(EPOCH FROM (%s - %s))::integer / 86400';
1166
+ // }
1167
+ // case '$dateAdd': {
1168
+ // // 日期加法
1169
+ // assert(argumentNumber === 3);
1170
+ // // 参数: date, amount, unit
1171
+ // return '(%s + INTERVAL \'1 %s\' * %s)';
1172
+ // }
1173
+ case '$dateCeil': {
1174
+ // 日期向上取整 - 参数: [date, unit]
1175
+ // 在 translateExpression 中特殊处理
1176
+ (0, assert_1.default)(argumentNumber === 2);
1177
+ return '__DATE_CEIL__(%s, %s)';
1178
+ }
1179
+ case '$dateFloor': {
1180
+ // 日期向下取整 - 参数: [date, unit]
1181
+ // 在 translateExpression 中特殊处理
1182
+ (0, assert_1.default)(argumentNumber === 2);
1183
+ return '__DATE_FLOOR__(%s, %s)';
1184
+ }
1185
+ // ========== 地理空间函数 (PostGIS) ==========
1186
+ case '$contains': {
1187
+ (0, assert_1.default)(argumentNumber === 2);
1188
+ return 'ST_Contains(%s, %s)';
1189
+ }
1190
+ case '$distance': {
1191
+ (0, assert_1.default)(argumentNumber === 2);
1192
+ return 'ST_Distance(%s, %s)';
1193
+ }
1194
+ case '$within': {
1195
+ (0, assert_1.default)(argumentNumber === 2);
1196
+ return 'ST_Within(%s, %s)';
1197
+ }
1198
+ case '$intersects': {
1199
+ (0, assert_1.default)(argumentNumber === 2);
1200
+ return 'ST_Intersects(%s, %s)';
1201
+ }
1202
+ // ========== 聚合函数 ==========
1203
+ case '$$count': {
1204
+ return 'COUNT(%s)';
1205
+ }
1206
+ case '$$sum': {
1207
+ return 'SUM(%s)';
1208
+ }
1209
+ case '$$max': {
1210
+ return 'MAX(%s)';
1211
+ }
1212
+ case '$$min': {
1213
+ return 'MIN(%s)';
1214
+ }
1215
+ case '$$avg': {
1216
+ return 'AVG(%s)';
1217
+ }
1218
+ default: {
1219
+ throw new Error(`unrecognized function ${fnName}`);
1220
+ }
1221
+ }
1222
+ }
1223
+ translateAttrInExpression(entity, attr, exprText) {
1224
+ const { attributes } = this.schema[entity];
1225
+ const attrDef = attributes[attr];
1226
+ if (!attrDef) {
1227
+ return exprText;
1228
+ }
1229
+ const { type } = attrDef;
1230
+ if (['date', 'time', 'datetime'].includes(type)) {
1231
+ // 从 Unix 时间戳(毫秒)转成 timestamp 类型参加 expr 的运算
1232
+ // PostgreSQL 使用 TO_TIMESTAMP,参数为秒
1233
+ return `TO_TIMESTAMP(${exprText}::double precision / 1000)`;
1234
+ }
1235
+ return exprText;
1236
+ }
1237
+ translateExpression(entity, alias, expression, refDict) {
1238
+ const translateConstant = (constant) => {
1239
+ if (constant instanceof Date) {
1240
+ // PostgreSQL 使用 TO_TIMESTAMP,参数为秒
1241
+ return `TO_TIMESTAMP(${constant.valueOf()}::double precision / 1000)`;
1242
+ }
1243
+ else if (typeof constant === 'string') {
1244
+ // 转义单引号
1245
+ return `'${constant.replace(/'/g, "''")}'`;
1246
+ }
1247
+ else {
1248
+ (0, assert_1.default)(typeof constant === 'number');
1249
+ return `${constant}`;
1250
+ }
1251
+ };
1252
+ const translateInner = (expr) => {
1253
+ const k = Object.keys(expr);
1254
+ let result;
1255
+ if (k.includes('#attr')) {
1256
+ const attrName = (expr)['#attr'];
1257
+ const attrText = `"${alias}"."${attrName}"`;
1258
+ result = this.translateAttrInExpression(entity, attrName, attrText);
1259
+ }
1260
+ else if (k.includes('#refId')) {
1261
+ const refId = (expr)['#refId'];
1262
+ const refAttr = (expr)['#refAttr'];
1263
+ (0, assert_1.default)(refDict[refId]);
1264
+ const [refAlias, refEntity] = refDict[refId];
1265
+ const attrText = `"${refAlias}"."${refAttr}"`;
1266
+ result = this.translateAttrInExpression(refEntity, refAttr, attrText);
1267
+ }
1268
+ else {
1269
+ (0, assert_1.default)(k.length === 1);
1270
+ const fnKey = k[0];
1271
+ const fnArgs = (expr)[fnKey];
1272
+ // 特殊处理日期相关函数
1273
+ if (fnKey === '$dateDiff') {
1274
+ // $dateDiff: [date1, date2, unit]
1275
+ (0, assert_1.default)(fnArgs instanceof Array && fnArgs.length === 3);
1276
+ const [date1Expr, date2Expr, unit] = fnArgs;
1277
+ // 转换日期表达式
1278
+ const translateDateArg = (arg) => {
1279
+ if (arg instanceof Date) {
1280
+ return `TO_TIMESTAMP(${arg.valueOf()}::double precision / 1000)`;
1281
+ }
1282
+ else if (typeof arg === 'number') {
1283
+ return `TO_TIMESTAMP(${arg}::double precision / 1000)`;
1284
+ }
1285
+ else {
1286
+ return translateInner(arg);
1287
+ }
1288
+ };
1289
+ const date1Str = translateDateArg(date1Expr);
1290
+ const date2Str = translateDateArg(date2Expr);
1291
+ // 根据单位生成不同的 SQL
1292
+ switch (unit) {
1293
+ case 's':
1294
+ result = `EXTRACT(EPOCH FROM (${date1Str} - ${date2Str}))::bigint`;
1295
+ break;
1296
+ case 'm':
1297
+ result = `(EXTRACT(EPOCH FROM (${date1Str} - ${date2Str})) / 60)::bigint`;
1298
+ break;
1299
+ case 'h':
1300
+ result = `(EXTRACT(EPOCH FROM (${date1Str} - ${date2Str})) / 3600)::bigint`;
1301
+ break;
1302
+ case 'd':
1303
+ result = `(EXTRACT(EPOCH FROM (${date1Str} - ${date2Str})) / 86400)::bigint`;
1304
+ break;
1305
+ case 'M':
1306
+ // 月份差异使用 AGE 函数
1307
+ result = `(EXTRACT(YEAR FROM AGE(${date1Str}, ${date2Str})) * 12 + EXTRACT(MONTH FROM AGE(${date1Str}, ${date2Str})))::bigint`;
1308
+ break;
1309
+ case 'y':
1310
+ result = `EXTRACT(YEAR FROM AGE(${date1Str}, ${date2Str}))::bigint`;
1311
+ break;
1312
+ default:
1313
+ throw new Error(`Unsupported date diff unit: ${unit}`);
1314
+ }
1315
+ }
1316
+ else if (fnKey === '$dateCeil') {
1317
+ // $dateCeil: [date, unit]
1318
+ (0, assert_1.default)(fnArgs instanceof Array && fnArgs.length === 2);
1319
+ const [dateExpr, unit] = fnArgs;
1320
+ // 获取毫秒时间戳表达式
1321
+ const getTimestampExpr = (arg) => {
1322
+ if (arg instanceof Date) {
1323
+ return `${arg.valueOf()}`;
1324
+ }
1325
+ else if (typeof arg === 'number') {
1326
+ return `${arg}`;
1327
+ }
1328
+ else {
1329
+ // 属性引用,直接返回属性(存储本身就是毫秒时间戳)
1330
+ const k = Object.keys(arg);
1331
+ if (k.includes('#attr')) {
1332
+ return `"${alias}"."${arg['#attr']}"`;
1333
+ }
1334
+ else if (k.includes('#refId')) {
1335
+ const refId = arg['#refId'];
1336
+ const refAttr = arg['#refAttr'];
1337
+ return `"${refDict[refId][0]}"."${refAttr}"`;
1338
+ }
1339
+ // 其他表达式递归处理
1340
+ return translateInner(arg);
1341
+ }
1342
+ };
1343
+ const tsExpr = getTimestampExpr(dateExpr);
1344
+ // 固定间隔单位:直接用时间戳数学运算
1345
+ const msPerUnit = {
1346
+ 's': 1000,
1347
+ 'm': 60000,
1348
+ 'h': 3600000,
1349
+ 'd': 86400000,
1350
+ };
1351
+ if (msPerUnit[unit]) {
1352
+ // CEIL 向上取整:CEIL(timestamp / interval) * interval
1353
+ result = `CEIL(${tsExpr}::numeric / ${msPerUnit[unit]}) * ${msPerUnit[unit]}`;
1354
+ }
1355
+ else {
1356
+ // 月和年需要用日期函数,但最终转回时间戳
1357
+ const dateStr = `TO_TIMESTAMP(${tsExpr}::double precision / 1000)`;
1358
+ const unitMap = {
1359
+ 'M': 'month',
1360
+ 'y': 'year'
1361
+ };
1362
+ const pgUnit = unitMap[unit];
1363
+ if (!pgUnit) {
1364
+ throw new Error(`Unsupported date ceil unit: ${unit}`);
1365
+ }
1366
+ // 向上取整:如果截断后不等于原值,则加一个单位,最后转回毫秒时间戳
1367
+ result = `(EXTRACT(EPOCH FROM (CASE WHEN DATE_TRUNC('${pgUnit}', ${dateStr}) = ${dateStr} THEN ${dateStr} ELSE DATE_TRUNC('${pgUnit}', ${dateStr}) + INTERVAL '1 ${pgUnit}' END)) * 1000)::bigint`;
1368
+ }
1369
+ }
1370
+ else if (fnKey === '$dateFloor') {
1371
+ // $dateFloor: [date, unit]
1372
+ (0, assert_1.default)(fnArgs instanceof Array && fnArgs.length === 2);
1373
+ const [dateExpr, unit] = fnArgs;
1374
+ // 获取毫秒时间戳表达式
1375
+ const getTimestampExpr = (arg) => {
1376
+ if (arg instanceof Date) {
1377
+ return `${arg.valueOf()}`;
1378
+ }
1379
+ else if (typeof arg === 'number') {
1380
+ return `${arg}`;
1381
+ }
1382
+ else {
1383
+ // 属性引用,直接返回属性(存储本身就是毫秒时间戳)
1384
+ const k = Object.keys(arg);
1385
+ if (k.includes('#attr')) {
1386
+ return `"${alias}"."${arg['#attr']}"`;
1387
+ }
1388
+ else if (k.includes('#refId')) {
1389
+ const refId = arg['#refId'];
1390
+ const refAttr = arg['#refAttr'];
1391
+ return `"${refDict[refId][0]}"."${refAttr}"`;
1392
+ }
1393
+ // 其他表达式递归处理
1394
+ return translateInner(arg);
1395
+ }
1396
+ };
1397
+ const tsExpr = getTimestampExpr(dateExpr);
1398
+ // 固定间隔单位:直接用时间戳数学运算
1399
+ const msPerUnit = {
1400
+ 's': 1000,
1401
+ 'm': 60000,
1402
+ 'h': 3600000,
1403
+ 'd': 86400000,
1404
+ };
1405
+ if (msPerUnit[unit]) {
1406
+ // FLOOR 向下取整:FLOOR(timestamp / interval) * interval
1407
+ result = `FLOOR(${tsExpr}::numeric / ${msPerUnit[unit]}) * ${msPerUnit[unit]}`;
1408
+ }
1409
+ else {
1410
+ // 月和年需要用日期函数,但最终转回时间戳
1411
+ const dateStr = `TO_TIMESTAMP(${tsExpr}::double precision / 1000)`;
1412
+ const unitMap = {
1413
+ 'M': 'month',
1414
+ 'y': 'year'
1415
+ };
1416
+ const pgUnit = unitMap[unit];
1417
+ if (!pgUnit) {
1418
+ throw new Error(`Unsupported date floor unit: ${unit}`);
1419
+ }
1420
+ // DATE_TRUNC 向下取整,转回毫秒时间戳
1421
+ result = `(EXTRACT(EPOCH FROM DATE_TRUNC('${pgUnit}', ${dateStr})) * 1000)::bigint`;
1422
+ }
1423
+ }
1424
+ else if (fnArgs instanceof Array) {
1425
+ // 原有的数组参数处理逻辑
1426
+ const fnName = this.translateFnName(fnKey, fnArgs.length);
1427
+ const args = [fnName];
1428
+ args.push(...fnArgs.map((ele) => {
1429
+ if (['string', 'number'].includes(typeof ele) || ele instanceof Date) {
1430
+ return translateConstant(ele);
1431
+ }
1432
+ else {
1433
+ return translateInner(ele);
1434
+ }
1435
+ }));
1436
+ result = util_1.format.apply(null, args);
1437
+ }
1438
+ else {
1439
+ // 原有的单参数处理逻辑
1440
+ const fnName = this.translateFnName(fnKey, 1);
1441
+ const args = [fnName];
1442
+ if (['string', 'number'].includes(typeof fnArgs) || fnArgs instanceof Date) {
1443
+ args.push(translateConstant(fnArgs));
1444
+ }
1445
+ else {
1446
+ args.push(translateInner(fnArgs));
1447
+ }
1448
+ result = util_1.format.apply(null, args);
1449
+ }
1450
+ }
1451
+ return result;
1452
+ };
1453
+ return translateInner(expression);
1454
+ }
1455
+ populateSelectStmt(projectionText, fromText, aliasDict, filterText, sorterText, groupByText, indexFrom, count, option) {
1456
+ let sql = `SELECT ${projectionText} FROM ${fromText}`;
1457
+ if (filterText) {
1458
+ sql += ` WHERE ${filterText}`;
1459
+ }
1460
+ if (groupByText) {
1461
+ sql += ` GROUP BY ${groupByText}`;
1462
+ }
1463
+ if (sorterText) {
1464
+ sql += ` ORDER BY ${sorterText}`;
1465
+ }
1466
+ // PostgreSQL 语法: LIMIT count OFFSET offset
1467
+ if (typeof count === 'number') {
1468
+ sql += ` LIMIT ${count}`;
1469
+ }
1470
+ if (typeof indexFrom === 'number' && indexFrom > 0) {
1471
+ sql += ` OFFSET ${indexFrom}`;
1472
+ }
1473
+ // FOR UPDATE 锁定
1474
+ if (option?.forUpdate) {
1475
+ sql += ' FOR UPDATE';
1476
+ if (typeof option.forUpdate === 'string') {
1477
+ // PostgreSQL 支持: NOWAIT, SKIP LOCKED, OF table_name
1478
+ sql += ` ${option.forUpdate}`;
1479
+ }
1480
+ }
1481
+ return sql;
1482
+ }
1483
+ translateUpdate(entity, operation, option) {
1484
+ const { attributes } = this.schema[entity];
1485
+ const { filter, sorter, indexFrom, count, data } = operation;
1486
+ (0, assert_1.default)(!sorter, '当前update不支持sorter行为');
1487
+ // 使用结构化的JOIN分析
1488
+ const { aliasDict, filterRefAlias, mainTable, mainAlias, joinInfos, currentNumber } = this.analyzeJoinStructured(entity, { filter, sorter });
1489
+ // 构建SET子句 - PostgreSQL中SET子句不能使用表别名前缀
1490
+ const setClauses = [];
1491
+ for (const attr in data) {
1492
+ (0, assert_1.default)(attributes.hasOwnProperty(attr));
1493
+ const value = this.translateAttrValue(attributes[attr].type, data[attr]);
1494
+ setClauses.push(`"${attr}" = ${value}`);
1495
+ }
1496
+ const setClause = setClauses.join(', ');
1497
+ // 构建过滤条件
1498
+ const { stmt: filterText } = this.translateFilter(entity, filter, aliasDict, filterRefAlias, currentNumber, option);
1499
+ let sql;
1500
+ if (joinInfos.length === 0) {
1501
+ // 单表更新
1502
+ sql = `UPDATE "${mainTable}" AS "${mainAlias}" SET ${setClause}`;
1503
+ if (filterText) {
1504
+ sql += ` WHERE ${filterText}`;
1505
+ }
1506
+ }
1507
+ else {
1508
+ // 多表更新 - PostgreSQL语法: UPDATE main SET ... FROM other_tables WHERE join_conditions AND filter
1509
+ sql = `UPDATE "${mainTable}" AS "${mainAlias}" SET ${setClause}`;
1510
+ // FROM子句包含所有JOIN的表
1511
+ const fromTables = joinInfos.map(j => `"${j.table}" AS "${j.alias}"`).join(', ');
1512
+ sql += ` FROM ${fromTables}`;
1513
+ // WHERE子句包含JOIN条件和过滤条件
1514
+ const joinConditions = this.buildJoinConditions(joinInfos);
1515
+ let whereClause = joinConditions;
1516
+ if (filterText) {
1517
+ whereClause = whereClause ? `${whereClause} AND ${filterText}` : filterText;
1518
+ }
1519
+ if (whereClause) {
1520
+ sql += ` WHERE ${whereClause}`;
1521
+ }
1522
+ }
1523
+ return sql;
1524
+ }
1525
+ translateRemove(entity, operation, option) {
1526
+ 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
+ const { attributes } = this.schema[entity];
1533
+ if (option?.deletePhysically) {
1534
+ // 物理删除
1535
+ (0, assert_1.default)((0, lodash_1.difference)(Object.keys(data), [types_1.UpdateAtAttribute, types_1.DeleteAtAttribute]).length === 0);
1536
+ let sql;
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;
1561
+ }
1562
+ else {
1563
+ // 软删除 - 实际是UPDATE操作
1564
+ const setClauses = [];
1565
+ for (const attr in data) {
1566
+ (0, assert_1.default)([types_1.TriggerDataAttribute, types_1.TriggerUuidAttribute, types_1.DeleteAtAttribute, types_1.UpdateAtAttribute].includes(attr));
1567
+ const value = this.translateAttrValue(attributes[attr].type, data[attr]);
1568
+ setClauses.push(`"${attr}" = ${value}`);
1569
+ }
1570
+ const setClause = setClauses.join(', ');
1571
+ let sql;
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);
1727
+ }
1728
+ return conditions.join(' AND ');
1729
+ }
1730
+ /**
1731
+ * 生成 PostgreSQL UPSERT 语句
1732
+ * INSERT ... ON CONFLICT (key) DO UPDATE SET ...
1733
+ */
1734
+ translateUpsert(entity, data, conflictKeys, updateAttrs) {
1735
+ const { schema } = this;
1736
+ const { attributes, storageName = entity } = schema[entity];
1737
+ // 基础 INSERT 语句
1738
+ let sql = this.translateInsert(entity, data);
1739
+ // ON CONFLICT 子句
1740
+ const conflictColumns = conflictKeys.map(k => this.quoteIdentifier(k)).join(', ');
1741
+ sql += ` ON CONFLICT (${conflictColumns})`;
1742
+ // DO UPDATE SET 子句
1743
+ const dataFull = data.reduce((prev, cur) => Object.assign({}, cur, prev), {});
1744
+ const attrsToUpdate = updateAttrs || Object.keys(dataFull).filter(attr => attributes.hasOwnProperty(attr) && !conflictKeys.includes(attr) && attr !== 'id');
1745
+ if (attrsToUpdate.length > 0) {
1746
+ const updateParts = attrsToUpdate.map(attr => `${this.quoteIdentifier(attr)} = EXCLUDED.${this.quoteIdentifier(attr)}`);
1747
+ sql += ` DO UPDATE SET ${updateParts.join(', ')}`;
1748
+ }
1749
+ else {
1750
+ sql += ' DO NOTHING';
1751
+ }
1752
+ return sql;
1753
+ }
1754
+ populateUpdateStmt(updateText, fromText, aliasDict, filterText, sorterText, indexFrom, count, option) {
1755
+ // 这个方法不应该被直接调用了,因为translateUpdate已经重写
1756
+ throw new Error('populateUpdateStmt should not be called directly in PostgreSQL. Use translateUpdate instead.');
1757
+ }
1758
+ populateRemoveStmt(updateText, fromText, aliasDict, filterText, sorterText, indexFrom, count, option) {
1759
+ // 这个方法不应该被直接调用了,因为translateRemove已经重写
1760
+ throw new Error('populateRemoveStmt should not be called directly in PostgreSQL. Use translateRemove instead.');
1761
+ }
1762
+ /**
1763
+ * 将 PostgreSQL 返回的 Type 回译成 oak 的类型,是 populateDataTypeDef 的反函数
1764
+ * @param type PostgreSQL 类型字符串
1765
+ */
1766
+ reTranslateToAttribute(type) {
1767
+ // 处理带长度的类型:character varying(255), character(10)
1768
+ const varcharMatch = /^character varying\((\d+)\)$/.exec(type);
1769
+ if (varcharMatch) {
1770
+ return {
1771
+ type: 'varchar',
1772
+ params: {
1773
+ length: parseInt(varcharMatch[1], 10),
1774
+ }
1775
+ };
1776
+ }
1777
+ const charMatch = /^character\((\d+)\)$/.exec(type);
1778
+ if (charMatch) {
1779
+ return {
1780
+ type: 'char',
1781
+ params: {
1782
+ length: parseInt(charMatch[1], 10),
1783
+ }
1784
+ };
1785
+ }
1786
+ // 处理带精度和小数位的类型:numeric(10,2)
1787
+ const numericWithScaleMatch = /^numeric\((\d+),(\d+)\)$/.exec(type);
1788
+ if (numericWithScaleMatch) {
1789
+ return {
1790
+ type: 'decimal',
1791
+ params: {
1792
+ precision: parseInt(numericWithScaleMatch[1], 10),
1793
+ scale: parseInt(numericWithScaleMatch[2], 10),
1794
+ },
1795
+ };
1796
+ }
1797
+ // 处理只带精度的类型:numeric(10), timestamp(6)
1798
+ const numericMatch = /^numeric\((\d+)\)$/.exec(type);
1799
+ if (numericMatch) {
1800
+ return {
1801
+ type: 'decimal',
1802
+ params: {
1803
+ precision: parseInt(numericMatch[1], 10),
1804
+ scale: 0,
1805
+ },
1806
+ };
1807
+ }
1808
+ const timestampMatch = /^timestamp\((\d+)\) without time zone$/.exec(type);
1809
+ if (timestampMatch) {
1810
+ return {
1811
+ type: 'timestamp',
1812
+ params: {
1813
+ precision: parseInt(timestampMatch[1], 10),
1814
+ },
1815
+ };
1816
+ }
1817
+ const timeMatch = /^time\((\d+)\) without time zone$/.exec(type);
1818
+ if (timeMatch) {
1819
+ return {
1820
+ type: 'time',
1821
+ params: {
1822
+ precision: parseInt(timeMatch[1], 10),
1823
+ },
1824
+ };
1825
+ }
1826
+ // PostgreSQL 类型映射到 oak 类型
1827
+ const typeMap = {
1828
+ 'bigint': 'bigint',
1829
+ 'integer': 'integer',
1830
+ 'smallint': 'smallint',
1831
+ 'real': 'real',
1832
+ 'double precision': 'double precision',
1833
+ 'boolean': 'boolean',
1834
+ 'text': 'text',
1835
+ 'jsonb': 'object',
1836
+ 'json': 'object',
1837
+ 'bytea': 'bytea',
1838
+ 'character varying': 'varchar',
1839
+ 'character': 'char',
1840
+ 'timestamp without time zone': 'timestamp',
1841
+ 'time without time zone': 'time',
1842
+ 'date': 'date',
1843
+ 'uuid': 'uuid',
1844
+ 'geometry': 'geometry',
1845
+ 'numeric': 'decimal',
1846
+ };
1847
+ const mappedType = typeMap[type];
1848
+ if (mappedType) {
1849
+ return { type: mappedType };
1850
+ }
1851
+ // 如果是用户定义的枚举类型,返回 enum(具体值需要额外查询)
1852
+ // 这里先返回基础类型,枚举值在 readSchema 中单独处理
1853
+ return { type: type };
1854
+ }
1855
+ /**
1856
+ * 从 PostgreSQL 数据库读取当前的 schema 结构
1857
+ */
1858
+ async readSchema(execFn) {
1859
+ const result = {};
1860
+ // 1. 获取所有表
1861
+ const tablesSql = `
1862
+ SELECT tablename
1863
+ FROM pg_tables
1864
+ WHERE schemaname = 'public'
1865
+ ORDER BY tablename;
1866
+ `;
1867
+ const [tablesResult] = await execFn(tablesSql);
1868
+ for (const tableRow of tablesResult) {
1869
+ const tableName = tableRow.tablename;
1870
+ // 2. 获取表的列信息
1871
+ const columnsSql = `
1872
+ SELECT
1873
+ column_name,
1874
+ data_type,
1875
+ character_maximum_length,
1876
+ numeric_precision,
1877
+ numeric_scale,
1878
+ is_nullable,
1879
+ column_default,
1880
+ udt_name
1881
+ FROM information_schema.columns
1882
+ WHERE table_schema = 'public'
1883
+ AND table_name = '${tableName}'
1884
+ ORDER BY ordinal_position;
1885
+ `;
1886
+ const [columnsResult] = await execFn(columnsSql);
1887
+ const attributes = {};
1888
+ for (const col of columnsResult) {
1889
+ const { column_name: colName, data_type: dataType, character_maximum_length: maxLength, numeric_precision: precision, numeric_scale: scale, is_nullable: isNullable, column_default: defaultValue, udt_name: udtName, } = col;
1890
+ let attr;
1891
+ // 处理用户定义类型(枚举)
1892
+ if (dataType === 'USER-DEFINED') {
1893
+ const enumSql = `
1894
+ SELECT e.enumlabel
1895
+ FROM pg_type t
1896
+ JOIN pg_enum e ON t.oid = e.enumtypid
1897
+ WHERE t.typname = '${udtName}'
1898
+ ORDER BY e.enumsortorder;
1899
+ `;
1900
+ const [enumResult] = await execFn(enumSql);
1901
+ const enumeration = enumResult.map((r) => r.enumlabel);
1902
+ attr = {
1903
+ type: 'enum',
1904
+ enumeration,
1905
+ };
1906
+ }
1907
+ else {
1908
+ // 构建完整的类型字符串
1909
+ let fullType = dataType;
1910
+ const integerTypes = ['bigint', 'integer', 'smallint', 'serial', 'bigserial', 'smallserial'];
1911
+ if (maxLength && !integerTypes.includes(dataType)) {
1912
+ fullType = `${dataType}(${maxLength})`;
1913
+ }
1914
+ else if (precision !== null && scale !== null && !integerTypes.includes(dataType)) {
1915
+ fullType = `${dataType}(${precision},${scale})`;
1916
+ }
1917
+ else if (precision !== null && !integerTypes.includes(dataType)) {
1918
+ fullType = `${dataType}(${precision})`;
1919
+ }
1920
+ attr = this.reTranslateToAttribute(fullType);
1921
+ }
1922
+ // ========== 类型还原逻辑 ==========
1923
+ // 框架将某些语义类型存储为 bigint,需要根据列名还原
1924
+ if (attr.type === 'bigint') {
1925
+ // 1. 检查是否是序列列
1926
+ if (colName === '$$seq$$' || (defaultValue && defaultValue.includes('nextval'))) {
1927
+ attr.type = 'sequence';
1928
+ attr.sequenceStart = 10000; // 默认起始值
1929
+ }
1930
+ // 2. 检查是否是时间戳列
1931
+ else if (['$$createAt$$', '$$updateAt$$', '$$deleteAt$$'].includes(colName)) {
1932
+ attr.type = 'datetime';
1933
+ }
1934
+ // 3. 检查其他可能的时间类型列(根据命名约定)
1935
+ else if (colName.endsWith('At') || colName.endsWith('Time')) {
1936
+ // 可选:根据业务约定判断是否应该是 datetime
1937
+ // 这里保守处理,只转换框架标准字段
1938
+ }
1939
+ }
1940
+ // 处理约束 - 只在为 true 时添加
1941
+ if (isNullable === 'NO') {
1942
+ attr.notNull = true;
1943
+ }
1944
+ // 处理默认值
1945
+ if (defaultValue && !defaultValue.includes('nextval')) {
1946
+ let cleanDefault = defaultValue.replace(/::[a-z]+/gi, '').replace(/'/g, '');
1947
+ if (cleanDefault === 'true') {
1948
+ attr.default = true;
1949
+ }
1950
+ else if (cleanDefault === 'false') {
1951
+ attr.default = false;
1952
+ }
1953
+ else if (!isNaN(Number(cleanDefault))) {
1954
+ attr.default = Number(cleanDefault);
1955
+ }
1956
+ else if (cleanDefault !== '') {
1957
+ attr.default = cleanDefault;
1958
+ }
1959
+ }
1960
+ // 检查唯一约束
1961
+ const uniqueSql = `
1962
+ SELECT COUNT(*) as cnt
1963
+ FROM pg_index ix
1964
+ JOIN pg_class t ON t.oid = ix.indrelid
1965
+ JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(ix.indkey)
1966
+ WHERE t.relname = '${tableName}'
1967
+ AND a.attname = '${colName}'
1968
+ AND ix.indisunique = true
1969
+ AND NOT ix.indisprimary
1970
+ AND array_length(ix.indkey, 1) = 1;
1971
+ `;
1972
+ const [uniqueResult] = await execFn(uniqueSql);
1973
+ const uniqueCount = parseInt(uniqueResult[0]?.cnt || '0', 10);
1974
+ if (uniqueCount > 0) {
1975
+ attr.unique = true;
1976
+ }
1977
+ attributes[colName] = attr;
1978
+ }
1979
+ // 3. 获取索引信息
1980
+ const indexesSql = `
1981
+ SELECT
1982
+ i.relname as index_name,
1983
+ ix.indisunique as is_unique,
1984
+ am.amname as index_type,
1985
+ pg_get_indexdef(ix.indexrelid) as index_def
1986
+ FROM pg_class t
1987
+ JOIN pg_index ix ON t.oid = ix.indrelid
1988
+ JOIN pg_class i ON i.oid = ix.indexrelid
1989
+ JOIN pg_am am ON i.relam = am.oid
1990
+ WHERE t.relname = '${tableName}'
1991
+ AND t.relkind = 'r'
1992
+ AND i.relname NOT LIKE '%_pkey'
1993
+ AND NOT ix.indisprimary
1994
+ ORDER BY i.relname;
1995
+ `;
1996
+ const [indexesResult] = await execFn(indexesSql);
1997
+ if (indexesResult.length > 0) {
1998
+ const indexes = [];
1999
+ for (const row of indexesResult) {
2000
+ const { index_name: indexName, is_unique: isUnique, index_type: indexType, index_def: indexDef } = row;
2001
+ // 解析索引定义以获取列名和配置
2002
+ const index = {
2003
+ name: indexName,
2004
+ attributes: [],
2005
+ };
2006
+ // 解析索引定义字符串
2007
+ // 示例: CREATE INDEX "user_index_fulltext_chinese" ON public."user" USING gin (to_tsvector('chinese'::regconfig, (COALESCE(name, ''::text) || ' '::text) || COALESCE(nickname, ''::text)))
2008
+ if (indexType === 'gin' && indexDef.includes('to_tsvector')) {
2009
+ // 全文索引
2010
+ index.config = { type: 'fulltext' };
2011
+ // 提取 tsConfig
2012
+ const tsConfigMatch = indexDef.match(/to_tsvector\('([^']+)'/);
2013
+ if (tsConfigMatch) {
2014
+ const tsConfig = tsConfigMatch[1];
2015
+ index.config.tsConfig = tsConfig;
2016
+ }
2017
+ // 提取列名(从 COALESCE 中)
2018
+ const columnMatches = indexDef.matchAll(/COALESCE\("?([^",\s]+)"?/g);
2019
+ const columns = Array.from(columnMatches, m => m[1]);
2020
+ index.attributes = columns.map(col => ({ name: col }));
2021
+ // 处理多语言索引的情况:移除语言后缀
2022
+ // 例如: user_index_fulltext_chinese -> index_fulltext
2023
+ const nameParts = indexName.split('_');
2024
+ if (nameParts.length > 2) {
2025
+ const possibleLang = nameParts[nameParts.length - 1];
2026
+ // 如果最后一部分是语言代码,移除它
2027
+ if (['chinese', 'english', 'simple', 'german', 'french', 'spanish', 'russian', 'japanese'].includes(possibleLang)) {
2028
+ index.name = nameParts.slice(0, -1).join('_');
2029
+ }
2030
+ }
2031
+ }
2032
+ else if (indexType === 'gist') {
2033
+ // 空间索引
2034
+ index.config = { type: 'spatial' };
2035
+ // 提取列名
2036
+ const columnMatch = indexDef.match(/\(([^)]+)\)/);
2037
+ if (columnMatch) {
2038
+ const columns = columnMatch[1].split(',').map(c => c.trim().replace(/"/g, ''));
2039
+ index.attributes = columns.map(col => ({ name: col }));
2040
+ }
2041
+ }
2042
+ else if (indexType === 'hash') {
2043
+ // 哈希索引
2044
+ index.config = { type: 'hash' };
2045
+ // 提取列名
2046
+ const columnMatch = indexDef.match(/\(([^)]+)\)/);
2047
+ if (columnMatch) {
2048
+ const columns = columnMatch[1].split(',').map(c => c.trim().replace(/"/g, ''));
2049
+ index.attributes = columns.map(col => ({ name: col }));
2050
+ }
2051
+ }
2052
+ else {
2053
+ // B-tree 索引(默认)
2054
+ // 提取列名和排序方向
2055
+ const columnMatch = indexDef.match(/\(([^)]+)\)/);
2056
+ if (columnMatch) {
2057
+ const columnDefs = columnMatch[1].split(',');
2058
+ index.attributes = columnDefs.map(colDef => {
2059
+ const trimmed = colDef.trim().replace(/"/g, '');
2060
+ const parts = trimmed.split(/\s+/);
2061
+ const attr = { name: parts[0] };
2062
+ // 检查排序方向
2063
+ if (parts.includes('DESC')) {
2064
+ attr.direction = 'DESC';
2065
+ }
2066
+ else if (parts.includes('ASC')) {
2067
+ attr.direction = 'ASC';
2068
+ }
2069
+ return attr;
2070
+ });
2071
+ }
2072
+ // 如果是唯一索引
2073
+ if (isUnique) {
2074
+ index.config = { unique: true };
2075
+ }
2076
+ }
2077
+ // 移除表名前缀(如果存在)
2078
+ // 例如: user_index_fulltext -> index_fulltext
2079
+ if (index.name.startsWith(`${tableName}_`)) {
2080
+ index.name = index.name.substring(tableName.length + 1);
2081
+ }
2082
+ indexes.push(index);
2083
+ }
2084
+ Object.assign(result, {
2085
+ [tableName]: {
2086
+ attributes,
2087
+ indexes,
2088
+ }
2089
+ });
2090
+ }
2091
+ else {
2092
+ Object.assign(result, {
2093
+ [tableName]: {
2094
+ attributes,
2095
+ }
2096
+ });
2097
+ }
2098
+ }
2099
+ return result;
2100
+ }
2101
+ /**
2102
+ * 将属性定义转换为 PostgreSQL DDL 语句
2103
+ * @param attr 属性名
2104
+ * @param attrDef 属性定义
2105
+ */
2106
+ translateAttributeDef(attr, attrDef) {
2107
+ let sql = `"${attr}" `;
2108
+ const { type, params, default: defaultValue, unique, notNull, sequenceStart, enumeration, } = attrDef;
2109
+ // 处理序列类型(IDENTITY)
2110
+ if (type === 'sequence' || (typeof sequenceStart === 'number')) {
2111
+ sql += `bigint GENERATED BY DEFAULT AS IDENTITY (START WITH ${sequenceStart || 10000}) UNIQUE`;
2112
+ return sql;
2113
+ }
2114
+ // 处理枚举类型
2115
+ if (type === 'enum') {
2116
+ (0, assert_1.default)(enumeration, 'Enum type requires enumeration values');
2117
+ sql += `enum(${enumeration.map(v => `'${v}'`).join(',')})`;
2118
+ }
2119
+ else {
2120
+ sql += this.populateDataTypeDef(type, params, enumeration);
2121
+ }
2122
+ // NOT NULL 约束
2123
+ if (notNull || type === 'geometry') {
2124
+ sql += ' NOT NULL';
2125
+ }
2126
+ // UNIQUE 约束
2127
+ if (unique) {
2128
+ sql += ' UNIQUE';
2129
+ }
2130
+ // 默认值
2131
+ if (defaultValue !== undefined && !sequenceStart) {
2132
+ (0, assert_1.default)(type !== 'ref', 'ref type should not have default value');
2133
+ sql += ` DEFAULT ${this.translateAttrValue(type, defaultValue)}`;
2134
+ }
2135
+ // 主键
2136
+ if (attr === 'id') {
2137
+ sql += ' PRIMARY KEY';
2138
+ }
2139
+ return sql;
2140
+ }
2141
+ /**
2142
+ * 比较两个 SQL 语句是否等价(用于 schema diff)
2143
+ * 忽略空格、大小写等格式差异
2144
+ */
2145
+ compareSql(sql1, sql2) {
2146
+ // 标准化 SQL:移除多余空格,统一大小写
2147
+ const normalize = (sql) => {
2148
+ return sql
2149
+ .replace(/\s+/g, ' ') // 多个空格合并为一个
2150
+ .replace(/\(\s+/g, '(') // 移除括号后的空格
2151
+ .replace(/\s+\)/g, ')') // 移除括号前的空格
2152
+ .replace(/,\s+/g, ',') // 移除逗号后的空格
2153
+ .trim()
2154
+ .toLowerCase();
2155
+ };
2156
+ return normalize(sql1) === normalize(sql2);
2157
+ }
2158
+ }
2159
+ exports.PostgreSQLTranslator = PostgreSQLTranslator;