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.
@@ -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 relation_1 = require("oak-domain/lib/store/relation");
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)}_${name}${suffix}" ON "${tableName}" `;
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
- (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子句不能使用表别名前缀
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 { 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
- }
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
- 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
- }
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
- 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;
1667
+ sql = `DELETE FROM "${mainTable}" AS "${mainAlias}" WHERE "${types_1.PrimaryKeyAttribute}" IN (${subquery})`;
1561
1668
  }
1562
1669
  else {
1563
- // 软删除 - 实际是UPDATE操作
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
- 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
- });
1678
+ sql = `UPDATE "${mainTable}" AS "${mainAlias}" SET ${setClause} WHERE "${types_1.PrimaryKeyAttribute}" IN (${subquery})`;
1704
1679
  }
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 ');
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>;