@supabase/pg-delta 1.0.0-alpha.17 → 1.0.0-alpha.19

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.
Files changed (33) hide show
  1. package/dist/core/expand-replace-dependencies.js +69 -0
  2. package/dist/core/objects/index/index.model.js +12 -2
  3. package/dist/core/objects/procedure/procedure.diff.js +33 -20
  4. package/dist/core/objects/rls-policy/changes/rls-policy.create.js +23 -0
  5. package/dist/core/objects/rls-policy/rls-policy.model.d.ts +49 -0
  6. package/dist/core/objects/rls-policy/rls-policy.model.js +122 -1
  7. package/dist/core/objects/table/table.diff.js +1 -0
  8. package/dist/core/objects/table/table.model.d.ts +4 -0
  9. package/dist/core/objects/table/table.model.js +2 -0
  10. package/dist/core/plan/sql-format/fixtures.js +8 -0
  11. package/dist/core/post-diff-cycle-breaking.d.ts +7 -0
  12. package/dist/core/post-diff-cycle-breaking.js +69 -3
  13. package/package.json +1 -1
  14. package/src/core/catalog.snapshot.test.ts +2 -0
  15. package/src/core/expand-replace-dependencies.test.ts +118 -0
  16. package/src/core/expand-replace-dependencies.ts +78 -0
  17. package/src/core/objects/index/index.model.test.ts +83 -0
  18. package/src/core/objects/index/index.model.ts +13 -4
  19. package/src/core/objects/procedure/procedure.diff.test.ts +100 -2
  20. package/src/core/objects/procedure/procedure.diff.ts +39 -21
  21. package/src/core/objects/rls-policy/changes/rls-policy.alter.test.ts +16 -0
  22. package/src/core/objects/rls-policy/changes/rls-policy.create.test.ts +128 -0
  23. package/src/core/objects/rls-policy/changes/rls-policy.create.ts +27 -0
  24. package/src/core/objects/rls-policy/changes/rls-policy.drop.test.ts +2 -0
  25. package/src/core/objects/rls-policy/rls-policy.diff.test.ts +2 -0
  26. package/src/core/objects/rls-policy/rls-policy.model.ts +134 -1
  27. package/src/core/objects/table/changes/table.alter.test.ts +1 -0
  28. package/src/core/objects/table/table.diff.test.ts +102 -0
  29. package/src/core/objects/table/table.diff.ts +1 -0
  30. package/src/core/objects/table/table.model.ts +2 -0
  31. package/src/core/plan/sql-format/fixtures.ts +8 -0
  32. package/src/core/post-diff-cycle-breaking.test.ts +142 -0
  33. package/src/core/post-diff-cycle-breaking.ts +83 -2
@@ -1,5 +1,7 @@
1
1
  import { CreateDomain } from "./objects/domain/changes/domain.create.js";
2
2
  import { DropDomain } from "./objects/domain/changes/domain.drop.js";
3
+ import { CreateIndex } from "./objects/index/changes/index.create.js";
4
+ import { DropIndex } from "./objects/index/changes/index.drop.js";
3
5
  import { CreateMaterializedView } from "./objects/materialized-view/changes/materialized-view.create.js";
4
6
  import { DropMaterializedView } from "./objects/materialized-view/changes/materialized-view.drop.js";
5
7
  import { CreateProcedure } from "./objects/procedure/changes/procedure.create.js";
@@ -32,6 +34,25 @@ export function expandReplaceDependencies({ changes, mainCatalog, branchCatalog,
32
34
  replaceRoots.add(id);
33
35
  }
34
36
  }
