oak-db 3.3.12 → 3.3.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,7 +7,6 @@ const util_1 = require("util");
7
7
  const lodash_1 = require("lodash");
8
8
  const types_1 = require("oak-domain/lib/types");
9
9
  const sqlTranslator_1 = require("../sqlTranslator");
10
- const relation_1 = require("oak-domain/lib/store/relation");
11
10
  const GeoTypes = [
12
11
  {
13
12
  type: 'point',
@@ -741,6 +740,7 @@ class PostgreSQLTranslator extends sqlTranslator_1.SqlTranslator {
741
740
  else if (typeof value === 'number') {
742
741
  return `${value}`;
743
742
  }
743
+ (0, assert_1.default)(typeof value === 'string', 'Invalid date/time value');
744
744
  return `'${(new Date(value)).valueOf()}'`;
745
745
  }
746
746
  case 'object':
@@ -967,12 +967,8 @@ class PostgreSQLTranslator extends sqlTranslator_1.SqlTranslator {
967
967
  }
968
968
  indexSql += '(';
969
969
  const indexColumns = [];
970
- let includeDeleteAt = false;
971
970
  for (const indexAttr of indexAttrs) {
972
971
  const { name: attrName, direction } = indexAttr;
973
- if (attrName === '$$deleteAt$$') {
974
- includeDeleteAt = true;
975
- }
976
972
  if (indexType === 'fulltext') {
977
973
  // 全文索引:使用 to_tsvector
978
974
  indexColumns.push(`to_tsvector('${tsLang}', COALESCE("${attrName}", ''))`);
@@ -986,10 +982,6 @@ class PostgreSQLTranslator extends sqlTranslator_1.SqlTranslator {
986
982
  indexColumns.push(col);
987
983
  }
988
984
  }
989
- // 非特殊索引自动包含 deleteAt
990
- if (!includeDeleteAt && !indexType) {
991
- indexColumns.push('"$$deleteAt$$"');
992
- }
993
985
  indexSql += indexColumns.join(', ');
994
986
  indexSql += ');';
995
987
  sqls.push(indexSql);
@@ -1490,10 +1482,11 @@ class PostgreSQLTranslator extends sqlTranslator_1.SqlTranslator {
1490
1482
  translateUpdate(entity, operation, option) {
1491
1483
  const { attributes } = this.schema[entity];
1492
1484
  const { filter, sorter, indexFrom, count, data } = operation;
1493
- (0, assert_1.default)(!sorter, '当前update不支持sorter行为');
1494
- // 使用结构化的JOIN分析
1495
- const { aliasDict, filterRefAlias, mainTable, mainAlias, joinInfos, currentNumber } = this.analyzeJoinStructured(entity, { filter, sorter });
1496
- // 构建SET子句 - PostgreSQL中SET子句不能使用表别名前缀
1485
+ // 参数验证
1486
+ this.validateOperationParams(sorter, indexFrom, count);
1487
+ const mainTable = this.getStorageName(entity);
1488
+ const mainAlias = `${entity}_1`;
1489
+ // 构建 SET 子句
1497
1490
  const setClauses = [];
1498
1491
  for (const attr in data) {
1499
1492
  (0, assert_1.default)(attributes.hasOwnProperty(attr));
@@ -1501,73 +1494,93 @@ class PostgreSQLTranslator extends sqlTranslator_1.SqlTranslator {
1501
1494
  setClauses.push(`"${attr}" = ${value}`);
1502
1495
  }
1503
1496
  const setClause = setClauses.join(', ');
1504
- // 构建过滤条件
1505
- const { stmt: filterText } = this.translateFilter(entity, filter, aliasDict, filterRefAlias, currentNumber, option);
1506
- let sql;
1507
- if (joinInfos.length === 0) {
1508
- // 单表更新
1509
- sql = `UPDATE "${mainTable}" AS "${mainAlias}" SET ${setClause}`;
1510
- if (filterText) {
1511
- sql += ` WHERE ${filterText}`;
1512
- }
1497
+ // 统一使用子查询方案
1498
+ const subqueryOperation = {
1499
+ data: { [types_1.PrimaryKeyAttribute]: 1 },
1500
+ filter: filter,
1501
+ indexFrom: indexFrom,
1502
+ count: count
1503
+ };
1504
+ const subquery = this.translateSelect(entity, subqueryOperation, { includedDeleted: option?.includedDeleted });
1505
+ let sql = `UPDATE "${mainTable}" AS "${mainAlias}" SET ${setClause} WHERE "${types_1.PrimaryKeyAttribute}" IN (${subquery})`;
1506
+ // 添加 RETURNING 子句
1507
+ return this.appendReturningClause(sql, entity, mainAlias, option);
1508
+ }
1509
+ /**
1510
+ * 将 projection 转换为 RETURNING 子句的列列表
1511
+ * @param entity 实体名
1512
+ * @param alias 表别名
1513
+ * @param projection 投影定义
1514
+ */
1515
+ buildReturningClause(entity, alias, projection) {
1516
+ const columns = [];
1517
+ const { attributes } = this.schema[entity];
1518
+ for (const attr in projection) {
1519
+ // if (!attributes[attr]) {
1520
+ // // 如果存在entity这个attr
1521
+ // const entityDesc = attributes['entity']
1522
+ // const entityIdDesc = attributes['entityId']
1523
+ // if (entityDesc && entityIdDesc && ((entityDesc.ref as string[])?.includes(attr))) {
1524
+ // // 特殊处理 entity 和 entityId 属性
1525
+ // }
1526
+ // continue;
1527
+ // }
1528
+ // 只能返回更新列的相关字段,所以直接assert
1529
+ (0, assert_1.default)(attributes.hasOwnProperty(attr), `RETURNING语法只能返回更新列的字段,但在实体 ${String(entity)} 中未找到原生属性「${attr}」的定义`);
1530
+ const dataType = attributes[attr].type;
1531
+ // 处理特殊类型的投影
1532
+ const columnExpr = this.translateAttrProjection(dataType, alias, attr);
1533
+ // RETURNING 需要明确的别名(不带表前缀)
1534
+ columns.push(`${columnExpr} AS "${attr}"`);
1513
1535
  }
1514
- else {
1515
- // 多表更新 - PostgreSQL语法: UPDATE main SET ... FROM other_tables WHERE join_conditions AND filter
1516
- sql = `UPDATE "${mainTable}" AS "${mainAlias}" SET ${setClause}`;
1517
- // FROM子句包含所有JOIN的表
1518
- const fromTables = joinInfos.map(j => `"${j.table}" AS "${j.alias}"`).join(', ');
1519
- sql += ` FROM ${fromTables}`;
1520
- // WHERE子句包含JOIN条件和过滤条件
1521
- const joinConditions = this.buildJoinConditions(joinInfos);
1522
- let whereClause = joinConditions;
1523
- if (filterText) {
1524
- whereClause = whereClause ? `${whereClause} AND ${filterText}` : filterText;
1525
- }
1526
- if (whereClause) {
1527
- sql += ` WHERE ${whereClause}`;
1528
- }
1536
+ if (columns.length === 0) {
1537
+ throw new Error(`No valid columns in RETURNING clause for entity ${String(entity)}`);
1538
+ }
1539
+ return columns.join(', ');
1540
+ }
1541
+ /**
1542
+ * 验证操作参数的合法性
1543
+ */
1544
+ validateOperationParams(sorter, indexFrom, count) {
1545
+ (0, assert_1.default)(!sorter, '当前update/remove不支持sorter行为');
1546
+ // 对于limit,如果有indexFrom则一定有count
1547
+ if (typeof indexFrom === 'number') {
1548
+ (0, assert_1.default)(typeof count === 'number' && count > 0);
1549
+ }
1550
+ }
1551
+ /**
1552
+ * 添加RETURNING子句
1553
+ */
1554
+ appendReturningClause(sql, entity, mainAlias, option) {
1555
+ if (option?.returning) {
1556
+ const returningClause = this.buildReturningClause(entity, mainAlias, option.returning);
1557
+ return `${sql} RETURNING ${returningClause}`;
1529
1558
  }
1530
1559
  return sql;
1531
1560
  }
1532
1561
  translateRemove(entity, operation, option) {
1533
1562
  const { data, filter, sorter, indexFrom, count } = operation;
1534
- (0, assert_1.default)(!sorter, '当前remove不支持sorter行为');
1535
- // 使用结构化的JOIN分析
1536
- const { aliasDict, filterRefAlias, mainTable, mainAlias, joinInfos, currentNumber } = this.analyzeJoinStructured(entity, { filter, sorter });
1537
- // 构建过滤条件
1538
- const { stmt: filterText } = this.translateFilter(entity, filter, aliasDict, filterRefAlias, currentNumber, { includedDeleted: option?.includedDeleted });
1539
1563
  const { attributes } = this.schema[entity];
1564
+ // 参数验证
1565
+ this.validateOperationParams(sorter, indexFrom, count);
1566
+ const mainTable = this.getStorageName(entity);
1567
+ const mainAlias = `${entity}_1`;
1568
+ // 构建子查询
1569
+ const subqueryOperation = {
1570
+ data: { [types_1.PrimaryKeyAttribute]: 1 },
1571
+ filter: filter,
1572
+ indexFrom: indexFrom,
1573
+ count: count
1574
+ };
1575
+ const subquery = this.translateSelect(entity, subqueryOperation, { includedDeleted: option?.includedDeleted });
1576
+ let sql;
1540
1577
  if (option?.deletePhysically) {
1541
1578
  // 物理删除
1542
1579
  (0, assert_1.default)((0, lodash_1.difference)(Object.keys(data), [types_1.UpdateAtAttribute, types_1.DeleteAtAttribute]).length === 0);
1543
- let sql;
1544
- if (joinInfos.length === 0) {
1545
- // 单表删除
1546
- sql = `DELETE FROM "${mainTable}" AS "${mainAlias}"`;
1547
- if (filterText) {
1548
- sql += ` WHERE ${filterText}`;
1549
- }
1550
- }
1551
- else {
1552
- // 多表删除 - PostgreSQL语法: DELETE FROM main USING other_tables WHERE join_conditions AND filter
1553
- sql = `DELETE FROM "${mainTable}" AS "${mainAlias}"`;
1554
- // USING子句包含所有JOIN的表
1555
- const usingTables = joinInfos.map(j => `"${j.table}" AS "${j.alias}"`).join(', ');
1556
- sql += ` USING ${usingTables}`;
1557
- // WHERE子句包含JOIN条件和过滤条件
1558
- const joinConditions = this.buildJoinConditions(joinInfos);
1559
- let whereClause = joinConditions;
1560
- if (filterText) {
1561
- whereClause = whereClause ? `${whereClause} AND ${filterText}` : filterText;
1562
- }
1563
- if (whereClause) {
1564
- sql += ` WHERE ${whereClause}`;
1565
- }
1566
- }
1567
- return sql;
1580
+ sql = `DELETE FROM "${mainTable}" AS "${mainAlias}" WHERE "${types_1.PrimaryKeyAttribute}" IN (${subquery})`;
1568
1581
  }
1569
1582
  else {
1570
- // 软删除 - 实际是UPDATE操作
1583
+ // 软删除- 实际是 UPDATE
1571
1584
  const setClauses = [];
1572
1585
  for (const attr in data) {
1573
1586
  (0, assert_1.default)([types_1.TriggerDataAttribute, types_1.TriggerUuidAttribute, types_1.DeleteAtAttribute, types_1.UpdateAtAttribute].includes(attr));
@@ -1575,164 +1588,10 @@ class PostgreSQLTranslator extends sqlTranslator_1.SqlTranslator {
1575
1588
  setClauses.push(`"${attr}" = ${value}`);
1576
1589
  }
1577
1590
  const setClause = setClauses.join(', ');
1578
- let sql;
1579
- if (joinInfos.length === 0) {
1580
- // 单表更新
1581
- sql = `UPDATE "${mainTable}" AS "${mainAlias}" SET ${setClause}`;
1582
- if (filterText) {
1583
- sql += ` WHERE ${filterText}`;
1584
- }
1585
- }
1586
- else {
1587
- // 多表更新
1588
- sql = `UPDATE "${mainTable}" AS "${mainAlias}" SET ${setClause}`;
1589
- const fromTables = joinInfos.map(j => `"${j.table}" AS "${j.alias}"`).join(', ');
1590
- sql += ` FROM ${fromTables}`;
1591
- const joinConditions = this.buildJoinConditions(joinInfos);
1592
- let whereClause = joinConditions;
1593
- if (filterText) {
1594
- whereClause = whereClause ? `${whereClause} AND ${filterText}` : filterText;
1595
- }
1596
- if (whereClause) {
1597
- sql += ` WHERE ${whereClause}`;
1598
- }
1599
- }
1600
- return sql;
1601
- }
1602
- }
1603
- /**
1604
- * PostgreSQL专用的结构化JOIN分析
1605
- * 返回结构化的JOIN信息,而不是拼接好的FROM字符串
1606
- */
1607
- analyzeJoinStructured(entity, { filter, sorter }, initialNumber) {
1608
- const { schema } = this;
1609
- let number = initialNumber || 1;
1610
- const filterRefAlias = {};
1611
- const mainAlias = `${entity}_${number++}`;
1612
- const mainTable = this.getStorageName(entity);
1613
- const aliasDict = {
1614
- './': mainAlias,
1615
- };
1616
- const joinInfos = [];
1617
- const analyzeFilterNode = ({ node, path, entityName, alias }) => {
1618
- Object.keys(node).forEach((op) => {
1619
- if (['$and', '$or'].includes(op)) {
1620
- node[op].forEach((subNode) => analyzeFilterNode({
1621
- node: subNode,
1622
- path,
1623
- entityName,
1624
- alias,
1625
- }));
1626
- }
1627
- else if (['$not'].includes(op)) {
1628
- analyzeFilterNode({
1629
- node: node[op],
1630
- path,
1631
- entityName,
1632
- alias,
1633
- });
1634
- }
1635
- else if (['$text'].includes(op)) {
1636
- // 全文搜索,不需要JOIN
1637
- }
1638
- else {
1639
- const rel = (0, relation_1.judgeRelation)(this.schema, entityName, op);
1640
- if (typeof rel === 'string') {
1641
- const pathAttr = `${path}${op}/`;
1642
- if (!aliasDict.hasOwnProperty(pathAttr)) {
1643
- const alias2 = `${rel}_${number++}`;
1644
- aliasDict[pathAttr] = alias2;
1645
- joinInfos.push({
1646
- table: this.getStorageName(rel),
1647
- alias: alias2,
1648
- leftAlias: alias,
1649
- leftKey: op + 'Id',
1650
- rightKey: 'id',
1651
- });
1652
- analyzeFilterNode({
1653
- node: node[op],
1654
- path: pathAttr,
1655
- entityName: rel,
1656
- alias: alias2,
1657
- });
1658
- }
1659
- else {
1660
- analyzeFilterNode({
1661
- node: node[op],
1662
- path: pathAttr,
1663
- entityName: rel,
1664
- alias: aliasDict[pathAttr],
1665
- });
1666
- }
1667
- }
1668
- else if (rel === 2) {
1669
- const pathAttr = `${path}${op}/`;
1670
- if (!aliasDict.hasOwnProperty(pathAttr)) {
1671
- const alias2 = `${op}_${number++}`;
1672
- aliasDict[pathAttr] = alias2;
1673
- joinInfos.push({
1674
- table: this.getStorageName(op),
1675
- alias: alias2,
1676
- leftAlias: alias,
1677
- leftKey: 'entityId',
1678
- rightKey: 'id',
1679
- extraCondition: `"${alias}"."entity" = '${op}'`,
1680
- });
1681
- analyzeFilterNode({
1682
- node: node[op],
1683
- path: pathAttr,
1684
- entityName: op,
1685
- alias: alias2,
1686
- });
1687
- }
1688
- else {
1689
- analyzeFilterNode({
1690
- node: node[op],
1691
- path: pathAttr,
1692
- entityName: op,
1693
- alias: aliasDict[pathAttr],
1694
- });
1695
- }
1696
- }
1697
- }
1698
- });
1699
- if (node['#id']) {
1700
- (0, assert_1.default)(!filterRefAlias[node['#id']]);
1701
- filterRefAlias[node['#id']] = [alias, entityName];
1702
- }
1703
- };
1704
- if (filter) {
1705
- analyzeFilterNode({
1706
- node: filter,
1707
- path: './',
1708
- entityName: entity,
1709
- alias: mainAlias,
1710
- });
1711
- }
1712
- // TODO: sorter的分析类似,这里省略(UPDATE/DELETE通常不需要sorter)
1713
- (0, assert_1.default)(!sorter, '当前analyzeJoinStructured不支持sorter行为');
1714
- return {
1715
- aliasDict,
1716
- filterRefAlias,
1717
- mainTable,
1718
- mainAlias,
1719
- joinInfos,
1720
- currentNumber: number,
1721
- };
1722
- }
1723
- /**
1724
- * 构建JOIN条件(用于UPDATE/DELETE的WHERE子句)
1725
- */
1726
- buildJoinConditions(joinInfos) {
1727
- const conditions = [];
1728
- for (const join of joinInfos) {
1729
- let condition = `"${join.leftAlias}"."${join.leftKey}" = "${join.alias}"."${join.rightKey}"`;
1730
- if (join.extraCondition) {
1731
- condition = `(${condition} AND ${join.extraCondition})`;
1732
- }
1733
- conditions.push(condition);
1591
+ sql = `UPDATE "${mainTable}" AS "${mainAlias}" SET ${setClause} WHERE "${types_1.PrimaryKeyAttribute}" IN (${subquery})`;
1734
1592
  }
1735
- return conditions.join(' AND ');
1593
+ // 添加 RETURNING 子句
1594
+ return this.appendReturningClause(sql, entity, mainAlias, option);
1736
1595
  }
1737
1596
  /**
1738
1597
  * 生成 PostgreSQL UPSERT 语句
@@ -1766,5 +1625,401 @@ class PostgreSQLTranslator extends sqlTranslator_1.SqlTranslator {
1766
1625
  // 这个方法不应该被直接调用了,因为translateRemove已经重写
1767
1626
  throw new Error('populateRemoveStmt should not be called directly in PostgreSQL. Use translateRemove instead.');
1768
1627
  }
1628
+ /**
1629
+ * 将 PostgreSQL 返回的 Type 回译成 oak 的类型,是 populateDataTypeDef 的反函数
1630
+ * @param type PostgreSQL 类型字符串
1631
+ */
1632
+ reTranslateToAttribute(type) {
1633
+ // 处理带长度的类型:character varying(255), character(10)
1634
+ const varcharMatch = /^character varying\((\d+)\)$/.exec(type);
1635
+ if (varcharMatch) {
1636
+ return {
1637
+ type: 'varchar',
1638
+ params: {
1639
+ length: parseInt(varcharMatch[1], 10),
1640
+ }
1641
+ };
1642
+ }
1643
+ const charMatch = /^character\((\d+)\)$/.exec(type);
1644
+ if (charMatch) {
1645
+ return {
1646
+ type: 'char',
1647
+ params: {
1648
+ length: parseInt(charMatch[1], 10),
1649
+ }
1650
+ };
1651
+ }
1652
+ // 处理带精度和小数位的类型:numeric(10,2)
1653
+ const numericWithScaleMatch = /^numeric\((\d+),(\d+)\)$/.exec(type);
1654
+ if (numericWithScaleMatch) {
1655
+ return {
1656
+ type: 'decimal',
1657
+ params: {
1658
+ precision: parseInt(numericWithScaleMatch[1], 10),
1659
+ scale: parseInt(numericWithScaleMatch[2], 10),
1660
+ },
1661
+ };
1662
+ }
1663
+ // 处理只带精度的类型:numeric(10), timestamp(6)
1664
+ const numericMatch = /^numeric\((\d+)\)$/.exec(type);
1665
+ if (numericMatch) {
1666
+ return {
1667
+ type: 'decimal',
1668
+ params: {
1669
+ precision: parseInt(numericMatch[1], 10),
1670
+ scale: 0,
1671
+ },
1672
+ };
1673
+ }
1674
+ const timestampMatch = /^timestamp\((\d+)\) without time zone$/.exec(type);
1675
+ if (timestampMatch) {
1676
+ return {
1677
+ type: 'timestamp',
1678
+ params: {
1679
+ precision: parseInt(timestampMatch[1], 10),
1680
+ },
1681
+ };
1682
+ }
1683
+ const timeMatch = /^time\((\d+)\) without time zone$/.exec(type);
1684
+ if (timeMatch) {
1685
+ return {
1686
+ type: 'time',
1687
+ params: {
1688
+ precision: parseInt(timeMatch[1], 10),
1689
+ },
1690
+ };
1691
+ }
1692
+ // PostgreSQL 类型映射到 oak 类型
1693
+ const typeMap = {
1694
+ 'bigint': 'bigint',
1695
+ 'integer': 'integer',
1696
+ 'smallint': 'smallint',
1697
+ 'real': 'real',
1698
+ 'double precision': 'double precision',
1699
+ 'boolean': 'boolean',
1700
+ 'text': 'text',
1701
+ 'jsonb': 'object',
1702
+ 'json': 'object',
1703
+ 'bytea': 'bytea',
1704
+ 'character varying': 'varchar',
1705
+ 'character': 'char',
1706
+ 'timestamp without time zone': 'timestamp',
1707
+ 'time without time zone': 'time',
1708
+ 'date': 'date',
1709
+ 'uuid': 'uuid',
1710
+ 'geometry': 'geometry',
1711
+ 'numeric': 'decimal',
1712
+ };
1713
+ const mappedType = typeMap[type];
1714
+ if (mappedType) {
1715
+ return { type: mappedType };
1716
+ }
1717
+ // 如果是用户定义的枚举类型,返回 enum(具体值需要额外查询)
1718
+ // 这里先返回基础类型,枚举值在 readSchema 中单独处理
1719
+ return { type: type };
1720
+ }
1721
+ /**
1722
+ * 从 PostgreSQL 数据库读取当前的 schema 结构
1723
+ */
1724
+ async readSchema(execFn) {
1725
+ const result = {};
1726
+ // 1. 获取所有表
1727
+ const tablesSql = `
1728
+ SELECT tablename
1729
+ FROM pg_tables
1730
+ WHERE schemaname = 'public'
1731
+ ORDER BY tablename;
1732
+ `;
1733
+ const [tablesResult] = await execFn(tablesSql);
1734
+ for (const tableRow of tablesResult) {
1735
+ const tableName = tableRow.tablename;
1736
+ // 2. 获取表的列信息
1737
+ const columnsSql = `
1738
+ SELECT
1739
+ column_name,
1740
+ data_type,
1741
+ character_maximum_length,
1742
+ numeric_precision,
1743
+ numeric_scale,
1744
+ is_nullable,
1745
+ column_default,
1746
+ udt_name
1747
+ FROM information_schema.columns
1748
+ WHERE table_schema = 'public'
1749
+ AND table_name = '${tableName}'
1750
+ ORDER BY ordinal_position;
1751
+ `;
1752
+ const [columnsResult] = await execFn(columnsSql);
1753
+ const attributes = {};
1754
+ for (const col of columnsResult) {
1755
+ 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;
1756
+ let attr;
1757
+ // 处理用户定义类型(枚举)
1758
+ if (dataType === 'USER-DEFINED') {
1759
+ const enumSql = `
1760
+ SELECT e.enumlabel
1761
+ FROM pg_type t
1762
+ JOIN pg_enum e ON t.oid = e.enumtypid
1763
+ WHERE t.typname = '${udtName}'
1764
+ ORDER BY e.enumsortorder;
1765
+ `;
1766
+ const [enumResult] = await execFn(enumSql);
1767
+ const enumeration = enumResult.map((r) => r.enumlabel);
1768
+ attr = {
1769
+ type: 'enum',
1770
+ enumeration,
1771
+ };
1772
+ }
1773
+ else {
1774
+ // 构建完整的类型字符串
1775
+ let fullType = dataType;
1776
+ const integerTypes = ['bigint', 'integer', 'smallint', 'serial', 'bigserial', 'smallserial'];
1777
+ if (maxLength && !integerTypes.includes(dataType)) {
1778
+ fullType = `${dataType}(${maxLength})`;
1779
+ }
1780
+ else if (precision !== null && scale !== null && !integerTypes.includes(dataType)) {
1781
+ fullType = `${dataType}(${precision},${scale})`;
1782
+ }
1783
+ else if (precision !== null && !integerTypes.includes(dataType)) {
1784
+ fullType = `${dataType}(${precision})`;
1785
+ }
1786
+ attr = this.reTranslateToAttribute(fullType);
1787
+ }
1788
+ // ========== 类型还原逻辑 ==========
1789
+ // 框架将某些语义类型存储为 bigint,需要根据列名还原
1790
+ if (attr.type === 'bigint') {
1791
+ // 1. 检查是否是序列列
1792
+ if (colName === '$$seq$$' || (defaultValue && defaultValue.includes('nextval'))) {
1793
+ attr.type = 'sequence';
1794
+ attr.sequenceStart = 10000; // 默认起始值
1795
+ }
1796
+ // 2. 检查是否是时间戳列
1797
+ else if (['$$createAt$$', '$$updateAt$$', '$$deleteAt$$'].includes(colName)) {
1798
+ attr.type = 'datetime';
1799
+ }
1800
+ // 3. 检查其他可能的时间类型列(根据命名约定)
1801
+ else if (colName.endsWith('At') || colName.endsWith('Time')) {
1802
+ // 可选:根据业务约定判断是否应该是 datetime
1803
+ // 这里保守处理,只转换框架标准字段
1804
+ }
1805
+ }
1806
+ // 处理约束 - 只在为 true 时添加
1807
+ if (isNullable === 'NO') {
1808
+ attr.notNull = true;
1809
+ }
1810
+ // 处理默认值
1811
+ if (defaultValue && !defaultValue.includes('nextval')) {
1812
+ let cleanDefault = defaultValue.replace(/::[a-z]+/gi, '').replace(/'/g, '');
1813
+ if (cleanDefault === 'true') {
1814
+ attr.default = true;
1815
+ }
1816
+ else if (cleanDefault === 'false') {
1817
+ attr.default = false;
1818
+ }
1819
+ else if (!isNaN(Number(cleanDefault))) {
1820
+ attr.default = Number(cleanDefault);
1821
+ }
1822
+ else if (cleanDefault !== '') {
1823
+ attr.default = cleanDefault;
1824
+ }
1825
+ }
1826
+ // 检查唯一约束
1827
+ const uniqueSql = `
1828
+ SELECT COUNT(*) as cnt
1829
+ FROM pg_index ix
1830
+ JOIN pg_class t ON t.oid = ix.indrelid
1831
+ JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(ix.indkey)
1832
+ WHERE t.relname = '${tableName}'
1833
+ AND a.attname = '${colName}'
1834
+ AND ix.indisunique = true
1835
+ AND NOT ix.indisprimary
1836
+ AND array_length(ix.indkey, 1) = 1;
1837
+ `;
1838
+ const [uniqueResult] = await execFn(uniqueSql);
1839
+ const uniqueCount = parseInt(uniqueResult[0]?.cnt || '0', 10);
1840
+ if (uniqueCount > 0) {
1841
+ attr.unique = true;
1842
+ }
1843
+ attributes[colName] = attr;
1844
+ }
1845
+ // 3. 获取索引信息
1846
+ const indexesSql = `
1847
+ SELECT
1848
+ i.relname as index_name,
1849
+ ix.indisunique as is_unique,
1850
+ am.amname as index_type,
1851
+ pg_get_indexdef(ix.indexrelid) as index_def
1852
+ FROM pg_class t
1853
+ JOIN pg_index ix ON t.oid = ix.indrelid
1854
+ JOIN pg_class i ON i.oid = ix.indexrelid
1855
+ JOIN pg_am am ON i.relam = am.oid
1856
+ WHERE t.relname = '${tableName}'
1857
+ AND t.relkind = 'r'
1858
+ AND i.relname NOT LIKE '%_pkey'
1859
+ AND NOT ix.indisprimary
1860
+ ORDER BY i.relname;
1861
+ `;
1862
+ const [indexesResult] = await execFn(indexesSql);
1863
+ if (indexesResult.length > 0) {
1864
+ const indexes = [];
1865
+ for (const row of indexesResult) {
1866
+ const { index_name: indexName, is_unique: isUnique, index_type: indexType, index_def: indexDef } = row;
1867
+ // 解析索引定义以获取列名和配置
1868
+ const index = {
1869
+ name: indexName,
1870
+ attributes: [],
1871
+ };
1872
+ // 解析索引定义字符串
1873
+ // 示例: CREATE INDEX "user_index_fulltext_chinese" ON public."user" USING gin (to_tsvector('chinese'::regconfig, (COALESCE(name, ''::text) || ' '::text) || COALESCE(nickname, ''::text)))
1874
+ if (indexType === 'gin' && indexDef.includes('to_tsvector')) {
1875
+ // 全文索引
1876
+ index.config = { type: 'fulltext' };
1877
+ // 提取 tsConfig
1878
+ const tsConfigMatch = indexDef.match(/to_tsvector\('([^']+)'/);
1879
+ if (tsConfigMatch) {
1880
+ const tsConfig = tsConfigMatch[1];
1881
+ index.config.tsConfig = tsConfig;
1882
+ }
1883
+ // 提取列名(从 COALESCE 中)
1884
+ const columnMatches = indexDef.matchAll(/COALESCE\("?([^",\s]+)"?/g);
1885
+ const columns = Array.from(columnMatches, m => m[1]);
1886
+ index.attributes = columns.map(col => ({ name: col }));
1887
+ // 处理多语言索引的情况:移除语言后缀
1888
+ // 例如: user_index_fulltext_chinese -> index_fulltext
1889
+ const nameParts = indexName.split('_');
1890
+ if (nameParts.length > 2) {
1891
+ const possibleLang = nameParts[nameParts.length - 1];
1892
+ // 如果最后一部分是语言代码,移除它
1893
+ if (['chinese', 'english', 'simple', 'german', 'french', 'spanish', 'russian', 'japanese'].includes(possibleLang)) {
1894
+ index.name = nameParts.slice(0, -1).join('_');
1895
+ }
1896
+ }
1897
+ }
1898
+ else if (indexType === 'gist') {
1899
+ // 空间索引
1900
+ index.config = { type: 'spatial' };
1901
+ // 提取列名
1902
+ const columnMatch = indexDef.match(/\(([^)]+)\)/);
1903
+ if (columnMatch) {
1904
+ const columns = columnMatch[1].split(',').map(c => c.trim().replace(/"/g, ''));
1905
+ index.attributes = columns.map(col => ({ name: col }));
1906
+ }
1907
+ }
1908
+ else if (indexType === 'hash') {
1909
+ // 哈希索引
1910
+ index.config = { type: 'hash' };
1911
+ // 提取列名
1912
+ const columnMatch = indexDef.match(/\(([^)]+)\)/);
1913
+ if (columnMatch) {
1914
+ const columns = columnMatch[1].split(',').map(c => c.trim().replace(/"/g, ''));
1915
+ index.attributes = columns.map(col => ({ name: col }));
1916
+ }
1917
+ }
1918
+ else {
1919
+ // B-tree 索引(默认)
1920
+ // 提取列名和排序方向
1921
+ const columnMatch = indexDef.match(/\(([^)]+)\)/);
1922
+ if (columnMatch) {
1923
+ const columnDefs = columnMatch[1].split(',');
1924
+ index.attributes = columnDefs.map(colDef => {
1925
+ const trimmed = colDef.trim().replace(/"/g, '');
1926
+ const parts = trimmed.split(/\s+/);
1927
+ const attr = { name: parts[0] };
1928
+ // 检查排序方向
1929
+ if (parts.includes('DESC')) {
1930
+ attr.direction = 'DESC';
1931
+ }
1932
+ else if (parts.includes('ASC')) {
1933
+ attr.direction = 'ASC';
1934
+ }
1935
+ return attr;
1936
+ });
1937
+ }
1938
+ // 如果是唯一索引
1939
+ if (isUnique) {
1940
+ index.config = { unique: true };
1941
+ }
1942
+ }
1943
+ // 移除表名前缀(如果存在)
1944
+ // 例如: user_index_fulltext -> index_fulltext
1945
+ if (index.name.startsWith(`${tableName}_`)) {
1946
+ index.name = index.name.substring(tableName.length + 1);
1947
+ }
1948
+ indexes.push(index);
1949
+ }
1950
+ Object.assign(result, {
1951
+ [tableName]: {
1952
+ attributes,
1953
+ indexes,
1954
+ }
1955
+ });
1956
+ }
1957
+ else {
1958
+ Object.assign(result, {
1959
+ [tableName]: {
1960
+ attributes,
1961
+ }
1962
+ });
1963
+ }
1964
+ }
1965
+ return result;
1966
+ }
1967
+ /**
1968
+ * 将属性定义转换为 PostgreSQL DDL 语句
1969
+ * @param attr 属性名
1970
+ * @param attrDef 属性定义
1971
+ */
1972
+ translateAttributeDef(attr, attrDef) {
1973
+ let sql = `"${attr}" `;
1974
+ const { type, params, default: defaultValue, unique, notNull, sequenceStart, enumeration, } = attrDef;
1975
+ // 处理序列类型(IDENTITY)
1976
+ if (type === 'sequence' || (typeof sequenceStart === 'number')) {
1977
+ sql += `bigint GENERATED BY DEFAULT AS IDENTITY (START WITH ${sequenceStart || 10000}) UNIQUE`;
1978
+ return sql;
1979
+ }
1980
+ // 处理枚举类型
1981
+ if (type === 'enum') {
1982
+ (0, assert_1.default)(enumeration, 'Enum type requires enumeration values');
1983
+ sql += `enum(${enumeration.map(v => `'${v}'`).join(',')})`;
1984
+ }
1985
+ else {
1986
+ sql += this.populateDataTypeDef(type, params, enumeration);
1987
+ }
1988
+ // NOT NULL 约束
1989
+ if (notNull || type === 'geometry') {
1990
+ sql += ' NOT NULL';
1991
+ }
1992
+ // UNIQUE 约束
1993
+ if (unique) {
1994
+ sql += ' UNIQUE';
1995
+ }
1996
+ // 默认值
1997
+ if (defaultValue !== undefined && !sequenceStart) {
1998
+ (0, assert_1.default)(type !== 'ref', 'ref type should not have default value');
1999
+ sql += ` DEFAULT ${this.translateAttrValue(type, defaultValue)}`;
2000
+ }
2001
+ // 主键
2002
+ if (attr === 'id') {
2003
+ sql += ' PRIMARY KEY';
2004
+ }
2005
+ return sql;
2006
+ }
2007
+ /**
2008
+ * 比较两个 SQL 语句是否等价(用于 schema diff)
2009
+ * 忽略空格、大小写等格式差异
2010
+ */
2011
+ compareSql(sql1, sql2) {
2012
+ // 标准化 SQL:移除多余空格,统一大小写
2013
+ const normalize = (sql) => {
2014
+ return sql
2015
+ .replace(/\s+/g, ' ') // 多个空格合并为一个
2016
+ .replace(/\(\s+/g, '(') // 移除括号后的空格
2017
+ .replace(/\s+\)/g, ')') // 移除括号前的空格
2018
+ .replace(/,\s+/g, ',') // 移除逗号后的空格
2019
+ .trim()
2020
+ .toLowerCase();
2021
+ };
2022
+ return normalize(sql1) === normalize(sql2);
2023
+ }
1769
2024
  }
1770
2025
  exports.PostgreSQLTranslator = PostgreSQLTranslator;