oak-db 3.3.13 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/lib/MySQL/connector.d.ts +9 -1
- package/lib/MySQL/connector.js +33 -14
- package/lib/MySQL/migration.d.ts +10 -0
- package/lib/MySQL/migration.js +649 -0
- package/lib/MySQL/store.d.ts +19 -2
- package/lib/MySQL/store.js +159 -110
- package/lib/MySQL/translator.d.ts +5 -1
- package/lib/MySQL/translator.js +47 -14
- package/lib/PostgreSQL/connector.d.ts +10 -0
- package/lib/PostgreSQL/connector.js +58 -51
- package/lib/PostgreSQL/migration.d.ts +10 -0
- package/lib/PostgreSQL/migration.js +984 -0
- package/lib/PostgreSQL/prepare.d.ts +2 -0
- package/lib/PostgreSQL/prepare.js +69 -0
- package/lib/PostgreSQL/store.d.ts +16 -2
- package/lib/PostgreSQL/store.js +196 -163
- package/lib/PostgreSQL/translator.d.ts +28 -8
- package/lib/PostgreSQL/translator.js +208 -226
- package/lib/index.d.ts +1 -0
- package/lib/migration.d.ts +27 -0
- package/lib/migration.js +1029 -0
- package/lib/sqlTranslator.d.ts +5 -1
- package/lib/sqlTranslator.js +12 -4
- package/lib/types/dbStore.d.ts +8 -15
- package/lib/types/migration.d.ts +251 -0
- package/lib/types/migration.js +2 -0
- package/lib/utils/indexInspection.d.ts +4 -0
- package/lib/utils/indexInspection.js +32 -0
- package/lib/utils/indexName.d.ts +15 -0
- package/lib/utils/indexName.js +76 -0
- package/lib/utils/inspection.d.ts +13 -0
- package/lib/utils/inspection.js +56 -0
- package/package.json +5 -2
|
@@ -3,11 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.PostgreSQLTranslator = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const assert_1 = tslib_1.__importDefault(require("assert"));
|
|
6
|
+
const crypto_1 = require("crypto");
|
|
6
7
|
const util_1 = require("util");
|
|
7
8
|
const lodash_1 = require("lodash");
|
|
8
9
|
const types_1 = require("oak-domain/lib/types");
|
|
9
10
|
const sqlTranslator_1 = require("../sqlTranslator");
|
|
10
|
-
const
|
|
11
|
+
const indexName_1 = require("../utils/indexName");
|
|
11
12
|
const GeoTypes = [
|
|
12
13
|
{
|
|
13
14
|
type: 'point',
|
|
@@ -97,10 +98,103 @@ function transformGeoData(data) {
|
|
|
97
98
|
}
|
|
98
99
|
}
|
|
99
100
|
class PostgreSQLTranslator extends sqlTranslator_1.SqlTranslator {
|
|
101
|
+
maxIndexNameLength = 63;
|
|
102
|
+
physicalIndexNameCache;
|
|
103
|
+
makeIndexNameCacheKey(entityName, tableName, logicalName, suffix = '') {
|
|
104
|
+
return [entityName, tableName, logicalName, suffix].join('\u0000');
|
|
105
|
+
}
|
|
106
|
+
getIndexSuffixes(index) {
|
|
107
|
+
if (index.config?.type !== 'fulltext' || !Array.isArray(index.config.tsConfig)) {
|
|
108
|
+
return [''];
|
|
109
|
+
}
|
|
110
|
+
if (index.config.tsConfig.length <= 1) {
|
|
111
|
+
return [''];
|
|
112
|
+
}
|
|
113
|
+
return index.config.tsConfig.map((lang) => `_${lang}`);
|
|
114
|
+
}
|
|
115
|
+
ensurePhysicalIndexNameCache() {
|
|
116
|
+
if (this.physicalIndexNameCache) {
|
|
117
|
+
return this.physicalIndexNameCache;
|
|
118
|
+
}
|
|
119
|
+
const entries = [];
|
|
120
|
+
for (const entityName in this.schema) {
|
|
121
|
+
const tableName = this.schema[entityName].storageName || entityName;
|
|
122
|
+
const indexes = this.schema[entityName].indexes || [];
|
|
123
|
+
for (const index of indexes) {
|
|
124
|
+
for (const suffix of this.getIndexSuffixes(index)) {
|
|
125
|
+
const key = this.makeIndexNameCacheKey(entityName, tableName, index.name, suffix);
|
|
126
|
+
entries.push({
|
|
127
|
+
key,
|
|
128
|
+
baseName: (0, indexName_1.buildCompactPhysicalIndexName)({
|
|
129
|
+
entityName,
|
|
130
|
+
tableName,
|
|
131
|
+
logicalName: index.name,
|
|
132
|
+
suffix,
|
|
133
|
+
maxLength: this.maxIndexNameLength,
|
|
134
|
+
}),
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const grouped = new Map();
|
|
140
|
+
entries.forEach(({ key, baseName }) => {
|
|
141
|
+
const bucket = grouped.get(baseName);
|
|
142
|
+
if (bucket) {
|
|
143
|
+
bucket.push(key);
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
grouped.set(baseName, [key]);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
const cache = new Map();
|
|
150
|
+
grouped.forEach((keys, baseName) => {
|
|
151
|
+
if (keys.length === 1) {
|
|
152
|
+
cache.set(keys[0], baseName);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
keys.sort();
|
|
156
|
+
keys.forEach((key) => {
|
|
157
|
+
const hash = (0, crypto_1.createHash)('sha1')
|
|
158
|
+
.update(key)
|
|
159
|
+
.digest('hex')
|
|
160
|
+
.slice(0, 8);
|
|
161
|
+
const prefixLength = Math.max(1, this.maxIndexNameLength - hash.length - 1);
|
|
162
|
+
cache.set(key, `${baseName.slice(0, prefixLength)}_${hash}`);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
this.physicalIndexNameCache = cache;
|
|
166
|
+
return cache;
|
|
167
|
+
}
|
|
100
168
|
// 生成 enum 类型名称
|
|
101
169
|
getEnumTypeName(entity, attr) {
|
|
102
170
|
return `${entity}_${attr}_enum`.toLowerCase();
|
|
103
171
|
}
|
|
172
|
+
getPhysicalIndexName(entityName, tableName, logicalName, suffix = '') {
|
|
173
|
+
const key = this.makeIndexNameCacheKey(entityName, tableName, logicalName, suffix);
|
|
174
|
+
return this.ensurePhysicalIndexNameCache().get(key)
|
|
175
|
+
|| (0, indexName_1.buildCompactPhysicalIndexName)({
|
|
176
|
+
entityName,
|
|
177
|
+
tableName,
|
|
178
|
+
logicalName,
|
|
179
|
+
suffix,
|
|
180
|
+
maxLength: this.maxIndexNameLength,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
getLegacyPhysicalIndexNames(entityName, tableName, logicalName, suffix = '') {
|
|
184
|
+
const names = (0, indexName_1.buildLegacyPhysicalIndexNames)({
|
|
185
|
+
entityName,
|
|
186
|
+
tableName,
|
|
187
|
+
logicalName,
|
|
188
|
+
suffix,
|
|
189
|
+
maxLength: this.maxIndexNameLength,
|
|
190
|
+
serverTruncatesWhenOverflow: true,
|
|
191
|
+
});
|
|
192
|
+
const currentName = this.getPhysicalIndexName(entityName, tableName, logicalName, suffix);
|
|
193
|
+
if (!names.includes(currentName)) {
|
|
194
|
+
names.push(currentName);
|
|
195
|
+
}
|
|
196
|
+
return Array.from(new Set(names));
|
|
197
|
+
}
|
|
104
198
|
/**
|
|
105
199
|
* 将 MySQL 风格的 JSON 路径转换为 PostgreSQL 路径数组格式
|
|
106
200
|
* 例如: ".foo.bar[0].baz" -> '{foo,bar,0,baz}'
|
|
@@ -955,7 +1049,7 @@ class PostgreSQLTranslator extends sqlTranslator_1.SqlTranslator {
|
|
|
955
1049
|
indexSql += 'IF NOT EXISTS ';
|
|
956
1050
|
}
|
|
957
1051
|
// 索引名称(多语言时添加后缀)
|
|
958
|
-
indexSql += `"${String(entity)
|
|
1052
|
+
indexSql += `"${this.getPhysicalIndexName(String(entity), tableName, name, suffix)}" ON "${tableName}" `;
|
|
959
1053
|
// 索引方法
|
|
960
1054
|
if (indexType === 'hash') {
|
|
961
1055
|
indexSql += 'USING HASH ';
|
|
@@ -1470,23 +1564,16 @@ class PostgreSQLTranslator extends sqlTranslator_1.SqlTranslator {
|
|
|
1470
1564
|
if (typeof indexFrom === 'number' && indexFrom > 0) {
|
|
1471
1565
|
sql += ` OFFSET ${indexFrom}`;
|
|
1472
1566
|
}
|
|
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
1567
|
return sql;
|
|
1482
1568
|
}
|
|
1483
1569
|
translateUpdate(entity, operation, option) {
|
|
1484
1570
|
const { attributes } = this.schema[entity];
|
|
1485
1571
|
const { filter, sorter, indexFrom, count, data } = operation;
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
const
|
|
1489
|
-
|
|
1572
|
+
// 参数验证
|
|
1573
|
+
this.validateOperationParams(sorter, indexFrom, count);
|
|
1574
|
+
const mainTable = this.getStorageName(entity);
|
|
1575
|
+
const mainAlias = `${entity}_1`;
|
|
1576
|
+
// 构建 SET 子句
|
|
1490
1577
|
const setClauses = [];
|
|
1491
1578
|
for (const attr in data) {
|
|
1492
1579
|
(0, assert_1.default)(attributes.hasOwnProperty(attr));
|
|
@@ -1494,73 +1581,93 @@ class PostgreSQLTranslator extends sqlTranslator_1.SqlTranslator {
|
|
|
1494
1581
|
setClauses.push(`"${attr}" = ${value}`);
|
|
1495
1582
|
}
|
|
1496
1583
|
const setClause = setClauses.join(', ');
|
|
1497
|
-
//
|
|
1498
|
-
const
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1584
|
+
// 统一使用子查询方案
|
|
1585
|
+
const subqueryOperation = {
|
|
1586
|
+
data: { [types_1.PrimaryKeyAttribute]: 1 },
|
|
1587
|
+
filter: filter,
|
|
1588
|
+
indexFrom: indexFrom,
|
|
1589
|
+
count: count
|
|
1590
|
+
};
|
|
1591
|
+
const subquery = this.translateSelect(entity, subqueryOperation, { includedDeleted: option?.includedDeleted });
|
|
1592
|
+
let sql = `UPDATE "${mainTable}" AS "${mainAlias}" SET ${setClause} WHERE "${types_1.PrimaryKeyAttribute}" IN (${subquery})`;
|
|
1593
|
+
// 添加 RETURNING 子句
|
|
1594
|
+
return this.appendReturningClause(sql, entity, mainAlias, option);
|
|
1595
|
+
}
|
|
1596
|
+
/**
|
|
1597
|
+
* 将 projection 转换为 RETURNING 子句的列列表
|
|
1598
|
+
* @param entity 实体名
|
|
1599
|
+
* @param alias 表别名
|
|
1600
|
+
* @param projection 投影定义
|
|
1601
|
+
*/
|
|
1602
|
+
buildReturningClause(entity, alias, projection) {
|
|
1603
|
+
const columns = [];
|
|
1604
|
+
const { attributes } = this.schema[entity];
|
|
1605
|
+
for (const attr in projection) {
|
|
1606
|
+
// if (!attributes[attr]) {
|
|
1607
|
+
// // 如果存在entity这个attr
|
|
1608
|
+
// const entityDesc = attributes['entity']
|
|
1609
|
+
// const entityIdDesc = attributes['entityId']
|
|
1610
|
+
// if (entityDesc && entityIdDesc && ((entityDesc.ref as string[])?.includes(attr))) {
|
|
1611
|
+
// // 特殊处理 entity 和 entityId 属性
|
|
1612
|
+
// }
|
|
1613
|
+
// continue;
|
|
1614
|
+
// }
|
|
1615
|
+
// 只能返回更新列的相关字段,所以直接assert
|
|
1616
|
+
(0, assert_1.default)(attributes.hasOwnProperty(attr), `RETURNING语法只能返回更新列的字段,但在实体 ${String(entity)} 中未找到原生属性「${attr}」的定义`);
|
|
1617
|
+
const dataType = attributes[attr].type;
|
|
1618
|
+
// 处理特殊类型的投影
|
|
1619
|
+
const columnExpr = this.translateAttrProjection(dataType, alias, attr);
|
|
1620
|
+
// RETURNING 需要明确的别名(不带表前缀)
|
|
1621
|
+
columns.push(`${columnExpr} AS "${attr}"`);
|
|
1622
|
+
}
|
|
1623
|
+
if (columns.length === 0) {
|
|
1624
|
+
throw new Error(`No valid columns in RETURNING clause for entity ${String(entity)}`);
|
|
1625
|
+
}
|
|
1626
|
+
return columns.join(', ');
|
|
1627
|
+
}
|
|
1628
|
+
/**
|
|
1629
|
+
* 验证操作参数的合法性
|
|
1630
|
+
*/
|
|
1631
|
+
validateOperationParams(sorter, indexFrom, count) {
|
|
1632
|
+
(0, assert_1.default)(!sorter, '当前update/remove不支持sorter行为');
|
|
1633
|
+
// 对于limit,如果有indexFrom则一定有count
|
|
1634
|
+
if (typeof indexFrom === 'number') {
|
|
1635
|
+
(0, assert_1.default)(typeof count === 'number' && count > 0);
|
|
1506
1636
|
}
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
let whereClause = joinConditions;
|
|
1516
|
-
if (filterText) {
|
|
1517
|
-
whereClause = whereClause ? `${whereClause} AND ${filterText}` : filterText;
|
|
1518
|
-
}
|
|
1519
|
-
if (whereClause) {
|
|
1520
|
-
sql += ` WHERE ${whereClause}`;
|
|
1521
|
-
}
|
|
1637
|
+
}
|
|
1638
|
+
/**
|
|
1639
|
+
* 添加RETURNING子句
|
|
1640
|
+
*/
|
|
1641
|
+
appendReturningClause(sql, entity, mainAlias, option) {
|
|
1642
|
+
if (option?.returning) {
|
|
1643
|
+
const returningClause = this.buildReturningClause(entity, mainAlias, option.returning);
|
|
1644
|
+
return `${sql} RETURNING ${returningClause}`;
|
|
1522
1645
|
}
|
|
1523
1646
|
return sql;
|
|
1524
1647
|
}
|
|
1525
1648
|
translateRemove(entity, operation, option) {
|
|
1526
1649
|
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
1650
|
const { attributes } = this.schema[entity];
|
|
1651
|
+
// 参数验证
|
|
1652
|
+
this.validateOperationParams(sorter, indexFrom, count);
|
|
1653
|
+
const mainTable = this.getStorageName(entity);
|
|
1654
|
+
const mainAlias = `${entity}_1`;
|
|
1655
|
+
// 构建子查询
|
|
1656
|
+
const subqueryOperation = {
|
|
1657
|
+
data: { [types_1.PrimaryKeyAttribute]: 1 },
|
|
1658
|
+
filter: filter,
|
|
1659
|
+
indexFrom: indexFrom,
|
|
1660
|
+
count: count
|
|
1661
|
+
};
|
|
1662
|
+
const subquery = this.translateSelect(entity, subqueryOperation, { includedDeleted: option?.includedDeleted });
|
|
1663
|
+
let sql;
|
|
1533
1664
|
if (option?.deletePhysically) {
|
|
1534
1665
|
// 物理删除
|
|
1535
1666
|
(0, assert_1.default)((0, lodash_1.difference)(Object.keys(data), [types_1.UpdateAtAttribute, types_1.DeleteAtAttribute]).length === 0);
|
|
1536
|
-
|
|
1537
|
-
if (joinInfos.length === 0) {
|
|
1538
|
-
// 单表删除
|
|
1539
|
-
sql = `DELETE FROM "${mainTable}" AS "${mainAlias}"`;
|
|
1540
|
-
if (filterText) {
|
|
1541
|
-
sql += ` WHERE ${filterText}`;
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
else {
|
|
1545
|
-
// 多表删除 - PostgreSQL语法: DELETE FROM main USING other_tables WHERE join_conditions AND filter
|
|
1546
|
-
sql = `DELETE FROM "${mainTable}" AS "${mainAlias}"`;
|
|
1547
|
-
// USING子句包含所有JOIN的表
|
|
1548
|
-
const usingTables = joinInfos.map(j => `"${j.table}" AS "${j.alias}"`).join(', ');
|
|
1549
|
-
sql += ` USING ${usingTables}`;
|
|
1550
|
-
// WHERE子句包含JOIN条件和过滤条件
|
|
1551
|
-
const joinConditions = this.buildJoinConditions(joinInfos);
|
|
1552
|
-
let whereClause = joinConditions;
|
|
1553
|
-
if (filterText) {
|
|
1554
|
-
whereClause = whereClause ? `${whereClause} AND ${filterText}` : filterText;
|
|
1555
|
-
}
|
|
1556
|
-
if (whereClause) {
|
|
1557
|
-
sql += ` WHERE ${whereClause}`;
|
|
1558
|
-
}
|
|
1559
|
-
}
|
|
1560
|
-
return sql;
|
|
1667
|
+
sql = `DELETE FROM "${mainTable}" AS "${mainAlias}" WHERE "${types_1.PrimaryKeyAttribute}" IN (${subquery})`;
|
|
1561
1668
|
}
|
|
1562
1669
|
else {
|
|
1563
|
-
//
|
|
1670
|
+
// 软删除- 实际是 UPDATE
|
|
1564
1671
|
const setClauses = [];
|
|
1565
1672
|
for (const attr in data) {
|
|
1566
1673
|
(0, assert_1.default)([types_1.TriggerDataAttribute, types_1.TriggerUuidAttribute, types_1.DeleteAtAttribute, types_1.UpdateAtAttribute].includes(attr));
|
|
@@ -1568,164 +1675,10 @@ class PostgreSQLTranslator extends sqlTranslator_1.SqlTranslator {
|
|
|
1568
1675
|
setClauses.push(`"${attr}" = ${value}`);
|
|
1569
1676
|
}
|
|
1570
1677
|
const setClause = setClauses.join(', ');
|
|
1571
|
-
|
|
1572
|
-
if (joinInfos.length === 0) {
|
|
1573
|
-
// 单表更新
|
|
1574
|
-
sql = `UPDATE "${mainTable}" AS "${mainAlias}" SET ${setClause}`;
|
|
1575
|
-
if (filterText) {
|
|
1576
|
-
sql += ` WHERE ${filterText}`;
|
|
1577
|
-
}
|
|
1578
|
-
}
|
|
1579
|
-
else {
|
|
1580
|
-
// 多表更新
|
|
1581
|
-
sql = `UPDATE "${mainTable}" AS "${mainAlias}" SET ${setClause}`;
|
|
1582
|
-
const fromTables = joinInfos.map(j => `"${j.table}" AS "${j.alias}"`).join(', ');
|
|
1583
|
-
sql += ` FROM ${fromTables}`;
|
|
1584
|
-
const joinConditions = this.buildJoinConditions(joinInfos);
|
|
1585
|
-
let whereClause = joinConditions;
|
|
1586
|
-
if (filterText) {
|
|
1587
|
-
whereClause = whereClause ? `${whereClause} AND ${filterText}` : filterText;
|
|
1588
|
-
}
|
|
1589
|
-
if (whereClause) {
|
|
1590
|
-
sql += ` WHERE ${whereClause}`;
|
|
1591
|
-
}
|
|
1592
|
-
}
|
|
1593
|
-
return sql;
|
|
1594
|
-
}
|
|
1595
|
-
}
|
|
1596
|
-
/**
|
|
1597
|
-
* PostgreSQL专用的结构化JOIN分析
|
|
1598
|
-
* 返回结构化的JOIN信息,而不是拼接好的FROM字符串
|
|
1599
|
-
*/
|
|
1600
|
-
analyzeJoinStructured(entity, { filter, sorter }, initialNumber) {
|
|
1601
|
-
const { schema } = this;
|
|
1602
|
-
let number = initialNumber || 1;
|
|
1603
|
-
const filterRefAlias = {};
|
|
1604
|
-
const mainAlias = `${entity}_${number++}`;
|
|
1605
|
-
const mainTable = this.getStorageName(entity);
|
|
1606
|
-
const aliasDict = {
|
|
1607
|
-
'./': mainAlias,
|
|
1608
|
-
};
|
|
1609
|
-
const joinInfos = [];
|
|
1610
|
-
const analyzeFilterNode = ({ node, path, entityName, alias }) => {
|
|
1611
|
-
Object.keys(node).forEach((op) => {
|
|
1612
|
-
if (['$and', '$or'].includes(op)) {
|
|
1613
|
-
node[op].forEach((subNode) => analyzeFilterNode({
|
|
1614
|
-
node: subNode,
|
|
1615
|
-
path,
|
|
1616
|
-
entityName,
|
|
1617
|
-
alias,
|
|
1618
|
-
}));
|
|
1619
|
-
}
|
|
1620
|
-
else if (['$not'].includes(op)) {
|
|
1621
|
-
analyzeFilterNode({
|
|
1622
|
-
node: node[op],
|
|
1623
|
-
path,
|
|
1624
|
-
entityName,
|
|
1625
|
-
alias,
|
|
1626
|
-
});
|
|
1627
|
-
}
|
|
1628
|
-
else if (['$text'].includes(op)) {
|
|
1629
|
-
// 全文搜索,不需要JOIN
|
|
1630
|
-
}
|
|
1631
|
-
else {
|
|
1632
|
-
const rel = (0, relation_1.judgeRelation)(this.schema, entityName, op);
|
|
1633
|
-
if (typeof rel === 'string') {
|
|
1634
|
-
const pathAttr = `${path}${op}/`;
|
|
1635
|
-
if (!aliasDict.hasOwnProperty(pathAttr)) {
|
|
1636
|
-
const alias2 = `${rel}_${number++}`;
|
|
1637
|
-
aliasDict[pathAttr] = alias2;
|
|
1638
|
-
joinInfos.push({
|
|
1639
|
-
table: this.getStorageName(rel),
|
|
1640
|
-
alias: alias2,
|
|
1641
|
-
leftAlias: alias,
|
|
1642
|
-
leftKey: op + 'Id',
|
|
1643
|
-
rightKey: 'id',
|
|
1644
|
-
});
|
|
1645
|
-
analyzeFilterNode({
|
|
1646
|
-
node: node[op],
|
|
1647
|
-
path: pathAttr,
|
|
1648
|
-
entityName: rel,
|
|
1649
|
-
alias: alias2,
|
|
1650
|
-
});
|
|
1651
|
-
}
|
|
1652
|
-
else {
|
|
1653
|
-
analyzeFilterNode({
|
|
1654
|
-
node: node[op],
|
|
1655
|
-
path: pathAttr,
|
|
1656
|
-
entityName: rel,
|
|
1657
|
-
alias: aliasDict[pathAttr],
|
|
1658
|
-
});
|
|
1659
|
-
}
|
|
1660
|
-
}
|
|
1661
|
-
else if (rel === 2) {
|
|
1662
|
-
const pathAttr = `${path}${op}/`;
|
|
1663
|
-
if (!aliasDict.hasOwnProperty(pathAttr)) {
|
|
1664
|
-
const alias2 = `${op}_${number++}`;
|
|
1665
|
-
aliasDict[pathAttr] = alias2;
|
|
1666
|
-
joinInfos.push({
|
|
1667
|
-
table: this.getStorageName(op),
|
|
1668
|
-
alias: alias2,
|
|
1669
|
-
leftAlias: alias,
|
|
1670
|
-
leftKey: 'entityId',
|
|
1671
|
-
rightKey: 'id',
|
|
1672
|
-
extraCondition: `"${alias}"."entity" = '${op}'`,
|
|
1673
|
-
});
|
|
1674
|
-
analyzeFilterNode({
|
|
1675
|
-
node: node[op],
|
|
1676
|
-
path: pathAttr,
|
|
1677
|
-
entityName: op,
|
|
1678
|
-
alias: alias2,
|
|
1679
|
-
});
|
|
1680
|
-
}
|
|
1681
|
-
else {
|
|
1682
|
-
analyzeFilterNode({
|
|
1683
|
-
node: node[op],
|
|
1684
|
-
path: pathAttr,
|
|
1685
|
-
entityName: op,
|
|
1686
|
-
alias: aliasDict[pathAttr],
|
|
1687
|
-
});
|
|
1688
|
-
}
|
|
1689
|
-
}
|
|
1690
|
-
}
|
|
1691
|
-
});
|
|
1692
|
-
if (node['#id']) {
|
|
1693
|
-
(0, assert_1.default)(!filterRefAlias[node['#id']]);
|
|
1694
|
-
filterRefAlias[node['#id']] = [alias, entityName];
|
|
1695
|
-
}
|
|
1696
|
-
};
|
|
1697
|
-
if (filter) {
|
|
1698
|
-
analyzeFilterNode({
|
|
1699
|
-
node: filter,
|
|
1700
|
-
path: './',
|
|
1701
|
-
entityName: entity,
|
|
1702
|
-
alias: mainAlias,
|
|
1703
|
-
});
|
|
1678
|
+
sql = `UPDATE "${mainTable}" AS "${mainAlias}" SET ${setClause} WHERE "${types_1.PrimaryKeyAttribute}" IN (${subquery})`;
|
|
1704
1679
|
}
|
|
1705
|
-
//
|
|
1706
|
-
|
|
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 ');
|
|
1680
|
+
// 添加 RETURNING 子句
|
|
1681
|
+
return this.appendReturningClause(sql, entity, mainAlias, option);
|
|
1729
1682
|
}
|
|
1730
1683
|
/**
|
|
1731
1684
|
* 生成 PostgreSQL UPSERT 语句
|
|
@@ -2138,6 +2091,35 @@ class PostgreSQLTranslator extends sqlTranslator_1.SqlTranslator {
|
|
|
2138
2091
|
}
|
|
2139
2092
|
return sql;
|
|
2140
2093
|
}
|
|
2094
|
+
translateColumnDefinition(entity, attr, attrDef) {
|
|
2095
|
+
let sql = `"${attr}" `;
|
|
2096
|
+
const { type, params, default: defaultValue, unique, notNull, sequenceStart, enumeration, } = attrDef;
|
|
2097
|
+
if (type === 'sequence' || (typeof sequenceStart === 'number')) {
|
|
2098
|
+
sql += `bigint GENERATED BY DEFAULT AS IDENTITY (START WITH ${sequenceStart || 10000}) UNIQUE`;
|
|
2099
|
+
return sql;
|
|
2100
|
+
}
|
|
2101
|
+
if (type === 'enum') {
|
|
2102
|
+
(0, assert_1.default)(enumeration, 'Enum type requires enumeration values');
|
|
2103
|
+
sql += `"${this.getEnumTypeName(entity, attr)}"`;
|
|
2104
|
+
}
|
|
2105
|
+
else {
|
|
2106
|
+
sql += this.populateDataTypeDef(type, params, enumeration);
|
|
2107
|
+
}
|
|
2108
|
+
if (notNull || type === 'geometry') {
|
|
2109
|
+
sql += ' NOT NULL';
|
|
2110
|
+
}
|
|
2111
|
+
if (unique) {
|
|
2112
|
+
sql += ' UNIQUE';
|
|
2113
|
+
}
|
|
2114
|
+
if (defaultValue !== undefined && !sequenceStart) {
|
|
2115
|
+
(0, assert_1.default)(type !== 'ref', 'ref type should not have default value');
|
|
2116
|
+
sql += ` DEFAULT ${this.translateAttrValue(type, defaultValue)}`;
|
|
2117
|
+
}
|
|
2118
|
+
if (attr === 'id') {
|
|
2119
|
+
sql += ' PRIMARY KEY';
|
|
2120
|
+
}
|
|
2121
|
+
return sql;
|
|
2122
|
+
}
|
|
2141
2123
|
/**
|
|
2142
2124
|
* 比较两个 SQL 语句是否等价(用于 schema diff)
|
|
2143
2125
|
* 忽略空格、大小写等格式差异
|
package/lib/index.d.ts
CHANGED
|
@@ -2,3 +2,4 @@ export * from './MySQL/store';
|
|
|
2
2
|
export { MySqlSelectOption, MysqlOperateOption } from './MySQL/translator';
|
|
3
3
|
export * from './PostgreSQL/store';
|
|
4
4
|
export { PostgreSQLSelectOption, PostgreSQLOperateOption } from './PostgreSQL/translator';
|
|
5
|
+
export type { MigrationPlan, MigrationPlanningOptions, MigrationWarning, RenameCandidate, SchemaInspectionResult, TableStats, Plan, } from './types/migration';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Attribute } from 'oak-domain/lib/types';
|
|
2
|
+
import { AttributeChangeClassification, AttributeSnapshot, MigrationEntityDict, MigrationIndexWithOrigin, MigrationSchema, MigrationPlanningOptions, IndexSnapshot, MigrationPlan, SchemaMigrationAdapter } from './types/migration';
|
|
3
|
+
export declare function normalizeAttribute(attr: Attribute, options?: {
|
|
4
|
+
schema?: MigrationSchema;
|
|
5
|
+
column?: string;
|
|
6
|
+
dialect?: SchemaMigrationAdapter['dialect'];
|
|
7
|
+
compareForeignKeys?: boolean;
|
|
8
|
+
}): AttributeSnapshot;
|
|
9
|
+
export declare function normalizeIndex(index: MigrationIndexWithOrigin): IndexSnapshot;
|
|
10
|
+
export declare function areAttributesEquivalent(oldAttr: Attribute, newAttr: Attribute, options?: {
|
|
11
|
+
oldSchema?: MigrationSchema;
|
|
12
|
+
newSchema?: MigrationSchema;
|
|
13
|
+
column?: string;
|
|
14
|
+
oldColumn?: string;
|
|
15
|
+
newColumn?: string;
|
|
16
|
+
dialect?: SchemaMigrationAdapter['dialect'];
|
|
17
|
+
compareForeignKeys?: boolean;
|
|
18
|
+
}): boolean;
|
|
19
|
+
export declare function areIndexesEquivalent(oldIndex: MigrationIndexWithOrigin, newIndex: MigrationIndexWithOrigin): boolean;
|
|
20
|
+
export declare function classifyAttributeChange(oldAttr?: Attribute, newAttr?: Attribute, options?: {
|
|
21
|
+
oldSchema?: MigrationSchema;
|
|
22
|
+
newSchema?: MigrationSchema;
|
|
23
|
+
column?: string;
|
|
24
|
+
dialect?: SchemaMigrationAdapter['dialect'];
|
|
25
|
+
compareForeignKeys?: boolean;
|
|
26
|
+
}): AttributeChangeClassification;
|
|
27
|
+
export declare function buildMigrationPlan<ED extends MigrationEntityDict>(currentSchema: MigrationSchema<ED>, targetSchema: MigrationSchema<ED>, adapter: SchemaMigrationAdapter<ED>, options?: MigrationPlanningOptions): MigrationPlan<ED>;
|