@supabase/pg-delta 1.0.0-alpha.24 → 1.0.0-alpha.26
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/core/catalog.model.d.ts +2 -2
- package/dist/core/catalog.model.js +28 -21
- package/dist/core/expand-replace-dependencies.js +1 -7
- package/dist/core/integrations/supabase.js +84 -0
- package/dist/core/objects/aggregate/changes/aggregate.privilege.js +21 -9
- package/dist/core/objects/foreign-data-wrapper/foreign-data-wrapper/changes/foreign-data-wrapper.alter.js +4 -1
- package/dist/core/objects/foreign-data-wrapper/foreign-data-wrapper/changes/foreign-data-wrapper.create.js +6 -3
- package/dist/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.model.d.ts +11 -0
- package/dist/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.model.js +11 -0
- package/dist/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.alter.js +4 -1
- package/dist/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.create.js +6 -3
- package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.model.d.ts +11 -0
- package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.model.js +11 -0
- package/dist/core/objects/foreign-data-wrapper/sensitive-options.d.ts +32 -0
- package/dist/core/objects/foreign-data-wrapper/sensitive-options.js +129 -0
- package/dist/core/objects/foreign-data-wrapper/server/changes/server.alter.js +4 -1
- package/dist/core/objects/foreign-data-wrapper/server/changes/server.create.js +6 -3
- package/dist/core/objects/foreign-data-wrapper/server/server.model.d.ts +10 -0
- package/dist/core/objects/foreign-data-wrapper/server/server.model.js +10 -0
- package/dist/core/objects/foreign-data-wrapper/user-mapping/changes/user-mapping.alter.js +4 -1
- package/dist/core/objects/foreign-data-wrapper/user-mapping/changes/user-mapping.create.js +6 -3
- package/dist/core/objects/foreign-data-wrapper/user-mapping/user-mapping.model.d.ts +10 -0
- package/dist/core/objects/foreign-data-wrapper/user-mapping/user-mapping.model.js +10 -0
- package/dist/core/objects/rls-policy/rls-policy.model.d.ts +2 -2
- package/dist/core/objects/table/table.diff.js +53 -30
- package/dist/core/objects/table/table.model.js +7 -2
- package/dist/core/plan/hierarchy.js +4 -4
- package/dist/core/postgres-config.d.ts +7 -0
- package/dist/core/postgres-config.js +19 -5
- package/dist/core/sort/debug-visualization.js +1 -1
- package/dist/core/sort/topological-sort.js +2 -2
- package/package.json +34 -33
- package/src/core/catalog.model.ts +40 -23
- package/src/core/catalog.snapshot.test.ts +1 -0
- package/src/core/expand-replace-dependencies.test.ts +12 -0
- package/src/core/expand-replace-dependencies.ts +1 -12
- package/src/core/integrations/supabase.test.ts +198 -0
- package/src/core/integrations/supabase.ts +84 -0
- package/src/core/objects/aggregate/changes/aggregate.base.ts +1 -1
- package/src/core/objects/aggregate/changes/aggregate.privilege.test.ts +79 -0
- package/src/core/objects/aggregate/changes/aggregate.privilege.ts +22 -9
- package/src/core/objects/collation/changes/collation.base.ts +1 -1
- package/src/core/objects/domain/changes/domain.base.ts +1 -1
- package/src/core/objects/extension/changes/extension.base.ts +1 -1
- package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/changes/foreign-data-wrapper.alter.test.ts +34 -4
- package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/changes/foreign-data-wrapper.alter.ts +5 -1
- package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/changes/foreign-data-wrapper.base.ts +1 -1
- package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/changes/foreign-data-wrapper.create.test.ts +34 -0
- package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/changes/foreign-data-wrapper.create.ts +7 -5
- package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.model.ts +11 -0
- package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.alter.test.ts +25 -4
- package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.alter.ts +5 -1
- package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.base.ts +1 -1
- package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.create.test.ts +54 -0
- package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.create.ts +7 -5
- package/src/core/objects/foreign-data-wrapper/foreign-table/foreign-table.model.ts +11 -0
- package/src/core/objects/foreign-data-wrapper/sensitive-options.test.ts +98 -0
- package/src/core/objects/foreign-data-wrapper/sensitive-options.ts +133 -0
- package/src/core/objects/foreign-data-wrapper/server/changes/server.alter.test.ts +39 -4
- package/src/core/objects/foreign-data-wrapper/server/changes/server.alter.ts +5 -1
- package/src/core/objects/foreign-data-wrapper/server/changes/server.base.ts +1 -1
- package/src/core/objects/foreign-data-wrapper/server/changes/server.create.test.ts +36 -0
- package/src/core/objects/foreign-data-wrapper/server/changes/server.create.ts +7 -5
- package/src/core/objects/foreign-data-wrapper/server/server.model.ts +10 -0
- package/src/core/objects/foreign-data-wrapper/user-mapping/changes/user-mapping.alter.test.ts +39 -6
- package/src/core/objects/foreign-data-wrapper/user-mapping/changes/user-mapping.alter.ts +5 -1
- package/src/core/objects/foreign-data-wrapper/user-mapping/changes/user-mapping.base.ts +1 -1
- package/src/core/objects/foreign-data-wrapper/user-mapping/changes/user-mapping.create.test.ts +38 -2
- package/src/core/objects/foreign-data-wrapper/user-mapping/changes/user-mapping.create.ts +7 -5
- package/src/core/objects/foreign-data-wrapper/user-mapping/user-mapping.model.ts +10 -0
- package/src/core/objects/index/changes/index.base.ts +1 -1
- package/src/core/objects/language/changes/language.base.ts +1 -1
- package/src/core/objects/materialized-view/changes/materialized-view.base.ts +1 -1
- package/src/core/objects/procedure/changes/procedure.base.ts +1 -1
- package/src/core/objects/rls-policy/changes/rls-policy.base.ts +1 -1
- package/src/core/objects/role/changes/role.base.ts +1 -1
- package/src/core/objects/schema/changes/schema.base.ts +1 -1
- package/src/core/objects/sequence/changes/sequence.base.ts +1 -1
- package/src/core/objects/table/changes/table.base.ts +1 -1
- package/src/core/objects/table/changes/table.comment.ts +2 -8
- package/src/core/objects/table/table.diff.test.ts +198 -5
- package/src/core/objects/table/table.diff.ts +63 -34
- package/src/core/objects/table/table.model.ts +7 -2
- package/src/core/objects/trigger/changes/trigger.alter.ts +1 -4
- package/src/core/objects/trigger/changes/trigger.base.ts +1 -1
- package/src/core/objects/type/composite-type/changes/composite-type.base.ts +1 -1
- package/src/core/objects/type/enum/changes/enum.base.ts +1 -1
- package/src/core/objects/type/range/changes/range.base.ts +1 -1
- package/src/core/objects/view/changes/view.base.ts +1 -1
- package/src/core/plan/hierarchy.ts +4 -4
- package/src/core/plan/sql-format/format-off.test.ts +4 -4
- package/src/core/plan/sql-format/format-pretty-lower-leading.test.ts +4 -4
- package/src/core/plan/sql-format/format-pretty-narrow.test.ts +5 -4
- package/src/core/plan/sql-format/format-pretty-preserve.test.ts +4 -4
- package/src/core/plan/sql-format/format-pretty-upper.test.ts +4 -4
- package/src/core/postgres-config.test.ts +39 -1
- package/src/core/postgres-config.ts +32 -16
- package/src/core/sort/debug-visualization.ts +1 -1
- package/src/core/sort/sort-changes.test.ts +1 -0
- package/src/core/sort/topological-sort.ts +2 -2
|
@@ -58,5 +58,15 @@ export declare class Server extends BasePgModel {
|
|
|
58
58
|
}[];
|
|
59
59
|
};
|
|
60
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Extract `pg_foreign_server` rows into `Server` models.
|
|
63
|
+
*
|
|
64
|
+
* The returned models carry option values **verbatim** from
|
|
65
|
+
* `pg_foreign_server.srvoptions`, which means cleartext secrets like
|
|
66
|
+
* `password` are present in memory. Always route through
|
|
67
|
+
* `extractCatalog` (which calls `normalizeCatalog`) before emitting
|
|
68
|
+
* options to any output channel — see CLI-1467 and
|
|
69
|
+
* `packages/pg-delta/src/core/objects/foreign-data-wrapper/sensitive-options.ts`.
|
|
70
|
+
*/
|
|
61
71
|
export declare function extractServers(pool: Pool): Promise<Server[]>;
|
|
62
72
|
export {};
|
|
@@ -64,6 +64,16 @@ export class Server extends BasePgModel {
|
|
|
64
64
|
};
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Extract `pg_foreign_server` rows into `Server` models.
|
|
69
|
+
*
|
|
70
|
+
* The returned models carry option values **verbatim** from
|
|
71
|
+
* `pg_foreign_server.srvoptions`, which means cleartext secrets like
|
|
72
|
+
* `password` are present in memory. Always route through
|
|
73
|
+
* `extractCatalog` (which calls `normalizeCatalog`) before emitting
|
|
74
|
+
* options to any output channel — see CLI-1467 and
|
|
75
|
+
* `packages/pg-delta/src/core/objects/foreign-data-wrapper/sensitive-options.ts`.
|
|
76
|
+
*/
|
|
67
77
|
export async function extractServers(pool) {
|
|
68
78
|
const { rows: serverRows } = await pool.query(sql `
|
|
69
79
|
select
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { quoteLiteral } from "../../../base.change.js";
|
|
2
|
+
import { redactOptionValue } from "../../sensitive-options.js";
|
|
2
3
|
import { AlterUserMappingChange } from "./user-mapping.base.js";
|
|
3
4
|
/**
|
|
4
5
|
* ALTER USER MAPPING ... OPTIONS ( ADD | SET | DROP ... )
|
|
@@ -22,7 +23,9 @@ export class AlterUserMappingSetOptions extends AlterUserMappingChange {
|
|
|
22
23
|
optionParts.push(`DROP ${opt.option}`);
|
|
23
24
|
}
|
|
24
25
|
else {
|
|
25
|
-
const value = opt.value !== undefined
|
|
26
|
+
const value = opt.value !== undefined
|
|
27
|
+
? quoteLiteral(redactOptionValue(opt.option, opt.value))
|
|
28
|
+
: "''";
|
|
26
29
|
optionParts.push(`${opt.action} ${opt.option} ${value}`);
|
|
27
30
|
}
|
|
28
31
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { quoteLiteral } from "../../../base.change.js";
|
|
2
2
|
import { stableId } from "../../../utils.js";
|
|
3
|
+
import { redactOptionValue } from "../../sensitive-options.js";
|
|
3
4
|
import { CreateUserMappingChange } from "./user-mapping.base.js";
|
|
4
5
|
/**
|
|
5
6
|
* Create a user mapping.
|
|
@@ -39,9 +40,11 @@ export class CreateUserMapping extends CreateUserMappingChange {
|
|
|
39
40
|
if (this.userMapping.options && this.userMapping.options.length > 0) {
|
|
40
41
|
const optionPairs = [];
|
|
41
42
|
for (let i = 0; i < this.userMapping.options.length; i += 2) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
const key = this.userMapping.options[i];
|
|
44
|
+
const value = this.userMapping.options[i + 1];
|
|
45
|
+
if (key === undefined || value === undefined)
|
|
46
|
+
continue;
|
|
47
|
+
optionPairs.push(`${key} ${quoteLiteral(redactOptionValue(key, value))}`);
|
|
45
48
|
}
|
|
46
49
|
if (optionPairs.length > 0) {
|
|
47
50
|
parts.push(`OPTIONS (${optionPairs.join(", ")})`);
|
|
@@ -32,5 +32,15 @@ export declare class UserMapping extends BasePgModel {
|
|
|
32
32
|
options: string[] | null;
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Extract `pg_user_mapping` rows into `UserMapping` models.
|
|
37
|
+
*
|
|
38
|
+
* The returned models carry option values **verbatim** from
|
|
39
|
+
* `pg_user_mapping.umoptions`, which means cleartext secrets like
|
|
40
|
+
* `password` are present in memory. Always route through
|
|
41
|
+
* `extractCatalog` (which calls `normalizeCatalog`) before emitting
|
|
42
|
+
* options to any output channel — see CLI-1467 and
|
|
43
|
+
* `packages/pg-delta/src/core/objects/foreign-data-wrapper/sensitive-options.ts`.
|
|
44
|
+
*/
|
|
35
45
|
export declare function extractUserMappings(pool: Pool): Promise<UserMapping[]>;
|
|
36
46
|
export {};
|
|
@@ -44,6 +44,16 @@ export class UserMapping extends BasePgModel {
|
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Extract `pg_user_mapping` rows into `UserMapping` models.
|
|
49
|
+
*
|
|
50
|
+
* The returned models carry option values **verbatim** from
|
|
51
|
+
* `pg_user_mapping.umoptions`, which means cleartext secrets like
|
|
52
|
+
* `password` are present in memory. Always route through
|
|
53
|
+
* `extractCatalog` (which calls `normalizeCatalog`) before emitting
|
|
54
|
+
* options to any output channel — see CLI-1467 and
|
|
55
|
+
* `packages/pg-delta/src/core/objects/foreign-data-wrapper/sensitive-options.ts`.
|
|
56
|
+
*/
|
|
47
57
|
export async function extractUserMappings(pool) {
|
|
48
58
|
const { rows: mappingRows } = await pool.query(sql `
|
|
49
59
|
select
|
|
@@ -3,9 +3,9 @@ import z from "zod";
|
|
|
3
3
|
import { BasePgModel } from "../base.model.ts";
|
|
4
4
|
declare const rlsPolicyReferencedRelationSchema: z.ZodObject<{
|
|
5
5
|
kind: z.ZodEnum<{
|
|
6
|
+
table: "table";
|
|
6
7
|
foreign_table: "foreign_table";
|
|
7
8
|
materialized_view: "materialized_view";
|
|
8
|
-
table: "table";
|
|
9
9
|
view: "view";
|
|
10
10
|
}>;
|
|
11
11
|
schema: z.ZodString;
|
|
@@ -37,9 +37,9 @@ declare const rlsPolicyPropsSchema: z.ZodObject<{
|
|
|
37
37
|
comment: z.ZodNullable<z.ZodString>;
|
|
38
38
|
referenced_relations: z.ZodArray<z.ZodObject<{
|
|
39
39
|
kind: z.ZodEnum<{
|
|
40
|
+
table: "table";
|
|
40
41
|
foreign_table: "foreign_table";
|
|
41
42
|
materialized_view: "materialized_view";
|
|
42
|
-
table: "table";
|
|
43
43
|
view: "view";
|
|
44
44
|
}>;
|
|
45
45
|
schema: z.ZodString;
|
|
@@ -28,12 +28,6 @@ function createAlterConstraintChange(mainTable, branchTable) {
|
|
|
28
28
|
table: branchTable,
|
|
29
29
|
constraint: c,
|
|
30
30
|
}));
|
|
31
|
-
if (!c.validated) {
|
|
32
|
-
changes.push(new AlterTableValidateConstraint({
|
|
33
|
-
table: branchTable,
|
|
34
|
-
constraint: c,
|
|
35
|
-
}));
|
|
36
|
-
}
|
|
37
31
|
// Add comment for newly created constraint
|
|
38
32
|
if (c.comment !== null) {
|
|
39
33
|
changes.push(new CreateCommentOnConstraint({
|
|
@@ -53,7 +47,7 @@ function createAlterConstraintChange(mainTable, branchTable) {
|
|
|
53
47
|
changes.push(new AlterTableDropConstraint({ table: mainTable, constraint: c }));
|
|
54
48
|
}
|
|
55
49
|
}
|
|
56
|
-
// Altered constraints -> drop + add
|
|
50
|
+
// Altered constraints -> drop + add (or VALIDATE-only shortcut)
|
|
57
51
|
for (const [name, mainC] of mainByName) {
|
|
58
52
|
const branchC = branchByName.get(name);
|
|
59
53
|
if (!branchC)
|
|
@@ -62,23 +56,57 @@ function createAlterConstraintChange(mainTable, branchTable) {
|
|
|
62
56
|
if (mainC.is_partition_clone || branchC.is_partition_clone) {
|
|
63
57
|
continue;
|
|
64
58
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
mainC.
|
|
69
|
-
mainC.
|
|
70
|
-
mainC.
|
|
71
|
-
mainC.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
mainC.
|
|
77
|
-
mainC.
|
|
78
|
-
mainC.
|
|
79
|
-
mainC.
|
|
80
|
-
|
|
81
|
-
mainC.
|
|
59
|
+
// Cheap scalar `===` checks first; only fall through to JSON.stringify
|
|
60
|
+
// on the array fields when every scalar has already matched.
|
|
61
|
+
const fieldsEqualExceptValidated = mainC.constraint_type === branchC.constraint_type &&
|
|
62
|
+
mainC.deferrable === branchC.deferrable &&
|
|
63
|
+
mainC.initially_deferred === branchC.initially_deferred &&
|
|
64
|
+
mainC.is_local === branchC.is_local &&
|
|
65
|
+
mainC.no_inherit === branchC.no_inherit &&
|
|
66
|
+
mainC.is_temporal === branchC.is_temporal &&
|
|
67
|
+
mainC.foreign_key_table === branchC.foreign_key_table &&
|
|
68
|
+
mainC.foreign_key_schema === branchC.foreign_key_schema &&
|
|
69
|
+
mainC.on_update === branchC.on_update &&
|
|
70
|
+
mainC.on_delete === branchC.on_delete &&
|
|
71
|
+
mainC.match_type === branchC.match_type &&
|
|
72
|
+
mainC.check_expression === branchC.check_expression &&
|
|
73
|
+
JSON.stringify(mainC.key_columns) ===
|
|
74
|
+
JSON.stringify(branchC.key_columns) &&
|
|
75
|
+
JSON.stringify(mainC.foreign_key_columns) ===
|
|
76
|
+
JSON.stringify(branchC.foreign_key_columns);
|
|
77
|
+
// Safe-migration shortcut: when the only difference is `validated`
|
|
78
|
+
// flipping from false to true, emit a single `ALTER TABLE ... VALIDATE
|
|
79
|
+
// CONSTRAINT` instead of drop+add. VALIDATE CONSTRAINT only takes
|
|
80
|
+
// SHARE UPDATE EXCLUSIVE (concurrent reads/writes proceed), whereas
|
|
81
|
+
// dropping and re-adding takes ACCESS EXCLUSIVE for the entire scan.
|
|
82
|
+
// Postgres has no reverse command, so `true -> false` must still go
|
|
83
|
+
// through drop+add below.
|
|
84
|
+
if (fieldsEqualExceptValidated &&
|
|
85
|
+
mainC.validated === false &&
|
|
86
|
+
branchC.validated === true) {
|
|
87
|
+
changes.push(new AlterTableValidateConstraint({
|
|
88
|
+
table: branchTable,
|
|
89
|
+
constraint: branchC,
|
|
90
|
+
}));
|
|
91
|
+
// VALIDATE preserves the constraint OID, so its comment is preserved
|
|
92
|
+
// too. Only emit a comment change if it actually differs.
|
|
93
|
+
if (mainC.comment !== branchC.comment) {
|
|
94
|
+
if (branchC.comment === null) {
|
|
95
|
+
changes.push(new DropCommentOnConstraint({
|
|
96
|
+
table: mainTable,
|
|
97
|
+
constraint: mainC,
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
changes.push(new CreateCommentOnConstraint({
|
|
102
|
+
table: branchTable,
|
|
103
|
+
constraint: branchC,
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const changed = mainC.validated !== branchC.validated || !fieldsEqualExceptValidated;
|
|
82
110
|
if (changed) {
|
|
83
111
|
changes.push(new AlterTableDropConstraint({
|
|
84
112
|
table: mainTable,
|
|
@@ -88,12 +116,6 @@ function createAlterConstraintChange(mainTable, branchTable) {
|
|
|
88
116
|
table: branchTable,
|
|
89
117
|
constraint: branchC,
|
|
90
118
|
}));
|
|
91
|
-
if (!branchC.validated) {
|
|
92
|
-
changes.push(new AlterTableValidateConstraint({
|
|
93
|
-
table: branchTable,
|
|
94
|
-
constraint: branchC,
|
|
95
|
-
}));
|
|
96
|
-
}
|
|
97
119
|
// Ensure constraint comment is applied after re-creation
|
|
98
120
|
if (branchC.comment !== null) {
|
|
99
121
|
changes.push(new CreateCommentOnConstraint({
|
|
@@ -163,6 +185,7 @@ export function diffTables(ctx, main, branch) {
|
|
|
163
185
|
changes.push(...createAlterConstraintChange(
|
|
164
186
|
// Create a dummy table with no constraints do diff constraints against
|
|
165
187
|
new Table({
|
|
188
|
+
// oxlint-disable-next-line typescript/no-misused-spread
|
|
166
189
|
...branchTable,
|
|
167
190
|
constraints: [],
|
|
168
191
|
}), branchTable));
|
|
@@ -292,8 +292,13 @@ select
|
|
|
292
292
|
'no_inherit', c.connoinherit,
|
|
293
293
|
'is_temporal', coalesce((to_jsonb(c)->>'conperiod')::boolean, false),
|
|
294
294
|
|
|
295
|
-
--
|
|
296
|
-
|
|
295
|
+
-- Inherited from a parent (partition or classical inheritance).
|
|
296
|
+
-- coninhcount > 0 is the canonical signal across every constraint
|
|
297
|
+
-- kind. We previously used conparentid <> 0, but PostgreSQL only
|
|
298
|
+
-- populates conparentid for PK / UNIQUE / FK on partitions; CHECK
|
|
299
|
+
-- constraints on partitions always have conparentid = 0 and were
|
|
300
|
+
-- being re-emitted on every child, failing apply with 42710.
|
|
301
|
+
'is_partition_clone', (c.coninhcount > 0),
|
|
297
302
|
'parent_constraint_schema', case when c.conparentid <> 0::oid then pc.connamespace::regnamespace::text end,
|
|
298
303
|
'parent_constraint_name', case when c.conparentid <> 0::oid then quote_ident(pc.conname) end,
|
|
299
304
|
'parent_table_schema', case when c.conparentid <> 0::oid then pc_rel.relnamespace::regnamespace::text end,
|
|
@@ -262,7 +262,7 @@ function addClusterChange(cluster, change) {
|
|
|
262
262
|
break;
|
|
263
263
|
default: {
|
|
264
264
|
const _exhaustive = objectType;
|
|
265
|
-
throw new Error(`Unhandled object type: ${_exhaustive}`);
|
|
265
|
+
throw new Error(`Unhandled object type: ${JSON.stringify(_exhaustive)}`);
|
|
266
266
|
}
|
|
267
267
|
}
|
|
268
268
|
}
|
|
@@ -303,7 +303,7 @@ function addChildChange(schema, change) {
|
|
|
303
303
|
break;
|
|
304
304
|
default: {
|
|
305
305
|
const _exhaustive = parentType;
|
|
306
|
-
throw new Error(`Unhandled parent type: ${_exhaustive}`);
|
|
306
|
+
throw new Error(`Unhandled parent type: ${JSON.stringify(_exhaustive)}`);
|
|
307
307
|
}
|
|
308
308
|
}
|
|
309
309
|
const objectType = change.objectType;
|
|
@@ -351,7 +351,7 @@ function addChildChange(schema, change) {
|
|
|
351
351
|
break;
|
|
352
352
|
default: {
|
|
353
353
|
const _exhaustive = objectType;
|
|
354
|
-
throw new Error(`Unhandled object type: ${_exhaustive}`);
|
|
354
|
+
throw new Error(`Unhandled object type: ${JSON.stringify(_exhaustive)}`);
|
|
355
355
|
}
|
|
356
356
|
}
|
|
357
357
|
}
|
|
@@ -482,7 +482,7 @@ function addSchemaLevelChange(schema, change, enrichment) {
|
|
|
482
482
|
break;
|
|
483
483
|
default: {
|
|
484
484
|
const _exhaustive = objectType;
|
|
485
|
-
throw new Error(`Unhandled object type: ${_exhaustive}`);
|
|
485
|
+
throw new Error(`Unhandled object type: ${JSON.stringify(_exhaustive)}`);
|
|
486
486
|
}
|
|
487
487
|
}
|
|
488
488
|
}
|
|
@@ -30,6 +30,13 @@ export declare function connectWithRetry<T>(opts: {
|
|
|
30
30
|
maxBackoffMs?: number;
|
|
31
31
|
sleep?: (ms: number) => Promise<void>;
|
|
32
32
|
}): Promise<T>;
|
|
33
|
+
/**
|
|
34
|
+
* Race `connect()` against a `timeoutMs` rejection and clear the timer when
|
|
35
|
+
* either side wins. If the timer is left running after a fast connect, the
|
|
36
|
+
* pending `setTimeout` keeps the event loop alive and the process hangs for
|
|
37
|
+
* the rest of `timeoutMs`.
|
|
38
|
+
*/
|
|
39
|
+
export declare function connectWithTimeout<T>(connect: () => Promise<T>, timeoutMs: number, label: "source" | "target"): Promise<T>;
|
|
33
40
|
/**
|
|
34
41
|
* Options for creating a Pool with event listeners.
|
|
35
42
|
*/
|
|
@@ -180,6 +180,24 @@ export async function connectWithRetry(opts) {
|
|
|
180
180
|
// Unreachable: loop either returns or throws.
|
|
181
181
|
throw lastError;
|
|
182
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* Race `connect()` against a `timeoutMs` rejection and clear the timer when
|
|
185
|
+
* either side wins. If the timer is left running after a fast connect, the
|
|
186
|
+
* pending `setTimeout` keeps the event loop alive and the process hangs for
|
|
187
|
+
* the rest of `timeoutMs`.
|
|
188
|
+
*/
|
|
189
|
+
export function connectWithTimeout(connect, timeoutMs, label) {
|
|
190
|
+
let timer;
|
|
191
|
+
return Promise.race([
|
|
192
|
+
connect(),
|
|
193
|
+
new Promise((_, reject) => {
|
|
194
|
+
timer = setTimeout(() => reject(new Error(`Connection to ${label} database timed out after ${timeoutMs}ms. ` +
|
|
195
|
+
`The server may require SSL, use an invalid certificate, or be unreachable.`)), timeoutMs);
|
|
196
|
+
}),
|
|
197
|
+
]).finally(() => {
|
|
198
|
+
clearTimeout(timer);
|
|
199
|
+
});
|
|
200
|
+
}
|
|
183
201
|
/**
|
|
184
202
|
* Create a Pool with custom type handlers and optional event listeners.
|
|
185
203
|
*
|
|
@@ -347,11 +365,7 @@ export async function createManagedPool(url, options) {
|
|
|
347
365
|
const timeoutMs = DEFAULT_CONNECT_TIMEOUT_MS;
|
|
348
366
|
try {
|
|
349
367
|
const client = await connectWithRetry({
|
|
350
|
-
connect: () =>
|
|
351
|
-
pool.connect(),
|
|
352
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error(`Connection to ${label} database timed out after ${timeoutMs}ms. ` +
|
|
353
|
-
`The server may require SSL, use an invalid certificate, or be unreachable.`)), timeoutMs)),
|
|
354
|
-
]),
|
|
368
|
+
connect: () => connectWithTimeout(() => pool.connect(), timeoutMs, label),
|
|
355
369
|
});
|
|
356
370
|
client.release();
|
|
357
371
|
}
|
|
@@ -139,7 +139,7 @@ export function printDebugGraph(phaseChanges, graphData, edges, dependencyRows,
|
|
|
139
139
|
const mermaidDiagram = generateMermaidDiagram(phaseChanges, graphData, edges, requirementSets, dependenciesByReferencedId);
|
|
140
140
|
debugGraph("\n==== Mermaid (cycle detected) ====\n%s\n==== end ====", mermaidDiagram);
|
|
141
141
|
}
|
|
142
|
-
catch
|
|
142
|
+
catch {
|
|
143
143
|
// ignore debug printing errors
|
|
144
144
|
}
|
|
145
145
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export function performStableTopologicalSort(nodeCount, edges) {
|
|
7
7
|
const adjacencyList = Array.from({ length: nodeCount }, () => new Set());
|
|
8
|
-
const inDegreeCounts =
|
|
8
|
+
const inDegreeCounts = Array.from({ length: nodeCount }, () => 0);
|
|
9
9
|
for (const [sourceIndex, targetIndex] of edges) {
|
|
10
10
|
if (!adjacencyList[sourceIndex].has(targetIndex)) {
|
|
11
11
|
adjacencyList[sourceIndex].add(targetIndex);
|
|
@@ -51,7 +51,7 @@ export function findCycle(nodeCount, edges) {
|
|
|
51
51
|
adjacencyList[sourceIndex].push(targetIndex);
|
|
52
52
|
}
|
|
53
53
|
// 0 = unvisited, 1 = visiting, 2 = completed
|
|
54
|
-
const visitState =
|
|
54
|
+
const visitState = Array.from({ length: nodeCount }, () => 0);
|
|
55
55
|
const pathStack = [];
|
|
56
56
|
let cycleNodeIndexes = null;
|
|
57
57
|
const depthFirstSearch = (nodeIndex) => {
|
package/package.json
CHANGED
|
@@ -1,7 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supabase/pg-delta",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.26",
|
|
4
4
|
"description": "PostgreSQL migrations made easy",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"diff",
|
|
7
|
+
"migrations",
|
|
8
|
+
"pg",
|
|
9
|
+
"pg-delta",
|
|
10
|
+
"pgdelta",
|
|
11
|
+
"postgres"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://github.com/supabase/pg-toolbelt",
|
|
14
|
+
"bugs": "https://github.com/supabase/pg-toolbelt/issues",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"author": "Supabase",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/supabase/pg-toolbelt.git",
|
|
20
|
+
"directory": "packages/pg-delta"
|
|
21
|
+
},
|
|
22
|
+
"bin": {
|
|
23
|
+
"pgdelta": "./dist/cli/bin/cli.js"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"src",
|
|
28
|
+
"README.md",
|
|
29
|
+
"LICENSE"
|
|
30
|
+
],
|
|
5
31
|
"type": "module",
|
|
6
32
|
"sideEffects": false,
|
|
7
33
|
"main": "./dist/index.js",
|
|
@@ -36,40 +62,12 @@
|
|
|
36
62
|
"default": "./dist/core/catalog-export/index.js"
|
|
37
63
|
}
|
|
38
64
|
},
|
|
39
|
-
"bin": {
|
|
40
|
-
"pgdelta": "./dist/cli/bin/cli.js"
|
|
41
|
-
},
|
|
42
|
-
"files": [
|
|
43
|
-
"dist",
|
|
44
|
-
"src",
|
|
45
|
-
"README.md",
|
|
46
|
-
"LICENSE"
|
|
47
|
-
],
|
|
48
|
-
"keywords": [
|
|
49
|
-
"pg",
|
|
50
|
-
"postgres",
|
|
51
|
-
"migrations",
|
|
52
|
-
"diff",
|
|
53
|
-
"pg-delta",
|
|
54
|
-
"pgdelta"
|
|
55
|
-
],
|
|
56
|
-
"author": "Supabase",
|
|
57
|
-
"license": "MIT",
|
|
58
|
-
"homepage": "https://github.com/supabase/pg-toolbelt",
|
|
59
|
-
"repository": {
|
|
60
|
-
"type": "git",
|
|
61
|
-
"url": "https://github.com/supabase/pg-toolbelt.git",
|
|
62
|
-
"directory": "packages/pg-delta"
|
|
63
|
-
},
|
|
64
|
-
"bugs": "https://github.com/supabase/pg-toolbelt/issues",
|
|
65
|
-
"engines": {
|
|
66
|
-
"node": ">=20.0.0"
|
|
67
|
-
},
|
|
68
65
|
"scripts": {
|
|
69
66
|
"build": "tsc --project tsconfig.build.json",
|
|
70
67
|
"check-types": "tsc --noEmit",
|
|
71
68
|
"docs": "typedoc",
|
|
72
|
-
"format-and-lint": "
|
|
69
|
+
"format-and-lint": "oxfmt --check . && oxlint --deny-warnings",
|
|
70
|
+
"format-and-lint:fix": "oxfmt . && oxlint --fix",
|
|
73
71
|
"knip": "knip",
|
|
74
72
|
"pgdelta": "bun src/cli/bin/cli.ts",
|
|
75
73
|
"sync-base-images": "bun scripts/sync-supabase-base-images.ts",
|
|
@@ -77,12 +75,12 @@
|
|
|
77
75
|
"test:unit": "bun run test src/",
|
|
78
76
|
"test:integration": "bun run test tests/",
|
|
79
77
|
"update-empty-baseline": "bun scripts/update-empty-catalog-baseline.ts",
|
|
80
|
-
"version": "changeset version && bun install --no-frozen-lockfile && bun run format-and-lint
|
|
78
|
+
"version": "changeset version && bun install --no-frozen-lockfile && bun run format-and-lint:fix"
|
|
81
79
|
},
|
|
82
80
|
"dependencies": {
|
|
83
81
|
"@stricli/core": "^1.2.4",
|
|
84
|
-
"@ts-safeql/sql-tag": "^0.2.0",
|
|
85
82
|
"@supabase/pg-topo": "^1.0.0-alpha.1",
|
|
83
|
+
"@ts-safeql/sql-tag": "^0.2.0",
|
|
86
84
|
"chalk": "^5.6.2",
|
|
87
85
|
"debug": "^4.3.7",
|
|
88
86
|
"pg": "^8.17.2",
|
|
@@ -103,5 +101,8 @@
|
|
|
103
101
|
"testcontainers": "^11.10.0",
|
|
104
102
|
"typedoc": "^0.28.17",
|
|
105
103
|
"typescript": "^5.9.3"
|
|
104
|
+
},
|
|
105
|
+
"engines": {
|
|
106
|
+
"node": ">=20.0.0"
|
|
106
107
|
}
|
|
107
108
|
}
|
|
@@ -22,12 +22,13 @@ import {
|
|
|
22
22
|
} from "./objects/extension/extension.model.ts";
|
|
23
23
|
import {
|
|
24
24
|
extractForeignDataWrappers,
|
|
25
|
-
|
|
25
|
+
ForeignDataWrapper,
|
|
26
26
|
} from "./objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.model.ts";
|
|
27
27
|
import {
|
|
28
28
|
extractForeignTables,
|
|
29
|
-
|
|
29
|
+
ForeignTable,
|
|
30
30
|
} from "./objects/foreign-data-wrapper/foreign-table/foreign-table.model.ts";
|
|
31
|
+
import { redactSensitiveOptionPairs } from "./objects/foreign-data-wrapper/sensitive-options.ts";
|
|
31
32
|
import {
|
|
32
33
|
extractServers,
|
|
33
34
|
Server,
|
|
@@ -181,9 +182,8 @@ let _pg1516Baseline: Catalog | null = null;
|
|
|
181
182
|
let _pg17Baseline: Catalog | null = null;
|
|
182
183
|
|
|
183
184
|
async function loadBaselineJson(): Promise<Record<string, unknown>> {
|
|
184
|
-
const mod =
|
|
185
|
-
"./fixtures/empty-catalogs/postgres-15-16-baseline.json"
|
|
186
|
-
);
|
|
185
|
+
const mod =
|
|
186
|
+
await import("./fixtures/empty-catalogs/postgres-15-16-baseline.json");
|
|
187
187
|
return mod.default as Record<string, unknown>;
|
|
188
188
|
}
|
|
189
189
|
|
|
@@ -255,10 +255,12 @@ export async function createEmptyCatalog(
|
|
|
255
255
|
): Promise<Catalog> {
|
|
256
256
|
if (version >= 170000) {
|
|
257
257
|
const baseline = await getPg17Baseline();
|
|
258
|
+
// oxlint-disable-next-line typescript/no-misused-spread
|
|
258
259
|
return new Catalog({ ...baseline, version, currentUser });
|
|
259
260
|
}
|
|
260
261
|
if (version >= 150000) {
|
|
261
262
|
const baseline = await getPg1516Baseline();
|
|
263
|
+
// oxlint-disable-next-line typescript/no-misused-spread
|
|
262
264
|
return new Catalog({ ...baseline, version, currentUser });
|
|
263
265
|
}
|
|
264
266
|
|
|
@@ -421,29 +423,56 @@ function listToRecord<T extends BasePgModel>(list: T[]) {
|
|
|
421
423
|
}
|
|
422
424
|
|
|
423
425
|
function normalizeCatalog(catalog: Catalog): Catalog {
|
|
426
|
+
const foreignDataWrappers = mapRecord(
|
|
427
|
+
catalog.foreignDataWrappers,
|
|
428
|
+
(fdw) =>
|
|
429
|
+
new ForeignDataWrapper({
|
|
430
|
+
name: fdw.name,
|
|
431
|
+
owner: fdw.owner,
|
|
432
|
+
handler: fdw.handler,
|
|
433
|
+
validator: fdw.validator,
|
|
434
|
+
options: redactSensitiveOptionPairs(fdw.options),
|
|
435
|
+
comment: fdw.comment,
|
|
436
|
+
privileges: fdw.privileges,
|
|
437
|
+
}),
|
|
438
|
+
);
|
|
439
|
+
|
|
424
440
|
const servers = mapRecord(catalog.servers, (server) => {
|
|
425
|
-
const maskedOptions = maskOptions(server.options);
|
|
426
441
|
return new Server({
|
|
427
442
|
name: server.name,
|
|
428
443
|
owner: server.owner,
|
|
429
444
|
foreign_data_wrapper: server.foreign_data_wrapper,
|
|
430
445
|
type: server.type,
|
|
431
446
|
version: server.version,
|
|
432
|
-
options:
|
|
447
|
+
options: redactSensitiveOptionPairs(server.options),
|
|
433
448
|
comment: server.comment,
|
|
434
449
|
privileges: server.privileges,
|
|
435
450
|
});
|
|
436
451
|
});
|
|
437
452
|
|
|
438
453
|
const userMappings = mapRecord(catalog.userMappings, (mapping) => {
|
|
439
|
-
const maskedOptions = maskOptions(mapping.options);
|
|
440
454
|
return new UserMapping({
|
|
441
455
|
user: mapping.user,
|
|
442
456
|
server: mapping.server,
|
|
443
|
-
options:
|
|
457
|
+
options: redactSensitiveOptionPairs(mapping.options),
|
|
444
458
|
});
|
|
445
459
|
});
|
|
446
460
|
|
|
461
|
+
const foreignTables = mapRecord(
|
|
462
|
+
catalog.foreignTables,
|
|
463
|
+
(foreignTable) =>
|
|
464
|
+
new ForeignTable({
|
|
465
|
+
schema: foreignTable.schema,
|
|
466
|
+
name: foreignTable.name,
|
|
467
|
+
owner: foreignTable.owner,
|
|
468
|
+
server: foreignTable.server,
|
|
469
|
+
columns: foreignTable.columns,
|
|
470
|
+
options: redactSensitiveOptionPairs(foreignTable.options),
|
|
471
|
+
comment: foreignTable.comment,
|
|
472
|
+
privileges: foreignTable.privileges,
|
|
473
|
+
}),
|
|
474
|
+
);
|
|
475
|
+
|
|
447
476
|
const subscriptions = mapRecord(catalog.subscriptions, (subscription) => {
|
|
448
477
|
return new Subscription({
|
|
449
478
|
name: subscription.name,
|
|
@@ -490,10 +519,10 @@ function normalizeCatalog(catalog: Catalog): Catalog {
|
|
|
490
519
|
rules: catalog.rules,
|
|
491
520
|
ranges: catalog.ranges,
|
|
492
521
|
views: catalog.views,
|
|
493
|
-
foreignDataWrappers
|
|
522
|
+
foreignDataWrappers,
|
|
494
523
|
servers,
|
|
495
524
|
userMappings,
|
|
496
|
-
foreignTables
|
|
525
|
+
foreignTables,
|
|
497
526
|
depends: catalog.depends,
|
|
498
527
|
indexableObjects: catalog.indexableObjects,
|
|
499
528
|
version: catalog.version,
|
|
@@ -501,18 +530,6 @@ function normalizeCatalog(catalog: Catalog): Catalog {
|
|
|
501
530
|
});
|
|
502
531
|
}
|
|
503
532
|
|
|
504
|
-
function maskOptions(options: string[] | null): string[] | null {
|
|
505
|
-
if (!options || options.length === 0) return options;
|
|
506
|
-
const masked: string[] = [];
|
|
507
|
-
for (let i = 0; i < options.length; i += 2) {
|
|
508
|
-
const key = options[i];
|
|
509
|
-
const value = options[i + 1];
|
|
510
|
-
if (key === undefined || value === undefined) continue;
|
|
511
|
-
masked.push(key, `__OPTION_${key.toUpperCase()}__`);
|
|
512
|
-
}
|
|
513
|
-
return masked.length > 0 ? masked : null;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
533
|
function mapRecord<TValue, TResult>(
|
|
517
534
|
record: Record<string, TValue>,
|
|
518
535
|
mapper: (value: TValue) => TResult,
|
|
@@ -294,6 +294,7 @@ describe("catalog snapshot serde", () => {
|
|
|
294
294
|
|
|
295
295
|
const sourceCatalog = await createEmptyCatalog(160000, "postgres");
|
|
296
296
|
const targetCatalog = await createEmptyCatalog(160000, "postgres");
|
|
297
|
+
// oxlint-disable-next-line typescript/no-misused-spread
|
|
297
298
|
const source = { ...sourceCatalog };
|
|
298
299
|
|
|
299
300
|
expect(source instanceof Catalog).toBe(false);
|