37
+ // Procedure stableIds are signature-qualified
38
+ // (`procedure:schema.name(argtypes)`), so a function whose parameter types
39
+ // change has different ids in `createdIds` and `droppedIds` and would not
40
+ // appear in the intersection above. Treat any dropped procedure whose
41
+ // `(schema, name)` matches a created procedure as a replace root so
42
+ // dependents referencing the old signature via pg_depend get promoted to
43
+ // DROP+CREATE.
44
+ const createdProcedureNames = new Set();
45
+ for (const id of createdIds) {
46
+ const key = parseProcedureSchemaName(id);
47
+ if (key)
48
+ createdProcedureNames.add(key);
49
+ }
50
+ for (const id of droppedIds) {
51
+ const key = parseProcedureSchemaName(id);
52
+ if (key && createdProcedureNames.has(key)) {
53
+ replaceRoots.add(id);
54
+ }
55
+ }
35
56
  if (replaceRoots.size === 0) {
36
57
  return {
37
58
  changes,
@@ -149,6 +170,14 @@ function isOwnedSequenceColumnDependency(referencedId, dependentId, mainCatalog,
149
170
  return (dependentId ===
150
171
  stableId.column(sequence.owned_by_schema, sequence.owned_by_table, sequence.owned_by_column));
151
172
  }
173
+ function parseProcedureSchemaName(stableId) {
174
+ if (!stableId.startsWith("procedure:"))
175
+ return null;
176
+ const paren = stableId.indexOf("(");
177
+ if (paren === -1)
178
+ return null;
179
+ return stableId.slice("procedure:".length, paren);
180
+ }
152
181
  function normalizeDependentId(dependentId) {
153
182
  let id = dependentId;
154
183
  while (id.startsWith("comment:")) {
@@ -195,6 +224,18 @@ function resolveObjectForStableId(stableId, mainCatalog, branchCatalog) {
195
224
  const branch = branchCatalog.materializedViews[stableId];
196
225
  return main && branch ? { kind: "materialized_view", main, branch } : null;
197
226
  }
227
+ if (stableId.startsWith("index:")) {
228
+ const main = mainCatalog.indexes[stableId];
229
+ const branch = branchCatalog.indexes[stableId];
230
+ return main && branch
231
+ ? {
232
+ kind: "index",
233
+ main,
234
+ branch,
235
+ branchIndexableObject: branchCatalog.indexableObjects[branch.tableStableId],
236
+ }
237
+ : null;
238
+ }
198
239
  if (stableId.startsWith("procedure:")) {
199
240
  const main = mainCatalog.procedures[stableId];
200
241
  const branch = branchCatalog.procedures[stableId];
@@ -280,6 +321,34 @@ function buildReplaceChanges(resolved, options) {
280
321
  ? [new CreateMaterializedView({ materializedView: resolved.branch })]
281
322
  : []),
282
323
  ];
324
+ case "index":
325
+ // Constraint-owned, primary, and partition-attached indexes are managed
326
+ // by the owning constraint or parent-index DDL, not standalone
327
+ // CREATE INDEX / DROP INDEX. The `case "table":` branch above already
328
+ // recreates constraints via AlterTableAddConstraint; emitting a
329
+ // standalone drop/create here would fail in PostgreSQL
330
+ // ("cannot drop index ... because constraint ... requires it") or
331
+ // duplicate the index the constraint recreates. Skip matches
332
+ // diffIndexes (packages/pg-delta/src/core/objects/index/index.diff.ts).
333
+ if (resolved.main.is_owned_by_constraint ||
334
+ resolved.main.is_primary ||
335
+ resolved.main.is_index_partition ||
336
+ resolved.branch.is_owned_by_constraint ||
337
+ resolved.branch.is_primary ||
338
+ resolved.branch.is_index_partition) {
339
+ return null;
340
+ }
341
+ return [
342
+ ...(addDrop ? [new DropIndex({ index: resolved.main })] : []),
343
+ ...(addCreate
344
+ ? [
345
+ new CreateIndex({
346
+ index: resolved.branch,
347
+ indexableObject: resolved.branchIndexableObject,
348
+ }),
349
+ ]
350
+ : []),
351
+ ];
283
352
  case "procedure":
284
353
  return [
285
354
  ...(addDrop ? [new DropProcedure({ procedure: resolved.main })] : []),
@@ -36,6 +36,15 @@ const indexPropsSchema = z.object({
36
36
  comment: z.string().nullable(),
37
37
  owner: z.string(),
38
38
  });
39
+ // pg_get_indexdef(oid, colno, pretty) invokes pg_get_indexdef_worker with
40
+ // missing_ok = true, so it can return NULL when any internal system-cache lookup
41
+ // fails (race with concurrent DROP, role visibility edge cases, orphaned index
42
+ // metadata, recovery transients). An unreadable index cannot be diffed, so we
43
+ // accept NULL here and filter the row out with a debug log instead of crashing
44
+ // the whole catalog extraction.
45
+ const indexRowSchema = indexPropsSchema.extend({
46
+ definition: z.string().nullable(),
47
+ });
39
48
  export class Index extends BasePgModel {
40
49
  schema;
41
50
  table_name;
@@ -332,7 +341,8 @@ export async function extractIndexes(pool) {
332
341
 
333
342
  order by 1, 2
334
343
  `);
335
- // Validate and parse each row using the Zod schema
336
- const validatedRows = indexRows.map((row) => indexPropsSchema.parse(row));
344
+ const validatedRows = indexRows
345
+ .map((row) => indexRowSchema.parse(row))
346
+ .filter((row) => row.definition !== null);
337
347
  return validatedRows.map((row) => new Index(row));
338
348
  }
@@ -17,8 +17,7 @@ import { GrantProcedurePrivileges, RevokeGrantOptionProcedurePrivileges, RevokeP
17
17
  export function diffProcedures(ctx, main, branch) {
18
18
  const { created, dropped, altered } = diffObjects(main, branch);
19
19
  const changes = [];
20
- for (const procedureId of created) {
21
- const proc = branch[procedureId];
20
+ const appendCreateProcedureChanges = (proc) => {
22
21
  changes.push(new CreateProcedure({ procedure: proc }));
23
22
  // OWNER: If the procedure should be owned by someone other than the current user,
24
23
  // emit ALTER FUNCTION/PROCEDURE ... OWNER TO after creation
@@ -53,6 +52,9 @@ export function diffProcedures(ctx, main, branch) {
53
52
  Revoke: RevokeProcedurePrivileges,
54
53
  RevokeGrantOption: RevokeGrantOptionProcedurePrivileges,
55
54
  }, ctx.version));
55
+ };
56
+ for (const procedureId of created) {
57
+ appendCreateProcedureChanges(branch[procedureId]);
56
58
  }
57
59
  for (const procedureId of dropped) {
58
60
  changes.push(new DropProcedure({ procedure: main[procedureId] }));
@@ -60,22 +62,18 @@ export function diffProcedures(ctx, main, branch) {
60
62
  for (const procedureId of altered) {
61
63
  const mainProcedure = main[procedureId];
62
64
  const branchProcedure = branch[procedureId];
63
- // Check if non-alterable properties have changed
64
- // These require dropping and recreating the procedure
65
- const NON_ALTERABLE_FIELDS = [
65
+ // Fields that are part of the function's identity/signature. PostgreSQL
66
+ // rejects `CREATE OR REPLACE FUNCTION` for any of these changes with
67
+ // errors such as:
68
+ // - cannot change return type of existing function
69
+ // - cannot change name of input parameter "..."
70
+ // - cannot change whether a procedure has output parameters
71
+ // - cannot remove parameter defaults from existing function
72
+ // These require `DROP FUNCTION` followed by `CREATE FUNCTION`.
73
+ const SIGNATURE_BREAKING_FIELDS = [
66
74
  "kind",
67
75
  "return_type",
68
76
  "return_type_schema",
69
- "language",
70
- // The following properties are alterable in SQL, but our generator may choose
71
- // to replace on changes not covered by explicit ALTER actions. Keep them out here
72
- // to allow ALTER for those we implement below.
73
- // security_definer,
74
- // volatility,
75
- // parallel_safety,
76
- // is_strict,
77
- // leakproof,
78
- // Returns-set is part of the signature and not alterable
79
77
  "returns_set",
80
78
  "argument_count",
81
79
  "argument_default_count",
@@ -84,20 +82,35 @@ export function diffProcedures(ctx, main, branch) {
84
82
  "all_argument_types",
85
83
  "argument_modes",
86
84
  "argument_defaults",
85
+ ];
86
+ // Fields where `CREATE OR REPLACE` is sufficient - body replacement only.
87
+ // Other fields (security_definer, volatility, parallel_safety, is_strict,
88
+ // leakproof, config) are alterable via dedicated ALTER actions below.
89
+ const OR_REPLACEABLE_NON_ALTERABLE_FIELDS = [
90
+ "language",
87
91
  "source_code",
88
92
  "binary_path",
89
93
  "sql_body",
90
- // config is alterable via SET/RESET
91
94
  ];
92
- const nonAlterablePropsChanged = hasNonAlterableChanges(mainProcedure, branchProcedure, NON_ALTERABLE_FIELDS, {
95
+ const signatureChanged = hasNonAlterableChanges(mainProcedure, branchProcedure, SIGNATURE_BREAKING_FIELDS, {
93
96
  argument_names: deepEqual,
94
97
  argument_types: deepEqual,
95
98
  all_argument_types: deepEqual,
96
99
  argument_modes: deepEqual,
97
- config: deepEqual,
98
100
  });
99
- if (nonAlterablePropsChanged) {
100
- // Replace the entire procedure
101
+ const nonAlterablePropsChanged = signatureChanged ||
102
+ hasNonAlterableChanges(mainProcedure, branchProcedure, OR_REPLACEABLE_NON_ALTERABLE_FIELDS);
103
+ if (signatureChanged) {
104
+ // PostgreSQL cannot change an existing function's signature via
105
+ // `CREATE OR REPLACE`. Drop the old signature, then recreate.
106
+ // `expandReplaceDependencies` will cascade the replacement to dependent
107
+ // objects (views, triggers, column defaults) via pg_depend edges.
108
+ changes.push(new DropProcedure({ procedure: mainProcedure }));
109
+ appendCreateProcedureChanges(branchProcedure);
110
+ }
111
+ else if (nonAlterablePropsChanged) {
112
+ // Body-only non-alterable change - `CREATE OR REPLACE` preserves the
113
+ // function OID and keeps dependent objects attached.
101
114
  changes.push(new CreateProcedure({ procedure: branchProcedure, orReplace: true }));
102
115
  if (mainProcedure.comment !== branchProcedure.comment) {
103
116
  if (branchProcedure.comment === null) {
@@ -33,6 +33,29 @@ export class CreateRlsPolicy extends CreateRlsPolicyChange {
33
33
  dependencies.add(stableId.table(this.policy.schema, this.policy.table_name));
34
34
  // Owner dependency
35
35
  dependencies.add(stableId.role(this.policy.owner));
36
+ // Relations and functions referenced inside USING / WITH CHECK
37
+ // expressions must exist before the policy is created. These come from
38
+ // pg_depend (populated by PostgreSQL's recordDependencyOnExpr at policy
39
+ // creation), not from re-parsing the expression text.
40
+ for (const ref of this.policy.referenced_relations) {
41
+ switch (ref.kind) {
42
+ case "table":
43
+ dependencies.add(stableId.table(ref.schema, ref.name));
44
+ break;
45
+ case "view":
46
+ dependencies.add(stableId.view(ref.schema, ref.name));
47
+ break;
48
+ case "materialized_view":
49
+ dependencies.add(stableId.materializedView(ref.schema, ref.name));
50
+ break;
51
+ case "foreign_table":
52
+ dependencies.add(stableId.foreignTable(ref.schema, ref.name));
53
+ break;
54
+ }
55
+ }
56
+ for (const ref of this.policy.referenced_procedures) {
57
+ dependencies.add(stableId.procedure(ref.schema, ref.name, ref.argument_types.join(",")));
58
+ }
36
59
  return Array.from(dependencies);
37
60
  }
38
61
  serialize(_options) {
@@ -1,6 +1,23 @@
1
1
  import type { Pool } from "pg";
2
2
  import z from "zod";
3
3
  import { BasePgModel } from "../base.model.ts";
4
+ declare const rlsPolicyReferencedRelationSchema: z.ZodObject<{
5
+ kind: z.ZodEnum<{
6
+ foreign_table: "foreign_table";
7
+ materialized_view: "materialized_view";
8
+ table: "table";
9
+ view: "view";
10
+ }>;
11
+ schema: z.ZodString;
12
+ name: z.ZodString;
13
+ }, z.z.core.$strip>;
14
+ export type RlsPolicyReferencedRelation = z.infer<typeof rlsPolicyReferencedRelationSchema>;
15
+ declare const rlsPolicyReferencedProcedureSchema: z.ZodObject<{
16
+ schema: z.ZodString;
17
+ name: z.ZodString;
18
+ argument_types: z.ZodArray<z.ZodString>;
19
+ }, z.z.core.$strip>;
20
+ export type RlsPolicyReferencedProcedure = z.infer<typeof rlsPolicyReferencedProcedureSchema>;
4
21
  declare const rlsPolicyPropsSchema: z.ZodObject<{
5
22
  schema: z.ZodString;
6
23
  name: z.ZodString;
@@ -18,6 +35,21 @@ declare const rlsPolicyPropsSchema: z.ZodObject<{
18
35
  with_check_expression: z.ZodNullable<z.ZodString>;
19
36
  owner: z.ZodString;
20
37
  comment: z.ZodNullable<z.ZodString>;
38
+ referenced_relations: z.ZodArray<z.ZodObject<{
39
+ kind: z.ZodEnum<{
40
+ foreign_table: "foreign_table";
41
+ materialized_view: "materialized_view";
42
+ table: "table";
43
+ view: "view";
44
+ }>;
45
+ schema: z.ZodString;
46
+ name: z.ZodString;
47
+ }, z.z.core.$strip>>;
48
+ referenced_procedures: z.ZodArray<z.ZodObject<{
49
+ schema: z.ZodString;
50
+ name: z.ZodString;
51
+ argument_types: z.ZodArray<z.ZodString>;
52
+ }, z.z.core.$strip>>;
21
53
  }, z.z.core.$strip>;
22
54
  export type RlsPolicyProps = z.infer<typeof rlsPolicyPropsSchema>;
23
55
  export declare class RlsPolicy extends BasePgModel {
@@ -31,6 +63,23 @@ export declare class RlsPolicy extends BasePgModel {
31
63
  readonly with_check_expression: RlsPolicyProps["with_check_expression"];
32
64
  readonly owner: RlsPolicyProps["owner"];
33
65
  readonly comment: RlsPolicyProps["comment"];
66
+ /**
67
+ * Tables / views / materialized views / foreign tables that
68
+ * `using_expression` / `with_check_expression` reference, sourced from
69
+ * `pg_depend` (`recordDependencyOnExpr` at policy creation). Drives
70
+ * ordering dependencies in `CreateRlsPolicy.requires`. Intentionally
71
+ * excluded from `dataFields` — it's derived from the expression text
72
+ * and changes lockstep with it.
73
+ */
74
+ readonly referenced_relations: RlsPolicyProps["referenced_relations"];
75
+ /**
76
+ * Functions / procedures that `using_expression` / `with_check_expression`
77
+ * reference, sourced from `pg_depend` (refclassid = `pg_proc`). The
78
+ * argument-type signature comes straight from `pg_proc.proargtypes` via
79
+ * `format_type`, so it matches the signature the procedure extractor
80
+ * embeds in `stableId.procedure(...)`. Not part of `dataFields`.
81
+ */
82
+ readonly referenced_procedures: RlsPolicyProps["referenced_procedures"];
34
83
  constructor(props: RlsPolicyProps);
35
84
  get stableId(): `rlsPolicy:${string}`;
36
85
  get identityFields(): {
@@ -8,6 +8,22 @@ const RlsPolicyCommandSchema = z.enum([
8
8
  "d", // DELETE command
9
9
  "*", // ALL commands
10
10
  ]);
11
+ const RlsPolicyReferencedRelationKindSchema = z.enum([
12
+ "table",
13
+ "view",
14
+ "materialized_view",
15
+ "foreign_table",
16
+ ]);
17
+ const rlsPolicyReferencedRelationSchema = z.object({
18
+ kind: RlsPolicyReferencedRelationKindSchema,
19
+ schema: z.string(),
20
+ name: z.string(),
21
+ });
22
+ const rlsPolicyReferencedProcedureSchema = z.object({
23
+ schema: z.string(),
24
+ name: z.string(),
25
+ argument_types: z.array(z.string()),
26
+ });
11
27
  const rlsPolicyPropsSchema = z.object({
12
28
  schema: z.string(),
13
29
  name: z.string(),
@@ -19,6 +35,8 @@ const rlsPolicyPropsSchema = z.object({
19
35
  with_check_expression: z.string().nullable(),
20
36
  owner: z.string(),
21
37
  comment: z.string().nullable(),
38
+ referenced_relations: z.array(rlsPolicyReferencedRelationSchema),
39
+ referenced_procedures: z.array(rlsPolicyReferencedProcedureSchema),
22
40
  });
23
41
  export class RlsPolicy extends BasePgModel {
24
42
  schema;
@@ -31,6 +49,23 @@ export class RlsPolicy extends BasePgModel {
31
49
  with_check_expression;
32
50
  owner;
33
51
  comment;
52
+ /**
53
+ * Tables / views / materialized views / foreign tables that
54
+ * `using_expression` / `with_check_expression` reference, sourced from
55
+ * `pg_depend` (`recordDependencyOnExpr` at policy creation). Drives
56
+ * ordering dependencies in `CreateRlsPolicy.requires`. Intentionally
57
+ * excluded from `dataFields` — it's derived from the expression text
58
+ * and changes lockstep with it.
59
+ */
60
+ referenced_relations;
61
+ /**
62
+ * Functions / procedures that `using_expression` / `with_check_expression`
63
+ * reference, sourced from `pg_depend` (refclassid = `pg_proc`). The
64
+ * argument-type signature comes straight from `pg_proc.proargtypes` via
65
+ * `format_type`, so it matches the signature the procedure extractor
66
+ * embeds in `stableId.procedure(...)`. Not part of `dataFields`.
67
+ */
68
+ referenced_procedures;
34
69
  constructor(props) {
35
70
  super();
36
71
  // Identity fields
@@ -45,6 +80,9 @@ export class RlsPolicy extends BasePgModel {
45
80
  this.with_check_expression = props.with_check_expression;
46
81
  this.owner = props.owner;
47
82
  this.comment = props.comment;
83
+ // Derived metadata (not part of equality)
84
+ this.referenced_relations = props.referenced_relations;
85
+ this.referenced_procedures = props.referenced_procedures;
48
86
  }
49
87
  get stableId() {
50
88
  return `rlsPolicy:${this.schema}.${this.table_name}.${this.name}`;
@@ -88,6 +126,59 @@ extension_table_oids as (
88
126
  d.refclassid = 'pg_extension'::regclass
89
127
  and d.classid = 'pg_class'::regclass
90
128
  and d.deptype = 'e'
129
+ ),
130
+ policy_relation_deps as (
131
+ -- Relations referenced inside polqual / polwithcheck. PostgreSQL records
132
+ -- these via recordDependencyOnExpr(..., DEPENDENCY_NORMAL = 'n') at
133
+ -- CREATE POLICY time, so pg_depend is authoritative and we don't need to
134
+ -- re-parse the expression text. Covers regular tables, partitioned
135
+ -- tables, views, materialized views, and foreign tables — any relation
136
+ -- kind the policy can reference in a subquery.
137
+ select distinct
138
+ d.objid as policy_oid,
139
+ case ref_c.relkind
140
+ when 'r' then 'table'
141
+ when 'p' then 'table'
142
+ when 'v' then 'view'
143
+ when 'm' then 'materialized_view'
144
+ when 'f' then 'foreign_table'
145
+ end as ref_kind,
146
+ ref_ns.nspname as ref_schema,
147
+ ref_c.relname as ref_name
148
+ from
149
+ pg_depend d
150
+ join pg_policy p on p.oid = d.objid
151
+ join pg_class ref_c on ref_c.oid = d.refobjid
152
+ join pg_namespace ref_ns on ref_ns.oid = ref_c.relnamespace
153
+ where
154
+ d.classid = 'pg_policy'::regclass
155
+ and d.refclassid = 'pg_class'::regclass
156
+ and d.deptype = 'n'
157
+ and ref_c.relkind in ('r', 'p', 'v', 'm', 'f')
158
+ and d.refobjid <> p.polrelid
159
+ ),
160
+ policy_procedure_deps as (
161
+ -- Functions / procedures referenced inside polqual / polwithcheck. Same
162
+ -- pg_depend mechanism as above, just refclassid = pg_proc. proargtypes
163
+ -- formatted via format_type(oid, null) matches the signature produced by
164
+ -- the procedure extractor (see procedure.model.ts), so stableId.procedure
165
+ -- on both sides of the diff lines up exactly.
166
+ select distinct
167
+ d.objid as policy_oid,
168
+ ref_ns.nspname as ref_schema,
169
+ ref_p.proname as ref_name,
170
+ array(
171
+ select format_type(oid, null)
172
+ from unnest(ref_p.proargtypes) as oid
173
+ ) as ref_argument_types
174
+ from
175
+ pg_depend d
176
+ join pg_proc ref_p on ref_p.oid = d.refobjid
177
+ join pg_namespace ref_ns on ref_ns.oid = ref_p.pronamespace
178
+ where
179
+ d.classid = 'pg_policy'::regclass
180
+ and d.refclassid = 'pg_proc'::regclass
181
+ and d.deptype = 'n'
91
182
  )
92
183
  select
93
184
  tc.relnamespace::regnamespace::text as schema,
@@ -107,7 +198,37 @@ select
107
198
  pg_get_expr(p.polqual, p.polrelid) as using_expression,
108
199
  pg_get_expr(p.polwithcheck, p.polrelid) as with_check_expression,
109
200
  tc.relowner::regrole::text as owner,
110
- obj_description(p.oid, 'pg_policy') as comment
201
+ obj_description(p.oid, 'pg_policy') as comment,
202
+ coalesce(
203
+ (
204
+ select json_agg(
205
+ json_build_object(
206
+ 'kind', prd.ref_kind,
207
+ 'schema', prd.ref_schema,
208
+ 'name', prd.ref_name
209
+ )
210
+ order by prd.ref_schema, prd.ref_name
211
+ )
212
+ from policy_relation_deps prd
213
+ where prd.policy_oid = p.oid
214
+ ),
215
+ '[]'
216
+ ) as referenced_relations,
217
+ coalesce(
218
+ (
219
+ select json_agg(
220
+ json_build_object(
221
+ 'schema', ppd.ref_schema,
222
+ 'name', ppd.ref_name,
223
+ 'argument_types', ppd.ref_argument_types
224
+ )
225
+ order by ppd.ref_schema, ppd.ref_name, ppd.ref_argument_types
226
+ )
227
+ from policy_procedure_deps ppd
228
+ where ppd.policy_oid = p.oid
229
+ ),
230
+ '[]'
231
+ ) as referenced_procedures
111
232
  from
112
233
  pg_catalog.pg_policy p
113
234
  inner join pg_catalog.pg_class tc on tc.oid = p.polrelid
@@ -66,6 +66,7 @@ function createAlterConstraintChange(mainTable, branchTable) {
66
66
  mainC.validated !== branchC.validated ||
67
67
  mainC.is_local !== branchC.is_local ||
68
68
  mainC.no_inherit !== branchC.no_inherit ||
69
+ mainC.is_temporal !== branchC.is_temporal ||
69
70
  JSON.stringify(mainC.key_columns) !==
70
71
  JSON.stringify(branchC.key_columns) ||
71
72
  JSON.stringify(mainC.foreign_key_columns) !==
@@ -23,6 +23,7 @@ declare const tableConstraintPropsSchema: z.ZodObject<{
23
23
  validated: z.ZodBoolean;
24
24
  is_local: z.ZodBoolean;
25
25
  no_inherit: z.ZodBoolean;
26
+ is_temporal: z.ZodBoolean;
26
27
  is_partition_clone: z.ZodBoolean;
27
28
  parent_constraint_schema: z.ZodNullable<z.ZodString>;
28
29
  parent_constraint_name: z.ZodNullable<z.ZodString>;
@@ -125,6 +126,7 @@ declare const tablePropsSchema: z.ZodObject<{
125
126
  validated: z.ZodBoolean;
126
127
  is_local: z.ZodBoolean;
127
128
  no_inherit: z.ZodBoolean;
129
+ is_temporal: z.ZodBoolean;
128
130
  is_partition_clone: z.ZodBoolean;
129
131
  parent_constraint_schema: z.ZodNullable<z.ZodString>;
130
132
  parent_constraint_name: z.ZodNullable<z.ZodString>;
@@ -239,6 +241,7 @@ export declare class Table extends BasePgModel implements TableLikeObject {
239
241
  validated: boolean;
240
242
  is_local: boolean;
241
243
  no_inherit: boolean;
244
+ is_temporal: boolean;
242
245
  is_partition_clone: boolean;
243
246
  parent_constraint_schema: string | null;
244
247
  parent_constraint_name: string | null;
@@ -300,6 +303,7 @@ export declare class Table extends BasePgModel implements TableLikeObject {
300
303
  validated: boolean;
301
304
  is_local: boolean;
302
305
  no_inherit: boolean;
306
+ is_temporal: boolean;
303
307
  is_partition_clone: boolean;
304
308
  parent_constraint_schema: string | null;
305
309
  parent_constraint_name: string | null;
@@ -42,6 +42,7 @@ const tableConstraintPropsSchema = z.object({
42
42
  validated: z.boolean(),
43
43
  is_local: z.boolean(),
44
44
  no_inherit: z.boolean(),
45
+ is_temporal: z.boolean(),
45
46
  is_partition_clone: z.boolean(),
46
47
  parent_constraint_schema: z.string().nullable(),
47
48
  parent_constraint_name: z.string().nullable(),
@@ -253,6 +254,7 @@ select
253
254
  'validated', c.convalidated,
254
255
  'is_local', c.conislocal,
255
256
  'no_inherit', c.connoinherit,
257
+ 'is_temporal', coalesce((to_jsonb(c)->>'conperiod')::boolean, false),
256
258
 
257
259
  -- NEW: propagated-to-partition tagging (PG15+)
258
260
  'is_partition_clone', (c.conparentid <> 0::oid),
@@ -294,6 +294,7 @@ const pkConstraint = {
294
294
  validated: true,
295
295
  is_local: true,
296
296
  no_inherit: false,
297
+ is_temporal: false,
297
298
  is_partition_clone: false,
298
299
  parent_constraint_schema: null,
299
300
  parent_constraint_name: null,
@@ -324,6 +325,7 @@ const uniqueConstraint = {
324
325
  validated: true,
325
326
  is_local: true,
326
327
  no_inherit: false,
328
+ is_temporal: false,
327
329
  is_partition_clone: false,
328
330
  parent_constraint_schema: null,
329
331
  parent_constraint_name: null,
@@ -353,6 +355,7 @@ const fkConstraint = {
353
355
  validated: true,
354
356
  is_local: true,
355
357
  no_inherit: false,
358
+ is_temporal: false,
356
359
  is_partition_clone: false,
357
360
  parent_constraint_schema: null,
358
361
  parent_constraint_name: null,
@@ -382,6 +385,7 @@ const checkConstraint = {
382
385
  validated: true,
383
386
  is_local: true,
384
387
  no_inherit: true,
388
+ is_temporal: false,
385
389
  is_partition_clone: false,
386
390
  parent_constraint_schema: null,
387
391
  parent_constraint_name: null,
@@ -692,6 +696,8 @@ const rlsPolicy = new RlsPolicy({
692
696
  with_check_expression: null,
693
697
  owner: "owner1",
694
698
  comment: "rls policy comment",
699
+ referenced_relations: [],
700
+ referenced_procedures: [],
695
701
  });
696
702
  const rlsPolicyRestrictive = new RlsPolicy({
697
703
  schema: "public",
@@ -704,6 +710,8 @@ const rlsPolicyRestrictive = new RlsPolicy({
704
710
  with_check_expression: "status <> 'locked'",
705
711
  owner: "owner1",
706
712
  comment: null,
713
+ referenced_relations: [],
714
+ referenced_procedures: [],
707
715
  });
708
716
  const index = new Index({
709
717
  schema: "public",
@@ -8,6 +8,13 @@ import type { Change } from "./change.types.ts";
8
8
  * - If replace expansion added `DropTable(T)+CreateTable(T)`, targeted
9
9
  * `AlterTableDropColumn(T.*)` / `AlterTableDropConstraint(T.*)` changes are
10
10
  * redundant and create an unbreakable drop-phase cycle, so we elide them.
11
+ * - When the same `DropTable+CreateTable` pair is present, the expansion
12
+ * also emits one `AlterTableAddConstraint` / `AlterTableValidateConstraint`
13
+ * / `CreateCommentOnConstraint` per branch constraint, which may collide
14
+ * with the same change already emitted by `diffTables()` (for example on a
15
+ * shape flip or a new constraint). We dedupe these keeping only the last
16
+ * occurrence so the expansion's emission survives and the diffTables
17
+ * duplicate is removed.
11
18
  * - If two dropped tables reference each other via FK, we insert dedicated
12
19
  * `AlterTableDropConstraint` changes and teach the paired `DropTable`
13
20
  * changes not to claim those FK stable IDs.