@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
|
@@ -80,6 +80,16 @@ export class Server extends BasePgModel {
|
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Extract `pg_foreign_server` rows into `Server` models.
|
|
85
|
+
*
|
|
86
|
+
* The returned models carry option values **verbatim** from
|
|
87
|
+
* `pg_foreign_server.srvoptions`, which means cleartext secrets like
|
|
88
|
+
* `password` are present in memory. Always route through
|
|
89
|
+
* `extractCatalog` (which calls `normalizeCatalog`) before emitting
|
|
90
|
+
* options to any output channel — see CLI-1467 and
|
|
91
|
+
* `packages/pg-delta/src/core/objects/foreign-data-wrapper/sensitive-options.ts`.
|
|
92
|
+
*/
|
|
83
93
|
export async function extractServers(pool: Pool): Promise<Server[]> {
|
|
84
94
|
const { rows: serverRows } = await pool.query<ServerProps>(sql`
|
|
85
95
|
select
|
package/src/core/objects/foreign-data-wrapper/user-mapping/changes/user-mapping.alter.test.ts
CHANGED
|
@@ -23,10 +23,43 @@ describe.concurrent("user-mapping", () => {
|
|
|
23
23
|
await assertValidSql(change.serialize());
|
|
24
24
|
|
|
25
25
|
expect(change.serialize()).toBe(
|
|
26
|
-
"ALTER USER MAPPING FOR test_user SERVER test_server OPTIONS (ADD user 'remote_user', ADD password '
|
|
26
|
+
"ALTER USER MAPPING FOR test_user SERVER test_server OPTIONS (ADD user 'remote_user', ADD password '__OPTION_PASSWORD__')",
|
|
27
27
|
);
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
+
test("redacts sensitive option values to prevent secret leakage (CLI-1467)", async () => {
|
|
31
|
+
const props: UserMappingProps = {
|
|
32
|
+
user: "postgres",
|
|
33
|
+
server: "live_risk_server",
|
|
34
|
+
options: null,
|
|
35
|
+
};
|
|
36
|
+
const userMapping = new UserMapping(props);
|
|
37
|
+
const change = new AlterUserMappingSetOptions({
|
|
38
|
+
userMapping,
|
|
39
|
+
options: [
|
|
40
|
+
{ action: "ADD", option: "password", value: "real-user-password" },
|
|
41
|
+
{ action: "SET", option: "user", value: "fdw_reader" },
|
|
42
|
+
{
|
|
43
|
+
action: "ADD",
|
|
44
|
+
option: "passfile",
|
|
45
|
+
value: "/etc/secrets/passfile",
|
|
46
|
+
},
|
|
47
|
+
{ action: "SET", option: "sslpassword", value: "ssl-secret" },
|
|
48
|
+
],
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
await assertValidSql(change.serialize());
|
|
52
|
+
|
|
53
|
+
const sql = change.serialize();
|
|
54
|
+
expect(sql).not.toContain("real-user-password");
|
|
55
|
+
expect(sql).not.toContain("/etc/secrets/passfile");
|
|
56
|
+
expect(sql).not.toContain("ssl-secret");
|
|
57
|
+
expect(sql).toContain("SET user 'fdw_reader'");
|
|
58
|
+
expect(sql).toContain("ADD password '__OPTION_PASSWORD__'");
|
|
59
|
+
expect(sql).toContain("ADD passfile '__OPTION_PASSFILE__'");
|
|
60
|
+
expect(sql).toContain("SET sslpassword '__OPTION_SSLPASSWORD__'");
|
|
61
|
+
});
|
|
62
|
+
|
|
30
63
|
test("set options SET", async () => {
|
|
31
64
|
const props: UserMappingProps = {
|
|
32
65
|
user: "test_user",
|
|
@@ -42,7 +75,7 @@ describe.concurrent("user-mapping", () => {
|
|
|
42
75
|
await assertValidSql(change.serialize());
|
|
43
76
|
|
|
44
77
|
expect(change.serialize()).toBe(
|
|
45
|
-
"ALTER USER MAPPING FOR test_user SERVER test_server OPTIONS (SET password '
|
|
78
|
+
"ALTER USER MAPPING FOR test_user SERVER test_server OPTIONS (SET password '__OPTION_PASSWORD__')",
|
|
46
79
|
);
|
|
47
80
|
});
|
|
48
81
|
|
|
@@ -75,16 +108,16 @@ describe.concurrent("user-mapping", () => {
|
|
|
75
108
|
const change = new AlterUserMappingSetOptions({
|
|
76
109
|
userMapping,
|
|
77
110
|
options: [
|
|
78
|
-
{ action: "ADD", option: "
|
|
79
|
-
{ action: "SET", option: "
|
|
80
|
-
{ action: "DROP", option: "
|
|
111
|
+
{ action: "ADD", option: "user", value: "remote_user" },
|
|
112
|
+
{ action: "SET", option: "sslmode", value: "require" },
|
|
113
|
+
{ action: "DROP", option: "application_name" },
|
|
81
114
|
],
|
|
82
115
|
});
|
|
83
116
|
|
|
84
117
|
await assertValidSql(change.serialize());
|
|
85
118
|
|
|
86
119
|
expect(change.serialize()).toBe(
|
|
87
|
-
"ALTER USER MAPPING FOR PUBLIC SERVER test_server OPTIONS (ADD
|
|
120
|
+
"ALTER USER MAPPING FOR PUBLIC SERVER test_server OPTIONS (ADD user 'remote_user', SET sslmode 'require', DROP application_name)",
|
|
88
121
|
);
|
|
89
122
|
});
|
|
90
123
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { SerializeOptions } from "../../../../integrations/serialize/serialize.types.ts";
|
|
2
2
|
import { quoteLiteral } from "../../../base.change.ts";
|
|
3
|
+
import { redactOptionValue } from "../../sensitive-options.ts";
|
|
3
4
|
import type { UserMapping } from "../user-mapping.model.ts";
|
|
4
5
|
import { AlterUserMappingChange } from "./user-mapping.base.ts";
|
|
5
6
|
|
|
@@ -53,7 +54,10 @@ export class AlterUserMappingSetOptions extends AlterUserMappingChange {
|
|
|
53
54
|
if (opt.action === "DROP") {
|
|
54
55
|
optionParts.push(`DROP ${opt.option}`);
|
|
55
56
|
} else {
|
|
56
|
-
const value =
|
|
57
|
+
const value =
|
|
58
|
+
opt.value !== undefined
|
|
59
|
+
? quoteLiteral(redactOptionValue(opt.option, opt.value))
|
|
60
|
+
: "''";
|
|
57
61
|
optionParts.push(`${opt.action} ${opt.option} ${value}`);
|
|
58
62
|
}
|
|
59
63
|
}
|
|
@@ -4,7 +4,7 @@ import type { UserMapping } from "../user-mapping.model.ts";
|
|
|
4
4
|
abstract class BaseUserMappingChange extends BaseChange {
|
|
5
5
|
abstract readonly userMapping: UserMapping;
|
|
6
6
|
abstract readonly scope: "object";
|
|
7
|
-
readonly objectType
|
|
7
|
+
readonly objectType = "user_mapping" as const;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export abstract class CreateUserMappingChange extends BaseUserMappingChange {
|
package/src/core/objects/foreign-data-wrapper/user-mapping/changes/user-mapping.create.test.ts
CHANGED
|
@@ -72,7 +72,7 @@ describe("user-mapping", () => {
|
|
|
72
72
|
await assertValidSql(change.serialize());
|
|
73
73
|
|
|
74
74
|
expect(change.serialize()).toBe(
|
|
75
|
-
"CREATE USER MAPPING FOR test_user SERVER test_server OPTIONS (user 'remote_user', password '
|
|
75
|
+
"CREATE USER MAPPING FOR test_user SERVER test_server OPTIONS (user 'remote_user', password '__OPTION_PASSWORD__')",
|
|
76
76
|
);
|
|
77
77
|
});
|
|
78
78
|
|
|
@@ -90,7 +90,43 @@ describe("user-mapping", () => {
|
|
|
90
90
|
await assertValidSql(change.serialize());
|
|
91
91
|
|
|
92
92
|
expect(change.serialize()).toBe(
|
|
93
|
-
"CREATE USER MAPPING FOR PUBLIC SERVER test_server OPTIONS (user 'remote_user', password '
|
|
93
|
+
"CREATE USER MAPPING FOR PUBLIC SERVER test_server OPTIONS (user 'remote_user', password '__OPTION_PASSWORD__')",
|
|
94
94
|
);
|
|
95
95
|
});
|
|
96
|
+
|
|
97
|
+
test("redacts sensitive option values to prevent secret leakage (CLI-1467)", async () => {
|
|
98
|
+
const userMapping = new UserMapping({
|
|
99
|
+
user: "postgres",
|
|
100
|
+
server: "live_risk_server",
|
|
101
|
+
options: [
|
|
102
|
+
"user",
|
|
103
|
+
"fdw_reader",
|
|
104
|
+
"password",
|
|
105
|
+
"real-user-password",
|
|
106
|
+
"passfile",
|
|
107
|
+
"/etc/secrets/passfile",
|
|
108
|
+
"sslpassword",
|
|
109
|
+
"ssl-secret",
|
|
110
|
+
"passcode",
|
|
111
|
+
"krb-passcode",
|
|
112
|
+
],
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const change = new CreateUserMapping({
|
|
116
|
+
userMapping,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
await assertValidSql(change.serialize());
|
|
120
|
+
|
|
121
|
+
const sql = change.serialize();
|
|
122
|
+
expect(sql).not.toContain("real-user-password");
|
|
123
|
+
expect(sql).not.toContain("/etc/secrets/passfile");
|
|
124
|
+
expect(sql).not.toContain("ssl-secret");
|
|
125
|
+
expect(sql).not.toContain("krb-passcode");
|
|
126
|
+
expect(sql).toContain("user 'fdw_reader'");
|
|
127
|
+
expect(sql).toContain("password '__OPTION_PASSWORD__'");
|
|
128
|
+
expect(sql).toContain("passfile '__OPTION_PASSFILE__'");
|
|
129
|
+
expect(sql).toContain("sslpassword '__OPTION_SSLPASSWORD__'");
|
|
130
|
+
expect(sql).toContain("passcode '__OPTION_PASSCODE__'");
|
|
131
|
+
});
|
|
96
132
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { SerializeOptions } from "../../../../integrations/serialize/serialize.types.ts";
|
|
2
2
|
import { quoteLiteral } from "../../../base.change.ts";
|
|
3
3
|
import { stableId } from "../../../utils.ts";
|
|
4
|
+
import { redactOptionValue } from "../../sensitive-options.ts";
|
|
4
5
|
import type { UserMapping } from "../user-mapping.model.ts";
|
|
5
6
|
import { CreateUserMappingChange } from "./user-mapping.base.ts";
|
|
6
7
|
|
|
@@ -51,11 +52,12 @@ export class CreateUserMapping extends CreateUserMappingChange {
|
|
|
51
52
|
if (this.userMapping.options && this.userMapping.options.length > 0) {
|
|
52
53
|
const optionPairs: string[] = [];
|
|
53
54
|
for (let i = 0; i < this.userMapping.options.length; i += 2) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
const key = this.userMapping.options[i];
|
|
56
|
+
const value = this.userMapping.options[i + 1];
|
|
57
|
+
if (key === undefined || value === undefined) continue;
|
|
58
|
+
optionPairs.push(
|
|
59
|
+
`${key} ${quoteLiteral(redactOptionValue(key, value))}`,
|
|
60
|
+
);
|
|
59
61
|
}
|
|
60
62
|
if (optionPairs.length > 0) {
|
|
61
63
|
parts.push(`OPTIONS (${optionPairs.join(", ")})`);
|
|
@@ -56,6 +56,16 @@ export class UserMapping extends BasePgModel {
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Extract `pg_user_mapping` rows into `UserMapping` models.
|
|
61
|
+
*
|
|
62
|
+
* The returned models carry option values **verbatim** from
|
|
63
|
+
* `pg_user_mapping.umoptions`, which means cleartext secrets like
|
|
64
|
+
* `password` are present in memory. Always route through
|
|
65
|
+
* `extractCatalog` (which calls `normalizeCatalog`) before emitting
|
|
66
|
+
* options to any output channel — see CLI-1467 and
|
|
67
|
+
* `packages/pg-delta/src/core/objects/foreign-data-wrapper/sensitive-options.ts`.
|
|
68
|
+
*/
|
|
59
69
|
export async function extractUserMappings(pool: Pool): Promise<UserMapping[]> {
|
|
60
70
|
const { rows: mappingRows } = await pool.query<UserMappingProps>(sql`
|
|
61
71
|
select
|
|
@@ -4,7 +4,7 @@ import type { Index } from "../index.model.ts";
|
|
|
4
4
|
abstract class BaseIndexChange extends BaseChange {
|
|
5
5
|
abstract readonly index: Index;
|
|
6
6
|
abstract readonly scope: "object" | "comment";
|
|
7
|
-
readonly objectType
|
|
7
|
+
readonly objectType = "index" as const;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export abstract class CreateIndexChange extends BaseIndexChange {
|
|
@@ -4,7 +4,7 @@ import type { Language } from "../language.model.ts";
|
|
|
4
4
|
abstract class BaseLanguageChange extends BaseChange {
|
|
5
5
|
abstract readonly language: Language;
|
|
6
6
|
abstract readonly scope: "object" | "comment" | "privilege";
|
|
7
|
-
readonly objectType
|
|
7
|
+
readonly objectType = "language" as const;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export abstract class CreateLanguageChange extends BaseLanguageChange {
|
|
@@ -8,7 +8,7 @@ abstract class BaseMaterializedViewChange extends BaseChange {
|
|
|
8
8
|
| "comment"
|
|
9
9
|
| "privilege"
|
|
10
10
|
| "security_label";
|
|
11
|
-
readonly objectType
|
|
11
|
+
readonly objectType = "materialized_view" as const;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export abstract class CreateMaterializedViewChange extends BaseMaterializedViewChange {
|
|
@@ -8,7 +8,7 @@ abstract class BaseProcedureChange extends BaseChange {
|
|
|
8
8
|
| "comment"
|
|
9
9
|
| "privilege"
|
|
10
10
|
| "security_label";
|
|
11
|
-
readonly objectType
|
|
11
|
+
readonly objectType = "procedure" as const;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export abstract class CreateProcedureChange extends BaseProcedureChange {
|
|
@@ -4,7 +4,7 @@ import type { RlsPolicy } from "../rls-policy.model.ts";
|
|
|
4
4
|
abstract class BaseRlsPolicyChange extends BaseChange {
|
|
5
5
|
abstract readonly policy: RlsPolicy;
|
|
6
6
|
abstract readonly scope: "object" | "comment";
|
|
7
|
-
readonly objectType
|
|
7
|
+
readonly objectType = "rls_policy" as const;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export abstract class CreateRlsPolicyChange extends BaseRlsPolicyChange {
|
|
@@ -9,7 +9,7 @@ abstract class BaseRoleChange extends BaseChange {
|
|
|
9
9
|
| "membership"
|
|
10
10
|
| "default_privilege"
|
|
11
11
|
| "security_label";
|
|
12
|
-
readonly objectType
|
|
12
|
+
readonly objectType = "role" as const;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export abstract class CreateRoleChange extends BaseRoleChange {
|
|
@@ -8,7 +8,7 @@ abstract class BaseSchemaChange extends BaseChange {
|
|
|
8
8
|
| "comment"
|
|
9
9
|
| "privilege"
|
|
10
10
|
| "security_label";
|
|
11
|
-
readonly objectType
|
|
11
|
+
readonly objectType = "schema" as const;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export abstract class CreateSchemaChange extends BaseSchemaChange {
|
|
@@ -8,7 +8,7 @@ abstract class BaseSequenceChange extends BaseChange {
|
|
|
8
8
|
| "comment"
|
|
9
9
|
| "privilege"
|
|
10
10
|
| "security_label";
|
|
11
|
-
readonly objectType
|
|
11
|
+
readonly objectType = "sequence" as const;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export abstract class CreateSequenceChange extends BaseSequenceChange {
|
|
@@ -8,7 +8,7 @@ abstract class BaseTableChange extends BaseChange {
|
|
|
8
8
|
| "comment"
|
|
9
9
|
| "privilege"
|
|
10
10
|
| "security_label";
|
|
11
|
-
readonly objectType
|
|
11
|
+
readonly objectType = "table" as const;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export abstract class CreateTableChange extends BaseTableChange {
|
|
@@ -179,10 +179,7 @@ export class CreateCommentOnConstraint extends CreateTableChange {
|
|
|
179
179
|
public readonly constraint: TableConstraintProps;
|
|
180
180
|
public readonly scope = "comment" as const;
|
|
181
181
|
|
|
182
|
-
constructor(props: {
|
|
183
|
-
table: Table;
|
|
184
|
-
constraint: TableConstraintProps;
|
|
185
|
-
}) {
|
|
182
|
+
constructor(props: { table: Table; constraint: TableConstraintProps }) {
|
|
186
183
|
super();
|
|
187
184
|
this.table = props.table;
|
|
188
185
|
this.constraint = props.constraint;
|
|
@@ -228,10 +225,7 @@ export class DropCommentOnConstraint extends DropTableChange {
|
|
|
228
225
|
public readonly constraint: TableConstraintProps;
|
|
229
226
|
public readonly scope = "comment" as const;
|
|
230
227
|
|
|
231
|
-
constructor(props: {
|
|
232
|
-
table: Table;
|
|
233
|
-
constraint: TableConstraintProps;
|
|
234
|
-
}) {
|
|
228
|
+
constructor(props: { table: Table; constraint: TableConstraintProps }) {
|
|
235
229
|
super();
|
|
236
230
|
this.table = props.table;
|
|
237
231
|
this.constraint = props.constraint;
|
|
@@ -75,7 +75,7 @@ describe.concurrent("table.diff", () => {
|
|
|
75
75
|
expect(dropped[0]).toBeInstanceOf(DropTable);
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
-
test("created NOT VALID CHECK emits AddConstraint
|
|
78
|
+
test("created NOT VALID CHECK emits AddConstraint only (no Validate)", () => {
|
|
79
79
|
const main = new Table({
|
|
80
80
|
...base,
|
|
81
81
|
name: "t_nv",
|
|
@@ -102,6 +102,7 @@ describe.concurrent("table.diff", () => {
|
|
|
102
102
|
constraints: [],
|
|
103
103
|
});
|
|
104
104
|
const branch = new Table({
|
|
105
|
+
// oxlint-disable-next-line typescript/no-misused-spread
|
|
105
106
|
...main,
|
|
106
107
|
constraints: [
|
|
107
108
|
{
|
|
@@ -132,20 +133,208 @@ describe.concurrent("table.diff", () => {
|
|
|
132
133
|
match_type: null,
|
|
133
134
|
check_expression: "a > 0",
|
|
134
135
|
owner: "o1",
|
|
136
|
+
definition: "CHECK (a > 0) NOT VALID",
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
});
|
|
140
|
+
const changes = diffTables(
|
|
141
|
+
testContext,
|
|
142
|
+
{ [main.stableId]: main },
|
|
143
|
+
{ [branch.stableId]: branch },
|
|
144
|
+
);
|
|
145
|
+
const add = changes.find((c) => c instanceof AlterTableAddConstraint);
|
|
146
|
+
expect(add).toBeInstanceOf(AlterTableAddConstraint);
|
|
147
|
+
expect(add?.serialize()).toContain("NOT VALID");
|
|
148
|
+
expect(changes.some((c) => c instanceof AlterTableValidateConstraint)).toBe(
|
|
149
|
+
false,
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("NOT VALID -> validated emits only VALIDATE CONSTRAINT (no drop+add)", () => {
|
|
154
|
+
const sharedConstraint = {
|
|
155
|
+
name: "ck_nv",
|
|
156
|
+
constraint_type: "c" as const,
|
|
157
|
+
deferrable: false,
|
|
158
|
+
initially_deferred: false,
|
|
159
|
+
is_local: true,
|
|
160
|
+
no_inherit: false,
|
|
161
|
+
is_temporal: false,
|
|
162
|
+
is_partition_clone: false,
|
|
163
|
+
parent_constraint_schema: null,
|
|
164
|
+
parent_constraint_name: null,
|
|
165
|
+
parent_table_schema: null,
|
|
166
|
+
parent_table_name: null,
|
|
167
|
+
key_columns: [],
|
|
168
|
+
foreign_key_columns: null,
|
|
169
|
+
foreign_key_table: null,
|
|
170
|
+
foreign_key_schema: null,
|
|
171
|
+
foreign_key_table_is_partition: null,
|
|
172
|
+
foreign_key_parent_schema: null,
|
|
173
|
+
foreign_key_parent_table: null,
|
|
174
|
+
foreign_key_effective_schema: null,
|
|
175
|
+
foreign_key_effective_table: null,
|
|
176
|
+
on_update: null,
|
|
177
|
+
on_delete: null,
|
|
178
|
+
match_type: null,
|
|
179
|
+
check_expression: "a > 0",
|
|
180
|
+
owner: "o1",
|
|
181
|
+
comment: null,
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const main = new Table({
|
|
185
|
+
...base,
|
|
186
|
+
name: "t_nv",
|
|
187
|
+
columns: [
|
|
188
|
+
{
|
|
189
|
+
name: "a",
|
|
190
|
+
position: 1,
|
|
191
|
+
data_type: "integer",
|
|
192
|
+
data_type_str: "integer",
|
|
193
|
+
is_custom_type: false,
|
|
194
|
+
custom_type_type: null,
|
|
195
|
+
custom_type_category: null,
|
|
196
|
+
custom_type_schema: null,
|
|
197
|
+
custom_type_name: null,
|
|
198
|
+
not_null: false,
|
|
199
|
+
is_identity: false,
|
|
200
|
+
is_identity_always: false,
|
|
201
|
+
is_generated: false,
|
|
202
|
+
collation: null,
|
|
203
|
+
default: null,
|
|
204
|
+
comment: null,
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
constraints: [
|
|
208
|
+
{
|
|
209
|
+
...sharedConstraint,
|
|
210
|
+
validated: false,
|
|
211
|
+
definition: "CHECK (a > 0) NOT VALID",
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
});
|
|
215
|
+
const branch = new Table({
|
|
216
|
+
// oxlint-disable-next-line typescript/no-misused-spread
|
|
217
|
+
...main,
|
|
218
|
+
constraints: [
|
|
219
|
+
{
|
|
220
|
+
...sharedConstraint,
|
|
221
|
+
validated: true,
|
|
135
222
|
definition: "CHECK (a > 0)",
|
|
136
223
|
},
|
|
137
224
|
],
|
|
138
225
|
});
|
|
226
|
+
|
|
227
|
+
const changes = diffTables(
|
|
228
|
+
testContext,
|
|
229
|
+
{ [main.stableId]: main },
|
|
230
|
+
{ [branch.stableId]: branch },
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
const validate = changes.find(
|
|
234
|
+
(c) => c instanceof AlterTableValidateConstraint,
|
|
235
|
+
);
|
|
236
|
+
expect(validate).toBeInstanceOf(AlterTableValidateConstraint);
|
|
237
|
+
expect(validate?.serialize()).toMatchInlineSnapshot(
|
|
238
|
+
`"ALTER TABLE public.t_nv VALIDATE CONSTRAINT ck_nv"`,
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
expect(changes.some((c) => c instanceof AlterTableDropConstraint)).toBe(
|
|
242
|
+
false,
|
|
243
|
+
);
|
|
244
|
+
expect(changes.some((c) => c instanceof AlterTableAddConstraint)).toBe(
|
|
245
|
+
false,
|
|
246
|
+
);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("NOT VALID -> validated + other field change still drops+adds (no shortcut)", () => {
|
|
250
|
+
const sharedConstraint = {
|
|
251
|
+
name: "ck_nv",
|
|
252
|
+
constraint_type: "c" as const,
|
|
253
|
+
deferrable: false,
|
|
254
|
+
initially_deferred: false,
|
|
255
|
+
is_local: true,
|
|
256
|
+
no_inherit: false,
|
|
257
|
+
is_temporal: false,
|
|
258
|
+
is_partition_clone: false,
|
|
259
|
+
parent_constraint_schema: null,
|
|
260
|
+
parent_constraint_name: null,
|
|
261
|
+
parent_table_schema: null,
|
|
262
|
+
parent_table_name: null,
|
|
263
|
+
key_columns: [],
|
|
264
|
+
foreign_key_columns: null,
|
|
265
|
+
foreign_key_table: null,
|
|
266
|
+
foreign_key_schema: null,
|
|
267
|
+
foreign_key_table_is_partition: null,
|
|
268
|
+
foreign_key_parent_schema: null,
|
|
269
|
+
foreign_key_parent_table: null,
|
|
270
|
+
foreign_key_effective_schema: null,
|
|
271
|
+
foreign_key_effective_table: null,
|
|
272
|
+
on_update: null,
|
|
273
|
+
on_delete: null,
|
|
274
|
+
match_type: null,
|
|
275
|
+
owner: "o1",
|
|
276
|
+
comment: null,
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const main = new Table({
|
|
280
|
+
...base,
|
|
281
|
+
name: "t_nv",
|
|
282
|
+
columns: [
|
|
283
|
+
{
|
|
284
|
+
name: "a",
|
|
285
|
+
position: 1,
|
|
286
|
+
data_type: "integer",
|
|
287
|
+
data_type_str: "integer",
|
|
288
|
+
is_custom_type: false,
|
|
289
|
+
custom_type_type: null,
|
|
290
|
+
custom_type_category: null,
|
|
291
|
+
custom_type_schema: null,
|
|
292
|
+
custom_type_name: null,
|
|
293
|
+
not_null: false,
|
|
294
|
+
is_identity: false,
|
|
295
|
+
is_identity_always: false,
|
|
296
|
+
is_generated: false,
|
|
297
|
+
collation: null,
|
|
298
|
+
default: null,
|
|
299
|
+
comment: null,
|
|
300
|
+
},
|
|
301
|
+
],
|
|
302
|
+
constraints: [
|
|
303
|
+
{
|
|
304
|
+
...sharedConstraint,
|
|
305
|
+
validated: false,
|
|
306
|
+
check_expression: "a > 0",
|
|
307
|
+
definition: "CHECK (a > 0) NOT VALID",
|
|
308
|
+
},
|
|
309
|
+
],
|
|
310
|
+
});
|
|
311
|
+
const branch = new Table({
|
|
312
|
+
// oxlint-disable-next-line typescript/no-misused-spread
|
|
313
|
+
...main,
|
|
314
|
+
constraints: [
|
|
315
|
+
{
|
|
316
|
+
...sharedConstraint,
|
|
317
|
+
validated: true,
|
|
318
|
+
check_expression: "a > 1",
|
|
319
|
+
definition: "CHECK (a > 1)",
|
|
320
|
+
},
|
|
321
|
+
],
|
|
322
|
+
});
|
|
323
|
+
|
|
139
324
|
const changes = diffTables(
|
|
140
325
|
testContext,
|
|
141
326
|
{ [main.stableId]: main },
|
|
142
327
|
{ [branch.stableId]: branch },
|
|
143
328
|
);
|
|
329
|
+
|
|
330
|
+
expect(changes.some((c) => c instanceof AlterTableDropConstraint)).toBe(
|
|
331
|
+
true,
|
|
332
|
+
);
|
|
144
333
|
expect(changes.some((c) => c instanceof AlterTableAddConstraint)).toBe(
|
|
145
334
|
true,
|
|
146
335
|
);
|
|
147
336
|
expect(changes.some((c) => c instanceof AlterTableValidateConstraint)).toBe(
|
|
148
|
-
|
|
337
|
+
false,
|
|
149
338
|
);
|
|
150
339
|
});
|
|
151
340
|
|
|
@@ -362,7 +551,7 @@ describe.concurrent("table.diff", () => {
|
|
|
362
551
|
true,
|
|
363
552
|
);
|
|
364
553
|
expect(created.some((c) => c instanceof AlterTableValidateConstraint)).toBe(
|
|
365
|
-
|
|
554
|
+
false,
|
|
366
555
|
);
|
|
367
556
|
|
|
368
557
|
const dropped = diffTables(
|
|
@@ -480,6 +669,7 @@ describe.concurrent("table.diff", () => {
|
|
|
480
669
|
],
|
|
481
670
|
});
|
|
482
671
|
const tBranch = new Table({
|
|
672
|
+
// oxlint-disable-next-line typescript/no-misused-spread
|
|
483
673
|
...tMain,
|
|
484
674
|
constraints: [
|
|
485
675
|
{
|
|
@@ -501,7 +691,7 @@ describe.concurrent("table.diff", () => {
|
|
|
501
691
|
);
|
|
502
692
|
});
|
|
503
693
|
|
|
504
|
-
test("altered foreign key
|
|
694
|
+
test("altered foreign key to NOT VALID triggers drop+add without validate", () => {
|
|
505
695
|
const tMain = new Table({
|
|
506
696
|
...base,
|
|
507
697
|
name: "t_fk",
|
|
@@ -559,12 +749,14 @@ describe.concurrent("table.diff", () => {
|
|
|
559
749
|
],
|
|
560
750
|
});
|
|
561
751
|
const tBranch = new Table({
|
|
752
|
+
// oxlint-disable-next-line typescript/no-misused-spread
|
|
562
753
|
...tMain,
|
|
563
754
|
constraints: [
|
|
564
755
|
{
|
|
565
756
|
...(tMain.constraints[0] as (typeof tMain.constraints)[number]),
|
|
566
757
|
on_delete: "c",
|
|
567
758
|
validated: false,
|
|
759
|
+
definition: "FOREIGN KEY (a) REFERENCES other(a) NOT VALID",
|
|
568
760
|
},
|
|
569
761
|
],
|
|
570
762
|
});
|
|
@@ -606,7 +798,7 @@ describe.concurrent("table.diff", () => {
|
|
|
606
798
|
true,
|
|
607
799
|
);
|
|
608
800
|
expect(changes.some((c) => c instanceof AlterTableValidateConstraint)).toBe(
|
|
609
|
-
|
|
801
|
+
false,
|
|
610
802
|
);
|
|
611
803
|
});
|
|
612
804
|
|
|
@@ -686,6 +878,7 @@ describe.concurrent("table.diff", () => {
|
|
|
686
878
|
],
|
|
687
879
|
});
|
|
688
880
|
const tBranch = new Table({
|
|
881
|
+
// oxlint-disable-next-line typescript/no-misused-spread
|
|
689
882
|
...tMain,
|
|
690
883
|
constraints: [
|
|
691
884
|
{
|