@prisma-next/target-postgres 0.3.0-dev.53 → 0.3.0-dev.55
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/dist/control.d.mts +1 -1
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +515 -161
- package/dist/control.mjs.map +1 -1
- package/package.json +14 -14
- package/src/core/migrations/planner-reconciliation.ts +602 -0
- package/src/core/migrations/planner.ts +187 -224
- package/src/core/migrations/runner.ts +4 -16
package/dist/control.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as postgresTargetDescriptorMeta } from "./descriptor-meta-DxB8oZzB.mjs";
|
|
2
2
|
import { SQL_CHAR_CODEC_ID, SQL_FLOAT_CODEC_ID, SQL_INT_CODEC_ID, SQL_VARCHAR_CODEC_ID } from "@prisma-next/sql-relational-core/ast";
|
|
3
|
-
import { arraysEqual,
|
|
3
|
+
import { arraysEqual, verifySqlSchema } from "@prisma-next/family-sql/schema-verify";
|
|
4
4
|
import { ifDefined } from "@prisma-next/utils/defined";
|
|
5
5
|
import { bigintJsonReplacer, isTaggedBigInt } from "@prisma-next/contract/types";
|
|
6
6
|
import { createMigrationPlan, extractCodecControlHooks, plannerFailure, plannerSuccess, runnerFailure, runnerSuccess } from "@prisma-next/family-sql/control";
|
|
@@ -8,31 +8,6 @@ import { readMarker } from "@prisma-next/family-sql/verify";
|
|
|
8
8
|
import { SqlQueryError } from "@prisma-next/sql-errors";
|
|
9
9
|
import { ok, okVoid } from "@prisma-next/utils/result";
|
|
10
10
|
|
|
11
|
-
//#region ../../6-adapters/postgres/dist/codec-ids-Bsm9c7ns.mjs
|
|
12
|
-
const PG_TEXT_CODEC_ID = "pg/text@1";
|
|
13
|
-
const PG_ENUM_CODEC_ID = "pg/enum@1";
|
|
14
|
-
const PG_CHAR_CODEC_ID = "pg/char@1";
|
|
15
|
-
const PG_VARCHAR_CODEC_ID = "pg/varchar@1";
|
|
16
|
-
const PG_INT_CODEC_ID = "pg/int@1";
|
|
17
|
-
const PG_INT2_CODEC_ID = "pg/int2@1";
|
|
18
|
-
const PG_INT4_CODEC_ID = "pg/int4@1";
|
|
19
|
-
const PG_INT8_CODEC_ID = "pg/int8@1";
|
|
20
|
-
const PG_FLOAT_CODEC_ID = "pg/float@1";
|
|
21
|
-
const PG_FLOAT4_CODEC_ID = "pg/float4@1";
|
|
22
|
-
const PG_FLOAT8_CODEC_ID = "pg/float8@1";
|
|
23
|
-
const PG_NUMERIC_CODEC_ID = "pg/numeric@1";
|
|
24
|
-
const PG_BOOL_CODEC_ID = "pg/bool@1";
|
|
25
|
-
const PG_BIT_CODEC_ID = "pg/bit@1";
|
|
26
|
-
const PG_VARBIT_CODEC_ID = "pg/varbit@1";
|
|
27
|
-
const PG_TIMESTAMP_CODEC_ID = "pg/timestamp@1";
|
|
28
|
-
const PG_TIMESTAMPTZ_CODEC_ID = "pg/timestamptz@1";
|
|
29
|
-
const PG_TIME_CODEC_ID = "pg/time@1";
|
|
30
|
-
const PG_TIMETZ_CODEC_ID = "pg/timetz@1";
|
|
31
|
-
const PG_INTERVAL_CODEC_ID = "pg/interval@1";
|
|
32
|
-
const PG_JSON_CODEC_ID = "pg/json@1";
|
|
33
|
-
const PG_JSONB_CODEC_ID = "pg/jsonb@1";
|
|
34
|
-
|
|
35
|
-
//#endregion
|
|
36
11
|
//#region ../../6-adapters/postgres/dist/sql-utils-CSfAGEwF.mjs
|
|
37
12
|
/**
|
|
38
13
|
* Shared SQL utility functions for the Postgres adapter.
|
|
@@ -108,6 +83,31 @@ function validateEnumValueLength(value, enumTypeName) {
|
|
|
108
83
|
if (value.length > MAX_IDENTIFIER_LENGTH$1) throw new SqlEscapeError(`Enum value "${value.slice(0, 20)}..." for type "${enumTypeName}" exceeds PostgreSQL's ${MAX_IDENTIFIER_LENGTH$1}-character label limit`, value, "literal");
|
|
109
84
|
}
|
|
110
85
|
|
|
86
|
+
//#endregion
|
|
87
|
+
//#region ../../6-adapters/postgres/dist/codec-ids-Bsm9c7ns.mjs
|
|
88
|
+
const PG_TEXT_CODEC_ID = "pg/text@1";
|
|
89
|
+
const PG_ENUM_CODEC_ID = "pg/enum@1";
|
|
90
|
+
const PG_CHAR_CODEC_ID = "pg/char@1";
|
|
91
|
+
const PG_VARCHAR_CODEC_ID = "pg/varchar@1";
|
|
92
|
+
const PG_INT_CODEC_ID = "pg/int@1";
|
|
93
|
+
const PG_INT2_CODEC_ID = "pg/int2@1";
|
|
94
|
+
const PG_INT4_CODEC_ID = "pg/int4@1";
|
|
95
|
+
const PG_INT8_CODEC_ID = "pg/int8@1";
|
|
96
|
+
const PG_FLOAT_CODEC_ID = "pg/float@1";
|
|
97
|
+
const PG_FLOAT4_CODEC_ID = "pg/float4@1";
|
|
98
|
+
const PG_FLOAT8_CODEC_ID = "pg/float8@1";
|
|
99
|
+
const PG_NUMERIC_CODEC_ID = "pg/numeric@1";
|
|
100
|
+
const PG_BOOL_CODEC_ID = "pg/bool@1";
|
|
101
|
+
const PG_BIT_CODEC_ID = "pg/bit@1";
|
|
102
|
+
const PG_VARBIT_CODEC_ID = "pg/varbit@1";
|
|
103
|
+
const PG_TIMESTAMP_CODEC_ID = "pg/timestamp@1";
|
|
104
|
+
const PG_TIMESTAMPTZ_CODEC_ID = "pg/timestamptz@1";
|
|
105
|
+
const PG_TIME_CODEC_ID = "pg/time@1";
|
|
106
|
+
const PG_TIMETZ_CODEC_ID = "pg/timetz@1";
|
|
107
|
+
const PG_INTERVAL_CODEC_ID = "pg/interval@1";
|
|
108
|
+
const PG_JSON_CODEC_ID = "pg/json@1";
|
|
109
|
+
const PG_JSONB_CODEC_ID = "pg/jsonb@1";
|
|
110
|
+
|
|
111
111
|
//#endregion
|
|
112
112
|
//#region ../../6-adapters/postgres/dist/descriptor-meta-ilnFI7bx.mjs
|
|
113
113
|
const ENUM_INTROSPECT_QUERY = `
|
|
@@ -1463,6 +1463,395 @@ var control_default$1 = {
|
|
|
1463
1463
|
}
|
|
1464
1464
|
};
|
|
1465
1465
|
|
|
1466
|
+
//#endregion
|
|
1467
|
+
//#region src/core/migrations/planner-reconciliation.ts
|
|
1468
|
+
function buildReconciliationPlan(options) {
|
|
1469
|
+
const operations = [];
|
|
1470
|
+
const conflicts = [];
|
|
1471
|
+
const { mode } = options;
|
|
1472
|
+
const seenOperationIds = /* @__PURE__ */ new Set();
|
|
1473
|
+
for (const issue of sortSchemaIssues(options.issues)) {
|
|
1474
|
+
if (isAdditiveIssue(issue)) continue;
|
|
1475
|
+
const operation = buildReconciliationOperationFromIssue({
|
|
1476
|
+
issue,
|
|
1477
|
+
contract: options.contract,
|
|
1478
|
+
schemaName: options.schemaName,
|
|
1479
|
+
mode
|
|
1480
|
+
});
|
|
1481
|
+
if (operation) {
|
|
1482
|
+
if (!seenOperationIds.has(operation.id)) {
|
|
1483
|
+
seenOperationIds.add(operation.id);
|
|
1484
|
+
if (options.policy.allowedOperationClasses.includes(operation.operationClass)) operations.push(operation);
|
|
1485
|
+
else {
|
|
1486
|
+
const conflict = convertIssueToConflict(issue);
|
|
1487
|
+
if (conflict) conflicts.push(conflict);
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
} else {
|
|
1491
|
+
const conflict = convertIssueToConflict(issue);
|
|
1492
|
+
if (conflict) conflicts.push(conflict);
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
return {
|
|
1496
|
+
operations,
|
|
1497
|
+
conflicts: conflicts.sort(conflictComparator)
|
|
1498
|
+
};
|
|
1499
|
+
}
|
|
1500
|
+
function isAdditiveIssue(issue) {
|
|
1501
|
+
switch (issue.kind) {
|
|
1502
|
+
case "type_missing":
|
|
1503
|
+
case "type_values_mismatch":
|
|
1504
|
+
case "missing_table":
|
|
1505
|
+
case "missing_column":
|
|
1506
|
+
case "extension_missing": return true;
|
|
1507
|
+
case "primary_key_mismatch": return issue.actual === void 0;
|
|
1508
|
+
case "unique_constraint_mismatch":
|
|
1509
|
+
case "index_mismatch":
|
|
1510
|
+
case "foreign_key_mismatch": return issue.indexOrConstraint === void 0;
|
|
1511
|
+
default: return false;
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
function buildReconciliationOperationFromIssue(options) {
|
|
1515
|
+
const { issue, contract, schemaName, mode } = options;
|
|
1516
|
+
switch (issue.kind) {
|
|
1517
|
+
case "extra_table":
|
|
1518
|
+
if (!mode.allowDestructive || !issue.table) return null;
|
|
1519
|
+
return buildDropTableOperation(schemaName, issue.table);
|
|
1520
|
+
case "extra_column":
|
|
1521
|
+
if (!mode.allowDestructive || !issue.table || !issue.column) return null;
|
|
1522
|
+
return buildDropColumnOperation(schemaName, issue.table, issue.column);
|
|
1523
|
+
case "extra_index":
|
|
1524
|
+
if (!mode.allowDestructive || !issue.table || !issue.indexOrConstraint) return null;
|
|
1525
|
+
return buildDropIndexOperation(schemaName, issue.table, issue.indexOrConstraint);
|
|
1526
|
+
case "extra_foreign_key":
|
|
1527
|
+
case "extra_unique_constraint": {
|
|
1528
|
+
if (!mode.allowDestructive || !issue.table || !issue.indexOrConstraint) return null;
|
|
1529
|
+
const constraintKind = issue.kind === "extra_foreign_key" ? "foreignKey" : "unique";
|
|
1530
|
+
return buildDropConstraintOperation(schemaName, issue.table, issue.indexOrConstraint, constraintKind);
|
|
1531
|
+
}
|
|
1532
|
+
case "extra_primary_key": {
|
|
1533
|
+
if (!mode.allowDestructive || !issue.table) return null;
|
|
1534
|
+
const constraintName = issue.indexOrConstraint ?? `${issue.table}_pkey`;
|
|
1535
|
+
return buildDropConstraintOperation(schemaName, issue.table, constraintName, "primaryKey");
|
|
1536
|
+
}
|
|
1537
|
+
case "nullability_mismatch":
|
|
1538
|
+
if (!issue.table || !issue.column) return null;
|
|
1539
|
+
if (issue.expected === "true") return mode.allowWidening ? buildDropNotNullOperation(schemaName, issue.table, issue.column) : null;
|
|
1540
|
+
return mode.allowDestructive ? buildSetNotNullOperation(schemaName, issue.table, issue.column) : null;
|
|
1541
|
+
case "type_mismatch": {
|
|
1542
|
+
if (!mode.allowDestructive || !issue.table || !issue.column) return null;
|
|
1543
|
+
const contractColumn = getContractColumn(contract, issue.table, issue.column);
|
|
1544
|
+
if (!contractColumn) return null;
|
|
1545
|
+
return buildAlterColumnTypeOperation(schemaName, issue.table, issue.column, contractColumn);
|
|
1546
|
+
}
|
|
1547
|
+
default: return null;
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
function getContractColumn(contract, tableName, columnName) {
|
|
1551
|
+
const table = contract.storage.tables[tableName];
|
|
1552
|
+
if (!table) return null;
|
|
1553
|
+
return table.columns[columnName] ?? null;
|
|
1554
|
+
}
|
|
1555
|
+
function buildDropTableOperation(schemaName, tableName) {
|
|
1556
|
+
return {
|
|
1557
|
+
id: `dropTable.${tableName}`,
|
|
1558
|
+
label: `Drop table ${tableName}`,
|
|
1559
|
+
summary: `Drops extra table ${tableName}`,
|
|
1560
|
+
operationClass: "destructive",
|
|
1561
|
+
target: {
|
|
1562
|
+
id: "postgres",
|
|
1563
|
+
details: buildTargetDetails("table", tableName, schemaName)
|
|
1564
|
+
},
|
|
1565
|
+
precheck: [{
|
|
1566
|
+
description: `ensure table "${tableName}" exists`,
|
|
1567
|
+
sql: `SELECT to_regclass(${toRegclassLiteral(schemaName, tableName)}) IS NOT NULL`
|
|
1568
|
+
}],
|
|
1569
|
+
execute: [{
|
|
1570
|
+
description: `drop table "${tableName}"`,
|
|
1571
|
+
sql: `DROP TABLE ${qualifyTableName(schemaName, tableName)}`
|
|
1572
|
+
}],
|
|
1573
|
+
postcheck: [{
|
|
1574
|
+
description: `verify table "${tableName}" is removed`,
|
|
1575
|
+
sql: `SELECT to_regclass(${toRegclassLiteral(schemaName, tableName)}) IS NULL`
|
|
1576
|
+
}]
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1579
|
+
function buildDropColumnOperation(schemaName, tableName, columnName) {
|
|
1580
|
+
return {
|
|
1581
|
+
id: `dropColumn.${tableName}.${columnName}`,
|
|
1582
|
+
label: `Drop column ${columnName} from ${tableName}`,
|
|
1583
|
+
summary: `Drops extra column ${columnName} from table ${tableName}`,
|
|
1584
|
+
operationClass: "destructive",
|
|
1585
|
+
target: {
|
|
1586
|
+
id: "postgres",
|
|
1587
|
+
details: buildTargetDetails("column", columnName, schemaName, tableName)
|
|
1588
|
+
},
|
|
1589
|
+
precheck: [{
|
|
1590
|
+
description: `ensure column "${columnName}" exists`,
|
|
1591
|
+
sql: columnExistsCheck({
|
|
1592
|
+
schema: schemaName,
|
|
1593
|
+
table: tableName,
|
|
1594
|
+
column: columnName
|
|
1595
|
+
})
|
|
1596
|
+
}],
|
|
1597
|
+
execute: [{
|
|
1598
|
+
description: `drop column "${columnName}"`,
|
|
1599
|
+
sql: `ALTER TABLE ${qualifyTableName(schemaName, tableName)} DROP COLUMN ${quoteIdentifier(columnName)}`
|
|
1600
|
+
}],
|
|
1601
|
+
postcheck: [{
|
|
1602
|
+
description: `verify column "${columnName}" is removed`,
|
|
1603
|
+
sql: columnExistsCheck({
|
|
1604
|
+
schema: schemaName,
|
|
1605
|
+
table: tableName,
|
|
1606
|
+
column: columnName,
|
|
1607
|
+
exists: false
|
|
1608
|
+
})
|
|
1609
|
+
}]
|
|
1610
|
+
};
|
|
1611
|
+
}
|
|
1612
|
+
function buildDropIndexOperation(schemaName, tableName, indexName) {
|
|
1613
|
+
return {
|
|
1614
|
+
id: `dropIndex.${tableName}.${indexName}`,
|
|
1615
|
+
label: `Drop index ${indexName} on ${tableName}`,
|
|
1616
|
+
summary: `Drops extra index ${indexName} on table ${tableName}`,
|
|
1617
|
+
operationClass: "destructive",
|
|
1618
|
+
target: {
|
|
1619
|
+
id: "postgres",
|
|
1620
|
+
details: buildTargetDetails("index", indexName, schemaName, tableName)
|
|
1621
|
+
},
|
|
1622
|
+
precheck: [{
|
|
1623
|
+
description: `ensure index "${indexName}" exists`,
|
|
1624
|
+
sql: `SELECT to_regclass(${toRegclassLiteral(schemaName, indexName)}) IS NOT NULL`
|
|
1625
|
+
}],
|
|
1626
|
+
execute: [{
|
|
1627
|
+
description: `drop index "${indexName}"`,
|
|
1628
|
+
sql: `DROP INDEX ${qualifyTableName(schemaName, indexName)}`
|
|
1629
|
+
}],
|
|
1630
|
+
postcheck: [{
|
|
1631
|
+
description: `verify index "${indexName}" is removed`,
|
|
1632
|
+
sql: `SELECT to_regclass(${toRegclassLiteral(schemaName, indexName)}) IS NULL`
|
|
1633
|
+
}]
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
function buildDropConstraintOperation(schemaName, tableName, constraintName, constraintKind) {
|
|
1637
|
+
return {
|
|
1638
|
+
id: `dropConstraint.${tableName}.${constraintName}`,
|
|
1639
|
+
label: `Drop constraint ${constraintName} on ${tableName}`,
|
|
1640
|
+
summary: `Drops extra constraint ${constraintName} on table ${tableName}`,
|
|
1641
|
+
operationClass: "destructive",
|
|
1642
|
+
target: {
|
|
1643
|
+
id: "postgres",
|
|
1644
|
+
details: buildTargetDetails(constraintKind, constraintName, schemaName, tableName)
|
|
1645
|
+
},
|
|
1646
|
+
precheck: [{
|
|
1647
|
+
description: `ensure constraint "${constraintName}" exists`,
|
|
1648
|
+
sql: constraintExistsCheck({
|
|
1649
|
+
constraintName,
|
|
1650
|
+
schema: schemaName
|
|
1651
|
+
})
|
|
1652
|
+
}],
|
|
1653
|
+
execute: [{
|
|
1654
|
+
description: `drop constraint "${constraintName}"`,
|
|
1655
|
+
sql: `ALTER TABLE ${qualifyTableName(schemaName, tableName)}
|
|
1656
|
+
DROP CONSTRAINT ${quoteIdentifier(constraintName)}`
|
|
1657
|
+
}],
|
|
1658
|
+
postcheck: [{
|
|
1659
|
+
description: `verify constraint "${constraintName}" is removed`,
|
|
1660
|
+
sql: constraintExistsCheck({
|
|
1661
|
+
constraintName,
|
|
1662
|
+
schema: schemaName,
|
|
1663
|
+
exists: false
|
|
1664
|
+
})
|
|
1665
|
+
}]
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
function buildDropNotNullOperation(schemaName, tableName, columnName) {
|
|
1669
|
+
return {
|
|
1670
|
+
id: `alterNullability.${tableName}.${columnName}`,
|
|
1671
|
+
label: `Relax nullability for ${columnName} on ${tableName}`,
|
|
1672
|
+
summary: `Drops NOT NULL constraint for ${columnName} on table ${tableName}`,
|
|
1673
|
+
operationClass: "widening",
|
|
1674
|
+
target: {
|
|
1675
|
+
id: "postgres",
|
|
1676
|
+
details: buildTargetDetails("column", columnName, schemaName, tableName)
|
|
1677
|
+
},
|
|
1678
|
+
precheck: [{
|
|
1679
|
+
description: `ensure column "${columnName}" exists`,
|
|
1680
|
+
sql: columnExistsCheck({
|
|
1681
|
+
schema: schemaName,
|
|
1682
|
+
table: tableName,
|
|
1683
|
+
column: columnName
|
|
1684
|
+
})
|
|
1685
|
+
}],
|
|
1686
|
+
execute: [{
|
|
1687
|
+
description: `drop NOT NULL from "${columnName}"`,
|
|
1688
|
+
sql: `ALTER TABLE ${qualifyTableName(schemaName, tableName)}
|
|
1689
|
+
ALTER COLUMN ${quoteIdentifier(columnName)} DROP NOT NULL`
|
|
1690
|
+
}],
|
|
1691
|
+
postcheck: [{
|
|
1692
|
+
description: `verify "${columnName}" is nullable`,
|
|
1693
|
+
sql: columnNullabilityCheck({
|
|
1694
|
+
schema: schemaName,
|
|
1695
|
+
table: tableName,
|
|
1696
|
+
column: columnName,
|
|
1697
|
+
nullable: true
|
|
1698
|
+
})
|
|
1699
|
+
}]
|
|
1700
|
+
};
|
|
1701
|
+
}
|
|
1702
|
+
function buildSetNotNullOperation(schemaName, tableName, columnName) {
|
|
1703
|
+
const qualified = qualifyTableName(schemaName, tableName);
|
|
1704
|
+
return {
|
|
1705
|
+
id: `alterNullability.${tableName}.${columnName}`,
|
|
1706
|
+
label: `Enforce NOT NULL for ${columnName} on ${tableName}`,
|
|
1707
|
+
summary: `Sets NOT NULL on ${columnName} for table ${tableName}`,
|
|
1708
|
+
operationClass: "destructive",
|
|
1709
|
+
target: {
|
|
1710
|
+
id: "postgres",
|
|
1711
|
+
details: buildTargetDetails("column", columnName, schemaName, tableName)
|
|
1712
|
+
},
|
|
1713
|
+
precheck: [{
|
|
1714
|
+
description: `ensure column "${columnName}" exists`,
|
|
1715
|
+
sql: columnExistsCheck({
|
|
1716
|
+
schema: schemaName,
|
|
1717
|
+
table: tableName,
|
|
1718
|
+
column: columnName
|
|
1719
|
+
})
|
|
1720
|
+
}, {
|
|
1721
|
+
description: `ensure "${columnName}" has no NULL values`,
|
|
1722
|
+
sql: `SELECT NOT EXISTS (
|
|
1723
|
+
SELECT 1 FROM ${qualified}
|
|
1724
|
+
WHERE ${quoteIdentifier(columnName)} IS NULL
|
|
1725
|
+
LIMIT 1
|
|
1726
|
+
)`
|
|
1727
|
+
}],
|
|
1728
|
+
execute: [{
|
|
1729
|
+
description: `set NOT NULL on "${columnName}"`,
|
|
1730
|
+
sql: `ALTER TABLE ${qualified}
|
|
1731
|
+
ALTER COLUMN ${quoteIdentifier(columnName)} SET NOT NULL`
|
|
1732
|
+
}],
|
|
1733
|
+
postcheck: [{
|
|
1734
|
+
description: `verify "${columnName}" is NOT NULL`,
|
|
1735
|
+
sql: columnNullabilityCheck({
|
|
1736
|
+
schema: schemaName,
|
|
1737
|
+
table: tableName,
|
|
1738
|
+
column: columnName,
|
|
1739
|
+
nullable: false
|
|
1740
|
+
})
|
|
1741
|
+
}]
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
function buildAlterColumnTypeOperation(schemaName, tableName, columnName, column) {
|
|
1745
|
+
const qualified = qualifyTableName(schemaName, tableName);
|
|
1746
|
+
const expectedType = buildColumnTypeSql(column);
|
|
1747
|
+
return {
|
|
1748
|
+
id: `alterType.${tableName}.${columnName}`,
|
|
1749
|
+
label: `Alter type for ${columnName} on ${tableName}`,
|
|
1750
|
+
summary: `Changes type of ${columnName} to ${expectedType}`,
|
|
1751
|
+
operationClass: "destructive",
|
|
1752
|
+
target: {
|
|
1753
|
+
id: "postgres",
|
|
1754
|
+
details: buildTargetDetails("column", columnName, schemaName, tableName)
|
|
1755
|
+
},
|
|
1756
|
+
meta: {
|
|
1757
|
+
warning: "TABLE_REWRITE",
|
|
1758
|
+
detail: "ALTER COLUMN TYPE requires a full table rewrite and acquires an ACCESS EXCLUSIVE lock. On large tables, this can cause significant downtime."
|
|
1759
|
+
},
|
|
1760
|
+
precheck: [{
|
|
1761
|
+
description: `ensure column "${columnName}" exists`,
|
|
1762
|
+
sql: columnExistsCheck({
|
|
1763
|
+
schema: schemaName,
|
|
1764
|
+
table: tableName,
|
|
1765
|
+
column: columnName
|
|
1766
|
+
})
|
|
1767
|
+
}],
|
|
1768
|
+
execute: [{
|
|
1769
|
+
description: `alter type of "${columnName}"`,
|
|
1770
|
+
sql: `ALTER TABLE ${qualified}
|
|
1771
|
+
ALTER COLUMN ${quoteIdentifier(columnName)}
|
|
1772
|
+
TYPE ${expectedType}
|
|
1773
|
+
USING ${quoteIdentifier(columnName)}::${expectedType}`
|
|
1774
|
+
}],
|
|
1775
|
+
postcheck: [{
|
|
1776
|
+
description: `verify column "${columnName}" exists after type change`,
|
|
1777
|
+
sql: columnExistsCheck({
|
|
1778
|
+
schema: schemaName,
|
|
1779
|
+
table: tableName,
|
|
1780
|
+
column: columnName
|
|
1781
|
+
})
|
|
1782
|
+
}]
|
|
1783
|
+
};
|
|
1784
|
+
}
|
|
1785
|
+
function convertIssueToConflict(issue) {
|
|
1786
|
+
switch (issue.kind) {
|
|
1787
|
+
case "type_mismatch": return buildConflict("typeMismatch", issue);
|
|
1788
|
+
case "nullability_mismatch": return buildConflict("nullabilityConflict", issue);
|
|
1789
|
+
case "default_missing":
|
|
1790
|
+
case "default_mismatch":
|
|
1791
|
+
case "extra_table":
|
|
1792
|
+
case "extra_column":
|
|
1793
|
+
case "extra_primary_key":
|
|
1794
|
+
case "extra_foreign_key":
|
|
1795
|
+
case "extra_unique_constraint":
|
|
1796
|
+
case "extra_index": return buildConflict("missingButNonAdditive", issue);
|
|
1797
|
+
case "primary_key_mismatch":
|
|
1798
|
+
case "unique_constraint_mismatch":
|
|
1799
|
+
case "index_mismatch": return buildConflict("indexIncompatible", issue);
|
|
1800
|
+
case "foreign_key_mismatch": return buildConflict("foreignKeyConflict", issue);
|
|
1801
|
+
default: return null;
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
function buildConflict(kind, issue) {
|
|
1805
|
+
const location = buildConflictLocation(issue);
|
|
1806
|
+
const meta = issue.expected || issue.actual ? Object.freeze({
|
|
1807
|
+
...ifDefined("expected", issue.expected),
|
|
1808
|
+
...ifDefined("actual", issue.actual)
|
|
1809
|
+
}) : void 0;
|
|
1810
|
+
return {
|
|
1811
|
+
kind,
|
|
1812
|
+
summary: issue.message,
|
|
1813
|
+
...ifDefined("location", location),
|
|
1814
|
+
...ifDefined("meta", meta)
|
|
1815
|
+
};
|
|
1816
|
+
}
|
|
1817
|
+
function sortSchemaIssues(issues) {
|
|
1818
|
+
return [...issues].sort((a, b) => {
|
|
1819
|
+
const kindCompare = a.kind.localeCompare(b.kind);
|
|
1820
|
+
if (kindCompare !== 0) return kindCompare;
|
|
1821
|
+
const tableCompare = compareStrings(a.table, b.table);
|
|
1822
|
+
if (tableCompare !== 0) return tableCompare;
|
|
1823
|
+
const columnCompare = compareStrings(a.column, b.column);
|
|
1824
|
+
if (columnCompare !== 0) return columnCompare;
|
|
1825
|
+
return compareStrings(a.indexOrConstraint, b.indexOrConstraint);
|
|
1826
|
+
});
|
|
1827
|
+
}
|
|
1828
|
+
function buildConflictLocation(issue) {
|
|
1829
|
+
const location = {
|
|
1830
|
+
...ifDefined("table", issue.table),
|
|
1831
|
+
...ifDefined("column", issue.column),
|
|
1832
|
+
...ifDefined("constraint", issue.indexOrConstraint)
|
|
1833
|
+
};
|
|
1834
|
+
return Object.keys(location).length > 0 ? location : void 0;
|
|
1835
|
+
}
|
|
1836
|
+
function conflictComparator(a, b) {
|
|
1837
|
+
if (a.kind !== b.kind) return a.kind < b.kind ? -1 : 1;
|
|
1838
|
+
const aLocation = a.location ?? {};
|
|
1839
|
+
const bLocation = b.location ?? {};
|
|
1840
|
+
const tableCompare = compareStrings(aLocation.table, bLocation.table);
|
|
1841
|
+
if (tableCompare !== 0) return tableCompare;
|
|
1842
|
+
const columnCompare = compareStrings(aLocation.column, bLocation.column);
|
|
1843
|
+
if (columnCompare !== 0) return columnCompare;
|
|
1844
|
+
const constraintCompare = compareStrings(aLocation.constraint, bLocation.constraint);
|
|
1845
|
+
if (constraintCompare !== 0) return constraintCompare;
|
|
1846
|
+
return compareStrings(a.summary, b.summary);
|
|
1847
|
+
}
|
|
1848
|
+
function compareStrings(a, b) {
|
|
1849
|
+
if (a === b) return 0;
|
|
1850
|
+
if (a === void 0) return -1;
|
|
1851
|
+
if (b === void 0) return 1;
|
|
1852
|
+
return a < b ? -1 : 1;
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1466
1855
|
//#endregion
|
|
1467
1856
|
//#region src/core/migrations/planner.ts
|
|
1468
1857
|
const DEFAULT_PLANNER_CONFIG = { defaultSchema: "public" };
|
|
@@ -1480,13 +1869,23 @@ var PostgresMigrationPlanner = class {
|
|
|
1480
1869
|
const schemaName = options.schemaName ?? this.config.defaultSchema;
|
|
1481
1870
|
const policyResult = this.ensureAdditivePolicy(options.policy);
|
|
1482
1871
|
if (policyResult) return policyResult;
|
|
1483
|
-
const
|
|
1484
|
-
|
|
1872
|
+
const planningMode = this.resolvePlanningMode(options.policy);
|
|
1873
|
+
const schemaIssues = this.collectSchemaIssues(options, planningMode.includeExtraObjects);
|
|
1485
1874
|
const codecHooks = extractCodecControlHooks(options.frameworkComponents);
|
|
1486
1875
|
const operations = [];
|
|
1876
|
+
const reconciliationPlan = buildReconciliationPlan({
|
|
1877
|
+
contract: options.contract,
|
|
1878
|
+
issues: schemaIssues,
|
|
1879
|
+
schemaName,
|
|
1880
|
+
mode: planningMode,
|
|
1881
|
+
policy: options.policy
|
|
1882
|
+
});
|
|
1883
|
+
if (reconciliationPlan.conflicts.length > 0) return plannerFailure(reconciliationPlan.conflicts);
|
|
1487
1884
|
const storageTypePlan = this.buildStorageTypeOperations(options, schemaName, codecHooks);
|
|
1488
1885
|
if (storageTypePlan.conflicts.length > 0) return plannerFailure(storageTypePlan.conflicts);
|
|
1489
|
-
|
|
1886
|
+
const sortedTables = sortedEntries(options.contract.storage.tables);
|
|
1887
|
+
const schemaLookups = buildSchemaLookupMap(options.schema);
|
|
1888
|
+
operations.push(...this.buildDatabaseDependencyOperations(options), ...storageTypePlan.operations, ...reconciliationPlan.operations, ...this.buildTableOperations(sortedTables, options.schema, schemaName), ...this.buildColumnOperations(sortedTables, options.schema, schemaName), ...this.buildPrimaryKeyOperations(sortedTables, options.schema, schemaName), ...this.buildUniqueOperations(sortedTables, schemaLookups, schemaName), ...this.buildIndexOperations(sortedTables, schemaLookups, schemaName), ...this.buildFkBackingIndexOperations(sortedTables, schemaLookups, schemaName), ...this.buildForeignKeyOperations(sortedTables, schemaLookups, schemaName));
|
|
1490
1889
|
return plannerSuccess(createMigrationPlan({
|
|
1491
1890
|
targetId: "postgres",
|
|
1492
1891
|
origin: null,
|
|
@@ -1500,8 +1899,8 @@ var PostgresMigrationPlanner = class {
|
|
|
1500
1899
|
ensureAdditivePolicy(policy) {
|
|
1501
1900
|
if (!policy.allowedOperationClasses.includes("additive")) return plannerFailure([{
|
|
1502
1901
|
kind: "unsupportedOperation",
|
|
1503
|
-
summary: "
|
|
1504
|
-
why: "The
|
|
1902
|
+
summary: "Migration planner requires additive operations be allowed",
|
|
1903
|
+
why: "The planner requires the \"additive\" operation class to be allowed in the policy."
|
|
1505
1904
|
}]);
|
|
1506
1905
|
return null;
|
|
1507
1906
|
}
|
|
@@ -1576,7 +1975,7 @@ var PostgresMigrationPlanner = class {
|
|
|
1576
1975
|
}
|
|
1577
1976
|
buildTableOperations(tables, schema, schemaName) {
|
|
1578
1977
|
const operations = [];
|
|
1579
|
-
for (const [tableName, table] of
|
|
1978
|
+
for (const [tableName, table] of tables) {
|
|
1580
1979
|
if (schema.tables[tableName]) continue;
|
|
1581
1980
|
const qualified = qualifyTableName(schemaName, tableName);
|
|
1582
1981
|
operations.push({
|
|
@@ -1606,7 +2005,7 @@ var PostgresMigrationPlanner = class {
|
|
|
1606
2005
|
}
|
|
1607
2006
|
buildColumnOperations(tables, schema, schemaName) {
|
|
1608
2007
|
const operations = [];
|
|
1609
|
-
for (const [tableName, table] of
|
|
2008
|
+
for (const [tableName, table] of tables) {
|
|
1610
2009
|
const schemaTable = schema.tables[tableName];
|
|
1611
2010
|
if (!schemaTable) continue;
|
|
1612
2011
|
for (const [columnName, column] of sortedEntries(table.columns)) {
|
|
@@ -1646,10 +2045,11 @@ var PostgresMigrationPlanner = class {
|
|
|
1646
2045
|
})
|
|
1647
2046
|
}, ...notNull ? [{
|
|
1648
2047
|
description: `verify column "${columnName}" is NOT NULL`,
|
|
1649
|
-
sql:
|
|
2048
|
+
sql: columnNullabilityCheck({
|
|
1650
2049
|
schema,
|
|
1651
2050
|
table: tableName,
|
|
1652
|
-
column: columnName
|
|
2051
|
+
column: columnName,
|
|
2052
|
+
nullable: false
|
|
1653
2053
|
})
|
|
1654
2054
|
}] : []];
|
|
1655
2055
|
return {
|
|
@@ -1668,7 +2068,7 @@ var PostgresMigrationPlanner = class {
|
|
|
1668
2068
|
}
|
|
1669
2069
|
buildPrimaryKeyOperations(tables, schema, schemaName) {
|
|
1670
2070
|
const operations = [];
|
|
1671
|
-
for (const [tableName, table] of
|
|
2071
|
+
for (const [tableName, table] of tables) {
|
|
1672
2072
|
if (!table.primaryKey) continue;
|
|
1673
2073
|
const schemaTable = schema.tables[tableName];
|
|
1674
2074
|
if (!schemaTable || schemaTable.primaryKey) continue;
|
|
@@ -1700,12 +2100,12 @@ PRIMARY KEY (${table.primaryKey.columns.map(quoteIdentifier).join(", ")})`
|
|
|
1700
2100
|
}
|
|
1701
2101
|
return operations;
|
|
1702
2102
|
}
|
|
1703
|
-
buildUniqueOperations(tables,
|
|
2103
|
+
buildUniqueOperations(tables, schemaLookups, schemaName) {
|
|
1704
2104
|
const operations = [];
|
|
1705
|
-
for (const [tableName, table] of
|
|
1706
|
-
const
|
|
2105
|
+
for (const [tableName, table] of tables) {
|
|
2106
|
+
const lookup = schemaLookups.get(tableName);
|
|
1707
2107
|
for (const unique of table.uniques) {
|
|
1708
|
-
if (
|
|
2108
|
+
if (lookup && hasUniqueConstraint(lookup, unique.columns)) continue;
|
|
1709
2109
|
const constraintName = unique.name ?? `${tableName}_${unique.columns.join("_")}_key`;
|
|
1710
2110
|
operations.push({
|
|
1711
2111
|
id: `unique.${tableName}.${constraintName}`,
|
|
@@ -1742,12 +2142,12 @@ UNIQUE (${unique.columns.map(quoteIdentifier).join(", ")})`
|
|
|
1742
2142
|
}
|
|
1743
2143
|
return operations;
|
|
1744
2144
|
}
|
|
1745
|
-
buildIndexOperations(tables,
|
|
2145
|
+
buildIndexOperations(tables, schemaLookups, schemaName) {
|
|
1746
2146
|
const operations = [];
|
|
1747
|
-
for (const [tableName, table] of
|
|
1748
|
-
const
|
|
2147
|
+
for (const [tableName, table] of tables) {
|
|
2148
|
+
const lookup = schemaLookups.get(tableName);
|
|
1749
2149
|
for (const index of table.indexes) {
|
|
1750
|
-
if (
|
|
2150
|
+
if (lookup && hasIndex(lookup, index.columns)) continue;
|
|
1751
2151
|
const indexName = index.name ?? `${tableName}_${index.columns.join("_")}_idx`;
|
|
1752
2152
|
operations.push({
|
|
1753
2153
|
id: `index.${tableName}.${indexName}`,
|
|
@@ -1779,15 +2179,15 @@ UNIQUE (${unique.columns.map(quoteIdentifier).join(", ")})`
|
|
|
1779
2179
|
* Generates FK-backing index operations for FKs with `index: true`,
|
|
1780
2180
|
* but only when no matching user-declared index exists in `contractTable.indexes`.
|
|
1781
2181
|
*/
|
|
1782
|
-
buildFkBackingIndexOperations(tables,
|
|
2182
|
+
buildFkBackingIndexOperations(tables, schemaLookups, schemaName) {
|
|
1783
2183
|
const operations = [];
|
|
1784
|
-
for (const [tableName, table] of
|
|
1785
|
-
const
|
|
2184
|
+
for (const [tableName, table] of tables) {
|
|
2185
|
+
const lookup = schemaLookups.get(tableName);
|
|
1786
2186
|
const declaredIndexColumns = new Set(table.indexes.map((idx) => idx.columns.join(",")));
|
|
1787
2187
|
for (const fk of table.foreignKeys) {
|
|
1788
2188
|
if (fk.index === false) continue;
|
|
1789
2189
|
if (declaredIndexColumns.has(fk.columns.join(","))) continue;
|
|
1790
|
-
if (
|
|
2190
|
+
if (lookup && hasIndex(lookup, fk.columns)) continue;
|
|
1791
2191
|
const indexName = `${tableName}_${fk.columns.join("_")}_idx`;
|
|
1792
2192
|
operations.push({
|
|
1793
2193
|
id: `index.${tableName}.${indexName}`,
|
|
@@ -1815,13 +2215,13 @@ UNIQUE (${unique.columns.map(quoteIdentifier).join(", ")})`
|
|
|
1815
2215
|
}
|
|
1816
2216
|
return operations;
|
|
1817
2217
|
}
|
|
1818
|
-
buildForeignKeyOperations(tables,
|
|
2218
|
+
buildForeignKeyOperations(tables, schemaLookups, schemaName) {
|
|
1819
2219
|
const operations = [];
|
|
1820
|
-
for (const [tableName, table] of
|
|
1821
|
-
const
|
|
2220
|
+
for (const [tableName, table] of tables) {
|
|
2221
|
+
const lookup = schemaLookups.get(tableName);
|
|
1822
2222
|
for (const foreignKey of table.foreignKeys) {
|
|
1823
2223
|
if (foreignKey.constraint === false) continue;
|
|
1824
|
-
if (
|
|
2224
|
+
if (lookup && hasForeignKey(lookup, foreignKey)) continue;
|
|
1825
2225
|
const fkName = foreignKey.name ?? `${tableName}_${foreignKey.columns.join("_")}_fkey`;
|
|
1826
2226
|
operations.push({
|
|
1827
2227
|
id: `foreignKey.${tableName}.${fkName}`,
|
|
@@ -1857,62 +2257,27 @@ UNIQUE (${unique.columns.map(quoteIdentifier).join(", ")})`
|
|
|
1857
2257
|
return operations;
|
|
1858
2258
|
}
|
|
1859
2259
|
buildTargetDetails(objectType, name, schema, table) {
|
|
2260
|
+
return buildTargetDetails(objectType, name, schema, table);
|
|
2261
|
+
}
|
|
2262
|
+
resolvePlanningMode(policy) {
|
|
2263
|
+
const allowWidening = policy.allowedOperationClasses.includes("widening");
|
|
2264
|
+
const allowDestructive = policy.allowedOperationClasses.includes("destructive");
|
|
1860
2265
|
return {
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
...ifDefined("table", table)
|
|
2266
|
+
includeExtraObjects: allowWidening || allowDestructive,
|
|
2267
|
+
allowWidening,
|
|
2268
|
+
allowDestructive
|
|
1865
2269
|
};
|
|
1866
2270
|
}
|
|
1867
|
-
|
|
1868
|
-
|
|
2271
|
+
collectSchemaIssues(options, strict) {
|
|
2272
|
+
return verifySqlSchema({
|
|
1869
2273
|
contract: options.contract,
|
|
1870
2274
|
schema: options.schema,
|
|
1871
|
-
strict
|
|
2275
|
+
strict,
|
|
1872
2276
|
typeMetadataRegistry: /* @__PURE__ */ new Map(),
|
|
1873
2277
|
frameworkComponents: options.frameworkComponents,
|
|
1874
2278
|
normalizeDefault: parsePostgresDefault,
|
|
1875
2279
|
normalizeNativeType: normalizeSchemaNativeType
|
|
1876
|
-
});
|
|
1877
|
-
const conflicts = this.extractConflicts(verifyResult.schema.issues);
|
|
1878
|
-
if (conflicts.length > 0) return {
|
|
1879
|
-
kind: "conflict",
|
|
1880
|
-
conflicts
|
|
1881
|
-
};
|
|
1882
|
-
return { kind: "ok" };
|
|
1883
|
-
}
|
|
1884
|
-
extractConflicts(issues) {
|
|
1885
|
-
const conflicts = [];
|
|
1886
|
-
for (const issue of issues) {
|
|
1887
|
-
if (isAdditiveIssue(issue)) continue;
|
|
1888
|
-
const conflict = this.convertIssueToConflict(issue);
|
|
1889
|
-
if (conflict) conflicts.push(conflict);
|
|
1890
|
-
}
|
|
1891
|
-
return conflicts.sort(conflictComparator);
|
|
1892
|
-
}
|
|
1893
|
-
convertIssueToConflict(issue) {
|
|
1894
|
-
switch (issue.kind) {
|
|
1895
|
-
case "type_mismatch": return this.buildConflict("typeMismatch", issue);
|
|
1896
|
-
case "nullability_mismatch": return this.buildConflict("nullabilityConflict", issue);
|
|
1897
|
-
case "primary_key_mismatch": return this.buildConflict("indexIncompatible", issue);
|
|
1898
|
-
case "unique_constraint_mismatch": return this.buildConflict("indexIncompatible", issue);
|
|
1899
|
-
case "index_mismatch": return this.buildConflict("indexIncompatible", issue);
|
|
1900
|
-
case "foreign_key_mismatch": return this.buildConflict("foreignKeyConflict", issue);
|
|
1901
|
-
default: return null;
|
|
1902
|
-
}
|
|
1903
|
-
}
|
|
1904
|
-
buildConflict(kind, issue) {
|
|
1905
|
-
const location = buildConflictLocation(issue);
|
|
1906
|
-
const meta = issue.expected || issue.actual ? Object.freeze({
|
|
1907
|
-
...ifDefined("expected", issue.expected),
|
|
1908
|
-
...ifDefined("actual", issue.actual)
|
|
1909
|
-
}) : void 0;
|
|
1910
|
-
return {
|
|
1911
|
-
kind,
|
|
1912
|
-
summary: issue.message,
|
|
1913
|
-
...ifDefined("location", location),
|
|
1914
|
-
...ifDefined("meta", meta)
|
|
1915
|
-
};
|
|
2280
|
+
}).schema.issues;
|
|
1916
2281
|
}
|
|
1917
2282
|
};
|
|
1918
2283
|
function isSqlDependencyProvider(component) {
|
|
@@ -1941,6 +2306,23 @@ function buildCreateTableSql(qualifiedTableName, table) {
|
|
|
1941
2306
|
return `CREATE TABLE ${qualifiedTableName} (\n ${[...columnDefinitions, ...constraintDefinitions].join(",\n ")}\n)`;
|
|
1942
2307
|
}
|
|
1943
2308
|
/**
|
|
2309
|
+
* Pattern for safe PostgreSQL type names.
|
|
2310
|
+
* Allows letters, digits, underscores, spaces (for "double precision", "character varying"),
|
|
2311
|
+
* and trailing [] for array types.
|
|
2312
|
+
*/
|
|
2313
|
+
const SAFE_NATIVE_TYPE_PATTERN = /^[a-zA-Z][a-zA-Z0-9_ ]*(\[\])?$/;
|
|
2314
|
+
function assertSafeNativeType(nativeType) {
|
|
2315
|
+
if (!SAFE_NATIVE_TYPE_PATTERN.test(nativeType)) throw new Error(`Unsafe native type name in contract: "${nativeType}". Native type names must match /^[a-zA-Z][a-zA-Z0-9_ ]*(\\[\\])?\$/`);
|
|
2316
|
+
}
|
|
2317
|
+
/**
|
|
2318
|
+
* Sanity check against accidental SQL injection from malformed contract files.
|
|
2319
|
+
* Rejects semicolons, SQL comment tokens, and dollar-quoting.
|
|
2320
|
+
* Not a comprehensive security boundary — the contract is developer-authored.
|
|
2321
|
+
*/
|
|
2322
|
+
function assertSafeDefaultExpression(expression) {
|
|
2323
|
+
if (expression.includes(";") || /--|\/\*|\$\$|\bSELECT\b/i.test(expression)) throw new Error(`Unsafe default expression in contract: "${expression}". Default expressions must not contain semicolons, SQL comment tokens, dollar-quoting, or subqueries.`);
|
|
2324
|
+
}
|
|
2325
|
+
/**
|
|
1944
2326
|
* Builds the column type SQL, handling autoincrement as a special case.
|
|
1945
2327
|
* For autoincrement on int4/int8, we use SERIAL/BIGSERIAL types.
|
|
1946
2328
|
*/
|
|
@@ -1952,6 +2334,7 @@ function buildColumnTypeSql(column) {
|
|
|
1952
2334
|
if (column.nativeType === "int2" || column.nativeType === "smallint") return "SMALLSERIAL";
|
|
1953
2335
|
}
|
|
1954
2336
|
if (column.typeRef) return quoteIdentifier(column.nativeType);
|
|
2337
|
+
assertSafeNativeType(column.nativeType);
|
|
1955
2338
|
return renderParameterizedTypeSql(column) ?? column.nativeType;
|
|
1956
2339
|
}
|
|
1957
2340
|
/**
|
|
@@ -1982,7 +2365,8 @@ function buildColumnDefaultSql(columnDefault, column) {
|
|
|
1982
2365
|
case "literal": return `DEFAULT ${renderDefaultLiteral(columnDefault.value, column)}`;
|
|
1983
2366
|
case "function":
|
|
1984
2367
|
if (columnDefault.expression === "autoincrement()") return "";
|
|
1985
|
-
|
|
2368
|
+
assertSafeDefaultExpression(columnDefault.expression);
|
|
2369
|
+
return `DEFAULT (${columnDefault.expression})`;
|
|
1986
2370
|
case "sequence": return `DEFAULT nextval(${quoteIdentifier(columnDefault.name)}::regclass)`;
|
|
1987
2371
|
}
|
|
1988
2372
|
}
|
|
@@ -2001,6 +2385,14 @@ function renderDefaultLiteral(value, column) {
|
|
|
2001
2385
|
if (isJsonColumn) return `'${escapeLiteral(json)}'::${column.nativeType}`;
|
|
2002
2386
|
return `'${escapeLiteral(json)}'`;
|
|
2003
2387
|
}
|
|
2388
|
+
function buildTargetDetails(objectType, name, schema, table) {
|
|
2389
|
+
return {
|
|
2390
|
+
schema,
|
|
2391
|
+
objectType,
|
|
2392
|
+
name,
|
|
2393
|
+
...ifDefined("table", table)
|
|
2394
|
+
};
|
|
2395
|
+
}
|
|
2004
2396
|
function qualifyTableName(schema, table) {
|
|
2005
2397
|
return `${quoteIdentifier(schema)}.${quoteIdentifier(table)}`;
|
|
2006
2398
|
}
|
|
@@ -2027,14 +2419,15 @@ function columnExistsCheck({ schema, table, column, exists = true }) {
|
|
|
2027
2419
|
AND column_name = '${escapeLiteral(column)}'
|
|
2028
2420
|
)`;
|
|
2029
2421
|
}
|
|
2030
|
-
function
|
|
2422
|
+
function columnNullabilityCheck({ schema, table, column, nullable }) {
|
|
2423
|
+
const expected = nullable ? "YES" : "NO";
|
|
2031
2424
|
return `SELECT EXISTS (
|
|
2032
2425
|
SELECT 1
|
|
2033
2426
|
FROM information_schema.columns
|
|
2034
2427
|
WHERE table_schema = '${escapeLiteral(schema)}'
|
|
2035
2428
|
AND table_name = '${escapeLiteral(table)}'
|
|
2036
2429
|
AND column_name = '${escapeLiteral(column)}'
|
|
2037
|
-
AND is_nullable = '
|
|
2430
|
+
AND is_nullable = '${expected}'
|
|
2038
2431
|
)`;
|
|
2039
2432
|
}
|
|
2040
2433
|
function tableIsEmptyCheck(qualifiedTableName) {
|
|
@@ -2065,61 +2458,29 @@ function tableHasPrimaryKeyCheck(schema, table, exists, constraintName) {
|
|
|
2065
2458
|
${constraintFilter}
|
|
2066
2459
|
)`;
|
|
2067
2460
|
}
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
function hasUniqueConstraint(table, columns) {
|
|
2073
|
-
return isUniqueConstraintSatisfied(table.uniques, table.indexes, columns);
|
|
2074
|
-
}
|
|
2075
|
-
/**
|
|
2076
|
-
* Checks if table has an index satisfied by the given columns.
|
|
2077
|
-
* Uses shared semantic satisfaction predicate from verify-helpers.
|
|
2078
|
-
*/
|
|
2079
|
-
function hasIndex(table, columns) {
|
|
2080
|
-
return isIndexSatisfied(table.indexes, table.uniques, columns);
|
|
2081
|
-
}
|
|
2082
|
-
function hasForeignKey(table, fk) {
|
|
2083
|
-
return table.foreignKeys.some((candidate) => arraysEqual(candidate.columns, fk.columns) && candidate.referencedTable === fk.references.table && arraysEqual(candidate.referencedColumns, fk.references.columns));
|
|
2461
|
+
function buildSchemaLookupMap(schema) {
|
|
2462
|
+
const map = /* @__PURE__ */ new Map();
|
|
2463
|
+
for (const [tableName, table] of Object.entries(schema.tables)) map.set(tableName, buildSchemaTableLookup(table));
|
|
2464
|
+
return map;
|
|
2084
2465
|
}
|
|
2085
|
-
function
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
case "primary_key_mismatch": return issue.actual === void 0;
|
|
2093
|
-
case "unique_constraint_mismatch":
|
|
2094
|
-
case "index_mismatch":
|
|
2095
|
-
case "foreign_key_mismatch": return issue.indexOrConstraint === void 0;
|
|
2096
|
-
default: return false;
|
|
2097
|
-
}
|
|
2466
|
+
function buildSchemaTableLookup(table) {
|
|
2467
|
+
return {
|
|
2468
|
+
uniqueKeys: new Set(table.uniques.map((u) => u.columns.join(","))),
|
|
2469
|
+
indexKeys: new Set(table.indexes.map((i) => i.columns.join(","))),
|
|
2470
|
+
uniqueIndexKeys: new Set(table.indexes.filter((i) => i.unique).map((i) => i.columns.join(","))),
|
|
2471
|
+
fkKeys: new Set(table.foreignKeys.map((fk) => `${fk.columns.join(",")}|${fk.referencedTable}|${fk.referencedColumns.join(",")}`))
|
|
2472
|
+
};
|
|
2098
2473
|
}
|
|
2099
|
-
function
|
|
2100
|
-
const
|
|
2101
|
-
|
|
2102
|
-
if (issue.column) location.column = issue.column;
|
|
2103
|
-
if (issue.indexOrConstraint) location.constraint = issue.indexOrConstraint;
|
|
2104
|
-
return Object.keys(location).length > 0 ? location : void 0;
|
|
2474
|
+
function hasUniqueConstraint(lookup, columns) {
|
|
2475
|
+
const key = columns.join(",");
|
|
2476
|
+
return lookup.uniqueKeys.has(key) || lookup.uniqueIndexKeys.has(key);
|
|
2105
2477
|
}
|
|
2106
|
-
function
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
const bLocation = b.location ?? {};
|
|
2110
|
-
const tableCompare = compareStrings(aLocation.table, bLocation.table);
|
|
2111
|
-
if (tableCompare !== 0) return tableCompare;
|
|
2112
|
-
const columnCompare = compareStrings(aLocation.column, bLocation.column);
|
|
2113
|
-
if (columnCompare !== 0) return columnCompare;
|
|
2114
|
-
const constraintCompare = compareStrings(aLocation.constraint, bLocation.constraint);
|
|
2115
|
-
if (constraintCompare !== 0) return constraintCompare;
|
|
2116
|
-
return compareStrings(a.summary, b.summary);
|
|
2478
|
+
function hasIndex(lookup, columns) {
|
|
2479
|
+
const key = columns.join(",");
|
|
2480
|
+
return lookup.indexKeys.has(key) || lookup.uniqueKeys.has(key);
|
|
2117
2481
|
}
|
|
2118
|
-
function
|
|
2119
|
-
|
|
2120
|
-
if (a === void 0) return -1;
|
|
2121
|
-
if (b === void 0) return 1;
|
|
2122
|
-
return a < b ? -1 : 1;
|
|
2482
|
+
function hasForeignKey(lookup, fk) {
|
|
2483
|
+
return lookup.fkKeys.has(`${fk.columns.join(",")}|${fk.references.table}|${fk.references.columns.join(",")}`);
|
|
2123
2484
|
}
|
|
2124
2485
|
const REFERENTIAL_ACTION_SQL = {
|
|
2125
2486
|
noAction: "NO ACTION",
|
|
@@ -2483,14 +2844,7 @@ var PostgresMigrationRunner = class {
|
|
|
2483
2844
|
}
|
|
2484
2845
|
ensureMarkerCompatibility(marker, plan) {
|
|
2485
2846
|
const origin = plan.origin ?? null;
|
|
2486
|
-
if (!origin)
|
|
2487
|
-
if (!marker) return okVoid();
|
|
2488
|
-
if (this.markerMatchesDestination(marker, plan)) return okVoid();
|
|
2489
|
-
return runnerFailure("MARKER_ORIGIN_MISMATCH", `Existing contract marker (${marker.storageHash}) does not match plan origin (no marker expected).`, { meta: {
|
|
2490
|
-
markerStorageHash: marker.storageHash,
|
|
2491
|
-
expectedOrigin: null
|
|
2492
|
-
} });
|
|
2493
|
-
}
|
|
2847
|
+
if (!origin) return okVoid();
|
|
2494
2848
|
if (!marker) return runnerFailure("MARKER_ORIGIN_MISMATCH", `Missing contract marker: expected origin storage hash ${origin.storageHash}.`, { meta: { expectedOriginStorageHash: origin.storageHash } });
|
|
2495
2849
|
if (marker.storageHash !== origin.storageHash) return runnerFailure("MARKER_ORIGIN_MISMATCH", `Existing contract marker (${marker.storageHash}) does not match plan origin (${origin.storageHash}).`, { meta: {
|
|
2496
2850
|
markerStorageHash: marker.storageHash,
|