@supabase/pg-delta 1.0.0-alpha.22 → 1.0.0-alpha.24

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 (228) hide show
  1. package/dist/core/catalog.model.js +1 -0
  2. package/dist/core/integrations/filter/flatten.js +13 -0
  3. package/dist/core/objects/aggregate/aggregate.diff.js +16 -0
  4. package/dist/core/objects/aggregate/aggregate.model.d.ts +10 -0
  5. package/dist/core/objects/aggregate/aggregate.model.js +19 -1
  6. package/dist/core/objects/aggregate/changes/aggregate.base.d.ts +1 -1
  7. package/dist/core/objects/aggregate/changes/aggregate.security-label.d.ts +28 -0
  8. package/dist/core/objects/aggregate/changes/aggregate.security-label.js +64 -0
  9. package/dist/core/objects/aggregate/changes/aggregate.types.d.ts +2 -1
  10. package/dist/core/objects/base.model.d.ts +8 -0
  11. package/dist/core/objects/base.model.js +2 -0
  12. package/dist/core/objects/domain/changes/domain.base.d.ts +1 -1
  13. package/dist/core/objects/domain/changes/domain.security-label.d.ts +28 -0
  14. package/dist/core/objects/domain/changes/domain.security-label.js +61 -0
  15. package/dist/core/objects/domain/changes/domain.types.d.ts +2 -1
  16. package/dist/core/objects/domain/domain.diff.js +16 -0
  17. package/dist/core/objects/domain/domain.model.d.ts +10 -0
  18. package/dist/core/objects/domain/domain.model.js +19 -1
  19. package/dist/core/objects/event-trigger/changes/event-trigger.base.d.ts +1 -1
  20. package/dist/core/objects/event-trigger/changes/event-trigger.security-label.d.ts +28 -0
  21. package/dist/core/objects/event-trigger/changes/event-trigger.security-label.js +61 -0
  22. package/dist/core/objects/event-trigger/changes/event-trigger.types.d.ts +2 -1
  23. package/dist/core/objects/event-trigger/event-trigger.diff.js +16 -0
  24. package/dist/core/objects/event-trigger/event-trigger.model.d.ts +10 -0
  25. package/dist/core/objects/event-trigger/event-trigger.model.js +19 -1
  26. package/dist/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.base.d.ts +1 -1
  27. package/dist/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.security-label.d.ts +28 -0
  28. package/dist/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.security-label.js +61 -0
  29. package/dist/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.types.d.ts +2 -1
  30. package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.diff.js +16 -0
  31. package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.model.d.ts +22 -0
  32. package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.model.js +20 -1
  33. package/dist/core/objects/materialized-view/changes/materialized-view.base.d.ts +1 -1
  34. package/dist/core/objects/materialized-view/changes/materialized-view.security-label.d.ts +28 -0
  35. package/dist/core/objects/materialized-view/changes/materialized-view.security-label.js +61 -0
  36. package/dist/core/objects/materialized-view/changes/materialized-view.types.d.ts +2 -1
  37. package/dist/core/objects/materialized-view/materialized-view.diff.js +18 -0
  38. package/dist/core/objects/materialized-view/materialized-view.model.d.ts +22 -0
  39. package/dist/core/objects/materialized-view/materialized-view.model.js +20 -1
  40. package/dist/core/objects/procedure/changes/procedure.base.d.ts +1 -1
  41. package/dist/core/objects/procedure/changes/procedure.security-label.d.ts +28 -0
  42. package/dist/core/objects/procedure/changes/procedure.security-label.js +69 -0
  43. package/dist/core/objects/procedure/changes/procedure.types.d.ts +2 -1
  44. package/dist/core/objects/procedure/procedure.diff.js +16 -0
  45. package/dist/core/objects/procedure/procedure.model.d.ts +10 -0
  46. package/dist/core/objects/procedure/procedure.model.js +19 -1
  47. package/dist/core/objects/publication/changes/publication.base.d.ts +1 -1
  48. package/dist/core/objects/publication/changes/publication.security-label.d.ts +28 -0
  49. package/dist/core/objects/publication/changes/publication.security-label.js +61 -0
  50. package/dist/core/objects/publication/changes/publication.types.d.ts +2 -1
  51. package/dist/core/objects/publication/publication.diff.js +16 -0
  52. package/dist/core/objects/publication/publication.model.d.ts +14 -0
  53. package/dist/core/objects/publication/publication.model.js +20 -1
  54. package/dist/core/objects/role/changes/role.base.d.ts +1 -1
  55. package/dist/core/objects/role/changes/role.security-label.d.ts +28 -0
  56. package/dist/core/objects/role/changes/role.security-label.js +61 -0
  57. package/dist/core/objects/role/changes/role.types.d.ts +2 -1
  58. package/dist/core/objects/role/role.diff.js +16 -0
  59. package/dist/core/objects/role/role.model.d.ts +10 -0
  60. package/dist/core/objects/role/role.model.js +29 -0
  61. package/dist/core/objects/schema/changes/schema.base.d.ts +1 -1
  62. package/dist/core/objects/schema/changes/schema.security-label.d.ts +28 -0
  63. package/dist/core/objects/schema/changes/schema.security-label.js +61 -0
  64. package/dist/core/objects/schema/changes/schema.types.d.ts +2 -1
  65. package/dist/core/objects/schema/schema.diff.js +24 -1
  66. package/dist/core/objects/schema/schema.model.d.ts +10 -0
  67. package/dist/core/objects/schema/schema.model.js +18 -1
  68. package/dist/core/objects/security-label.types.d.ts +20 -0
  69. package/dist/core/objects/security-label.types.js +46 -0
  70. package/dist/core/objects/sequence/changes/sequence.base.d.ts +1 -1
  71. package/dist/core/objects/sequence/changes/sequence.security-label.d.ts +28 -0
  72. package/dist/core/objects/sequence/changes/sequence.security-label.js +61 -0
  73. package/dist/core/objects/sequence/changes/sequence.types.d.ts +2 -1
  74. package/dist/core/objects/sequence/sequence.diff.js +16 -0
  75. package/dist/core/objects/sequence/sequence.model.d.ts +10 -0
  76. package/dist/core/objects/sequence/sequence.model.js +19 -1
  77. package/dist/core/objects/subscription/changes/subscription.base.d.ts +1 -1
  78. package/dist/core/objects/subscription/changes/subscription.security-label.d.ts +28 -0
  79. package/dist/core/objects/subscription/changes/subscription.security-label.js +61 -0
  80. package/dist/core/objects/subscription/changes/subscription.types.d.ts +2 -1
  81. package/dist/core/objects/subscription/subscription.diff.js +16 -0
  82. package/dist/core/objects/subscription/subscription.model.d.ts +10 -0
  83. package/dist/core/objects/subscription/subscription.model.js +19 -1
  84. package/dist/core/objects/table/changes/table.base.d.ts +1 -1
  85. package/dist/core/objects/table/changes/table.security-label.d.ts +63 -0
  86. package/dist/core/objects/table/changes/table.security-label.js +134 -0
  87. package/dist/core/objects/table/changes/table.types.d.ts +2 -1
  88. package/dist/core/objects/table/table.diff.js +49 -0
  89. package/dist/core/objects/table/table.model.d.ts +30 -0
  90. package/dist/core/objects/table/table.model.js +34 -2
  91. package/dist/core/objects/type/composite-type/changes/composite-type.base.d.ts +1 -1
  92. package/dist/core/objects/type/composite-type/changes/composite-type.security-label.d.ts +28 -0
  93. package/dist/core/objects/type/composite-type/changes/composite-type.security-label.js +61 -0
  94. package/dist/core/objects/type/composite-type/changes/composite-type.types.d.ts +2 -1
  95. package/dist/core/objects/type/composite-type/composite-type.diff.js +16 -0
  96. package/dist/core/objects/type/composite-type/composite-type.model.d.ts +22 -0
  97. package/dist/core/objects/type/composite-type/composite-type.model.js +22 -2
  98. package/dist/core/objects/type/enum/changes/enum.base.d.ts +1 -1
  99. package/dist/core/objects/type/enum/changes/enum.security-label.d.ts +28 -0
  100. package/dist/core/objects/type/enum/changes/enum.security-label.js +61 -0
  101. package/dist/core/objects/type/enum/changes/enum.types.d.ts +2 -1
  102. package/dist/core/objects/type/enum/enum.diff.js +16 -0
  103. package/dist/core/objects/type/enum/enum.model.d.ts +10 -0
  104. package/dist/core/objects/type/enum/enum.model.js +20 -1
  105. package/dist/core/objects/type/range/changes/range.base.d.ts +1 -1
  106. package/dist/core/objects/type/range/changes/range.security-label.d.ts +28 -0
  107. package/dist/core/objects/type/range/changes/range.security-label.js +61 -0
  108. package/dist/core/objects/type/range/changes/range.types.d.ts +2 -1
  109. package/dist/core/objects/type/range/range.diff.js +16 -0
  110. package/dist/core/objects/type/range/range.model.d.ts +10 -0
  111. package/dist/core/objects/type/range/range.model.js +19 -1
  112. package/dist/core/objects/utils.d.ts +1 -0
  113. package/dist/core/objects/utils.js +3 -0
  114. package/dist/core/objects/view/changes/view.base.d.ts +1 -1
  115. package/dist/core/objects/view/changes/view.security-label.d.ts +28 -0
  116. package/dist/core/objects/view/changes/view.security-label.js +61 -0
  117. package/dist/core/objects/view/changes/view.types.d.ts +2 -1
  118. package/dist/core/objects/view/view.diff.js +13 -0
  119. package/dist/core/objects/view/view.model.d.ts +26 -0
  120. package/dist/core/objects/view/view.model.js +20 -1
  121. package/dist/core/plan/sql-format/fixtures.js +1 -0
  122. package/dist/core/post-diff-normalization.d.ts +7 -0
  123. package/dist/core/post-diff-normalization.js +33 -4
  124. package/dist/core/sort/cycle-breakers.js +139 -17
  125. package/package.json +1 -1
  126. package/src/core/catalog.model.ts +1 -0
  127. package/src/core/integrations/filter/dsl.test.ts +27 -0
  128. package/src/core/integrations/filter/flatten.ts +16 -0
  129. package/src/core/objects/aggregate/aggregate.diff.ts +33 -0
  130. package/src/core/objects/aggregate/aggregate.model.ts +22 -1
  131. package/src/core/objects/aggregate/changes/aggregate.base.ts +5 -1
  132. package/src/core/objects/aggregate/changes/aggregate.security-label.ts +99 -0
  133. package/src/core/objects/aggregate/changes/aggregate.types.ts +3 -1
  134. package/src/core/objects/base.model.ts +2 -0
  135. package/src/core/objects/domain/changes/domain.base.ts +5 -1
  136. package/src/core/objects/domain/changes/domain.security-label.test.ts +56 -0
  137. package/src/core/objects/domain/changes/domain.security-label.ts +77 -0
  138. package/src/core/objects/domain/changes/domain.types.ts +3 -1
  139. package/src/core/objects/domain/domain.diff.ts +33 -0
  140. package/src/core/objects/domain/domain.model.ts +22 -1
  141. package/src/core/objects/event-trigger/changes/event-trigger.base.ts +1 -1
  142. package/src/core/objects/event-trigger/changes/event-trigger.security-label.ts +95 -0
  143. package/src/core/objects/event-trigger/changes/event-trigger.types.ts +3 -1
  144. package/src/core/objects/event-trigger/event-trigger.diff.ts +33 -0
  145. package/src/core/objects/event-trigger/event-trigger.model.ts +22 -1
  146. package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.base.ts +5 -1
  147. package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.security-label.ts +95 -0
  148. package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.types.ts +3 -1
  149. package/src/core/objects/foreign-data-wrapper/foreign-table/foreign-table.diff.ts +33 -0
  150. package/src/core/objects/foreign-data-wrapper/foreign-table/foreign-table.model.ts +24 -1
  151. package/src/core/objects/materialized-view/changes/materialized-view.base.ts +5 -1
  152. package/src/core/objects/materialized-view/changes/materialized-view.security-label.test.ts +63 -0
  153. package/src/core/objects/materialized-view/changes/materialized-view.security-label.ts +95 -0
  154. package/src/core/objects/materialized-view/changes/materialized-view.types.ts +3 -1
  155. package/src/core/objects/materialized-view/materialized-view.diff.ts +37 -0
  156. package/src/core/objects/materialized-view/materialized-view.model.ts +25 -4
  157. package/src/core/objects/procedure/changes/procedure.base.ts +5 -1
  158. package/src/core/objects/procedure/changes/procedure.security-label.ts +105 -0
  159. package/src/core/objects/procedure/changes/procedure.types.ts +3 -1
  160. package/src/core/objects/procedure/procedure.diff.ts +33 -0
  161. package/src/core/objects/procedure/procedure.model.ts +23 -2
  162. package/src/core/objects/publication/changes/publication.base.ts +1 -1
  163. package/src/core/objects/publication/changes/publication.security-label.ts +95 -0
  164. package/src/core/objects/publication/changes/publication.types.ts +3 -1
  165. package/src/core/objects/publication/publication.diff.ts +33 -0
  166. package/src/core/objects/publication/publication.model.ts +24 -1
  167. package/src/core/objects/role/changes/role.base.ts +2 -1
  168. package/src/core/objects/role/changes/role.security-label.ts +77 -0
  169. package/src/core/objects/role/changes/role.types.ts +3 -1
  170. package/src/core/objects/role/role.diff.ts +33 -0
  171. package/src/core/objects/role/role.model.ts +32 -0
  172. package/src/core/objects/schema/changes/schema.alter.test.ts +1 -0
  173. package/src/core/objects/schema/changes/schema.base.ts +5 -1
  174. package/src/core/objects/schema/changes/schema.create.test.ts +1 -0
  175. package/src/core/objects/schema/changes/schema.drop.test.ts +1 -0
  176. package/src/core/objects/schema/changes/schema.security-label.test.ts +76 -0
  177. package/src/core/objects/schema/changes/schema.security-label.ts +77 -0
  178. package/src/core/objects/schema/changes/schema.types.ts +3 -1
  179. package/src/core/objects/schema/schema.diff.test.ts +1 -0
  180. package/src/core/objects/schema/schema.diff.ts +43 -1
  181. package/src/core/objects/schema/schema.model.ts +21 -1
  182. package/src/core/objects/security-label.types.test.ts +106 -0
  183. package/src/core/objects/security-label.types.ts +61 -0
  184. package/src/core/objects/sequence/changes/sequence.base.ts +5 -1
  185. package/src/core/objects/sequence/changes/sequence.security-label.test.ts +58 -0
  186. package/src/core/objects/sequence/changes/sequence.security-label.ts +92 -0
  187. package/src/core/objects/sequence/changes/sequence.types.ts +3 -1
  188. package/src/core/objects/sequence/sequence.diff.ts +33 -0
  189. package/src/core/objects/sequence/sequence.model.ts +22 -1
  190. package/src/core/objects/subscription/changes/subscription.base.ts +1 -1
  191. package/src/core/objects/subscription/changes/subscription.security-label.ts +95 -0
  192. package/src/core/objects/subscription/changes/subscription.types.ts +3 -1
  193. package/src/core/objects/subscription/subscription.diff.ts +33 -0
  194. package/src/core/objects/subscription/subscription.model.ts +22 -1
  195. package/src/core/objects/table/changes/table.base.ts +5 -1
  196. package/src/core/objects/table/changes/table.security-label.test.ts +140 -0
  197. package/src/core/objects/table/changes/table.security-label.ts +183 -0
  198. package/src/core/objects/table/changes/table.types.ts +3 -1
  199. package/src/core/objects/table/table.diff.ts +87 -0
  200. package/src/core/objects/table/table.model.ts +42 -2
  201. package/src/core/objects/type/composite-type/changes/composite-type.base.ts +5 -1
  202. package/src/core/objects/type/composite-type/changes/composite-type.security-label.ts +95 -0
  203. package/src/core/objects/type/composite-type/changes/composite-type.types.ts +3 -1
  204. package/src/core/objects/type/composite-type/composite-type.diff.ts +33 -0
  205. package/src/core/objects/type/composite-type/composite-type.model.ts +26 -2
  206. package/src/core/objects/type/enum/changes/enum.base.ts +5 -1
  207. package/src/core/objects/type/enum/changes/enum.security-label.ts +77 -0
  208. package/src/core/objects/type/enum/changes/enum.types.ts +3 -1
  209. package/src/core/objects/type/enum/enum.diff.ts +33 -0
  210. package/src/core/objects/type/enum/enum.model.ts +25 -1
  211. package/src/core/objects/type/range/changes/range.base.ts +5 -1
  212. package/src/core/objects/type/range/changes/range.security-label.ts +77 -0
  213. package/src/core/objects/type/range/changes/range.types.ts +3 -1
  214. package/src/core/objects/type/range/range.diff.ts +33 -0
  215. package/src/core/objects/type/range/range.model.ts +22 -1
  216. package/src/core/objects/utils.ts +3 -0
  217. package/src/core/objects/view/changes/view.base.ts +5 -1
  218. package/src/core/objects/view/changes/view.security-label.test.ts +64 -0
  219. package/src/core/objects/view/changes/view.security-label.ts +77 -0
  220. package/src/core/objects/view/changes/view.types.ts +3 -1
  221. package/src/core/objects/view/view.diff.ts +31 -0
  222. package/src/core/objects/view/view.model.ts +25 -2
  223. package/src/core/plan/sql-format/fixtures.ts +1 -0
  224. package/src/core/post-diff-normalization.test.ts +123 -0
  225. package/src/core/post-diff-normalization.ts +40 -4
  226. package/src/core/sort/cycle-breakers.test.ts +236 -2
  227. package/src/core/sort/cycle-breakers.ts +184 -24
  228. package/src/core/sort/sort-changes.test.ts +317 -0
@@ -3,6 +3,7 @@ import z from "zod";
3
3
  import { BasePgModel, columnPropsSchema, normalizeColumns, } from "../base.model.js";
4
4
  import { privilegePropsSchema, } from "../base.privilege-diff.js";
5
5
  import { extractWithDefinitionRetry, } from "../extract-with-retry.js";
6
+ import { normalizeSecurityLabels, securityLabelPropsSchema, } from "../security-label.types.js";
6
7
  import { ReplicaIdentitySchema } from "../table/table.model.js";
7
8
  const viewPropsSchema = z.object({
8
9
  schema: z.string(),
@@ -23,6 +24,7 @@ const viewPropsSchema = z.object({
23
24
  comment: z.string().nullable(),
24
25
  columns: z.array(columnPropsSchema),
25
26
  privileges: z.array(privilegePropsSchema),
27
+ security_labels: z.array(securityLabelPropsSchema).default([]).optional(),
26
28
  });
27
29
  // pg_get_viewdef(oid) can return NULL when the underlying view (or its
28
30
  // pg_rewrite row) is dropped between catalog scan and resolution, or under
@@ -51,6 +53,7 @@ export class View extends BasePgModel {
51
53
  comment;
52
54
  columns;
53
55
  privileges;
56
+ security_labels;
54
57
  constructor(props) {
55
58
  super();
56
59
  // Identity fields
@@ -73,6 +76,7 @@ export class View extends BasePgModel {
73
76
  this.comment = props.comment;
74
77
  this.columns = props.columns;
75
78
  this.privileges = props.privileges;
79
+ this.security_labels = props.security_labels ?? [];
76
80
  }
77
81
  get stableId() {
78
82
  return `view:${this.schema}.${this.name}`;
@@ -101,6 +105,7 @@ export class View extends BasePgModel {
101
105
  comment: this.comment,
102
106
  columns: this.columns,
103
107
  privileges: this.privileges,
108
+ security_labels: this.security_labels,
104
109
  };
105
110
  }
106
111
  stableSnapshot() {
@@ -109,6 +114,7 @@ export class View extends BasePgModel {
109
114
  data: {
110
115
  ...this.dataFields,
111
116
  columns: normalizeColumns(this.columns),
117
+ security_labels: normalizeSecurityLabels(this.security_labels),
112
118
  },
113
119
  };
114
120
  }
@@ -235,7 +241,20 @@ select
235
241
  join lateral aclexplode(src.acl) as x(grantor, grantee, privilege_type, is_grantable) on true
236
242
  group by x.grantee, x.privilege_type
237
243
  ) as grp
238
- ), '[]') as privileges
244
+ ), '[]') as privileges,
245
+ coalesce(
246
+ (
247
+ select json_agg(
248
+ json_build_object('provider', sl.provider, 'label', sl.label)
249
+ order by sl.provider
250
+ )
251
+ from pg_catalog.pg_seclabel sl
252
+ where sl.objoid = v.oid
253
+ and sl.classoid = 'pg_class'::regclass
254
+ and sl.objsubid = 0
255
+ ),
256
+ '[]'::json
257
+ ) as security_labels
239
258
  from
240
259
  views v
241
260
  left join pg_attribute a on a.attrelid = v.oid and a.attnum > 0 and not a.attisdropped
@@ -806,6 +806,7 @@ const schema = new Schema({
806
806
  owner: "admin",
807
807
  comment: "application schema",
808
808
  privileges: [],
809
+ security_labels: [],
809
810
  });
810
811
  const extension = new Extension({
811
812
  name: "pgcrypto",
@@ -15,6 +15,13 @@ import type { Table } from "./objects/table/table.model.ts";
15
15
  * `DropTable(T) + CreateTable(T)` pair. Without this, the apply phase
16
16
  * would try to drop a column that no longer exists in the freshly
17
17
  * recreated table.
18
+ * - Prunes `DropSequence(S)` changes when `S` is `OWNED BY` a column on a
19
+ * table promoted to `DropTable + CreateTable` by the expander. The
20
+ * `DROP TABLE` cascade drops the sequence at apply time; emitting an
21
+ * explicit `DROP SEQUENCE` in the same drop phase both duplicates the
22
+ * cascade and forms an unbreakable `DropSequence ↔ DropTable` cycle on
23
+ * the bidirectional pg_depend edges between the sequence and the
24
+ * owning column.
18
25
  * - Dedupes duplicate `AlterTableAddConstraint` /
19
26
  * `AlterTableValidateConstraint` / `CreateCommentOnConstraint` changes
20
27
  * produced when `diffTables()` and `expandReplaceDependencies()` both
@@ -1,5 +1,6 @@
1
1
  import { CreateIndex } from "./objects/index/changes/index.create.js";
2
2
  import { DropIndex } from "./objects/index/changes/index.drop.js";
3
+ import { DropSequence } from "./objects/sequence/changes/sequence.drop.js";
3
4
  import { AlterTableAddConstraint, AlterTableDropColumn, AlterTableDropConstraint, AlterTableSetReplicaIdentity, AlterTableValidateConstraint, } from "./objects/table/changes/table.alter.js";
4
5
  import { CreateCommentOnConstraint } from "./objects/table/changes/table.comment.js";
5
6
  import { stableId } from "./objects/utils.js";
@@ -7,11 +8,32 @@ function constraintStableId(table, constraintName) {
7
8
  return stableId.constraint(table.schema, table.name, constraintName);
8
9
  }
9
10
  function isSupersededByTableReplacement(change, replacedTableIds) {
10
- if (!(change instanceof AlterTableDropColumn) &&
11
- !(change instanceof AlterTableDropConstraint)) {
12
- return false;
11
+ if (change instanceof AlterTableDropColumn ||
12
+ change instanceof AlterTableDropConstraint) {
13
+ return replacedTableIds.has(change.table.stableId);
13
14
  }
14
- return replacedTableIds.has(change.table.stableId);
15
+ // `DropSequence(S)` is superseded when S is OWNED BY a column on a table
16
+ // that `expandReplaceDependencies` has promoted to `DropTable + CreateTable`
17
+ // in the same plan. PostgreSQL cascade-drops the OWNED BY sequence as part
18
+ // of the DROP TABLE, so the explicit DROP SEQUENCE is redundant and — more
19
+ // importantly — closes an unbreakable `DropSequence ↔ DropTable` cycle in
20
+ // the drop phase via the bidirectional pg_depend edges between the
21
+ // sequence and its owning column (`column → sequence` for the DEFAULT
22
+ // nextval reference, `sequence → column` for the OWNED BY auto-dependency).
23
+ // The alpha.15 short-circuit in `diffSequences.dropped` only suppresses
24
+ // `DropSequence` when the owning table itself is gone from `branchTables`;
25
+ // here the table survives in branch and the replacement is added later by
26
+ // the expander, so this whole-plan rewrite has to happen post-diff.
27
+ if (change instanceof DropSequence) {
28
+ if (!change.sequence.owned_by_schema ||
29
+ !change.sequence.owned_by_table ||
30
+ !change.sequence.owned_by_column) {
31
+ return false;
32
+ }
33
+ const ownedByTableId = stableId.table(change.sequence.owned_by_schema, change.sequence.owned_by_table);
34
+ return replacedTableIds.has(ownedByTableId);
35
+ }
36
+ return false;
15
37
  }
16
38
  /**
17
39
  * Drop earlier duplicates of `AlterTableAddConstraint` /
@@ -179,6 +201,13 @@ function restoreReplicaIdentityAfterIndexReplace(changes, branchTables) {
179
201
  * `DropTable(T) + CreateTable(T)` pair. Without this, the apply phase
180
202
  * would try to drop a column that no longer exists in the freshly
181
203
  * recreated table.
204
+ * - Prunes `DropSequence(S)` changes when `S` is `OWNED BY` a column on a
205
+ * table promoted to `DropTable + CreateTable` by the expander. The
206
+ * `DROP TABLE` cascade drops the sequence at apply time; emitting an
207
+ * explicit `DROP SEQUENCE` in the same drop phase both duplicates the
208
+ * cascade and forms an unbreakable `DropSequence ↔ DropTable` cycle on
209
+ * the bidirectional pg_depend edges between the sequence and the
210
+ * owning column.
182
211
  * - Dedupes duplicate `AlterTableAddConstraint` /
183
212
  * `AlterTableValidateConstraint` / `CreateCommentOnConstraint` changes
184
213
  * produced when `diffTables()` and `expandReplaceDependencies()` both
@@ -66,6 +66,32 @@ export function tryBreakCycleByChangeInjection(cycleNodeIndexes, phaseChanges) {
66
66
  const pubColBroken = tryBreakPublicationColumnCycle(cycleNodeIndexes, phaseChanges);
67
67
  if (pubColBroken)
68
68
  return pubColBroken;
69
+ // ─── Branch C: Publication ↔ dropped FK chain ↔ constraint drop ──────
70
+ // Triggered when publication membership is being removed for tables in
71
+ // the same drop phase as a FK chain, and the chain ends at a separately
72
+ // emitted `AlterTableDropConstraint` on a table that is also being
73
+ // removed from the publication.
74
+ //
75
+ // Example (4-change cycle):
76
+ // AlterPublicationDropTables(p, [labs, posts, post_attachments])
77
+ // DropTable(post_attachments)
78
+ // DropTable(posts)
79
+ // AlterTableDropConstraint(labs.unique_lab_id)
80
+ //
81
+ // Cycle:
82
+ // publication:p → table:post_attachments
83
+ // post_attachments.post_id_fkey → column:posts.id
84
+ // posts.lab_id_fkey → constraint:labs.unique_lab_id
85
+ // constraint:labs.unique_lab_id → table:labs
86
+ //
87
+ // Fix: inject explicit FK drops for the FK constraints claimed by the
88
+ // DropTables in the cycle, including FKs that point at the terminal
89
+ // dropped constraint. The publication and terminal constraint changes
90
+ // stay unchanged; only the intermediate FK ownership is reassigned from
91
+ // DropTable to dedicated AlterTableDropConstraint changes.
92
+ const pubFkConstraintBroken = tryBreakPublicationFkConstraintDropCycle(cycleNodeIndexes, phaseChanges);
93
+ if (pubFkConstraintBroken)
94
+ return pubFkConstraintBroken;
69
95
  // No known pattern. Returning null lets sortPhaseChanges throw the
70
96
  // formatted CycleError with full diagnostic — better a clear bug
71
97
  // report than silently shipping a broken plan.
@@ -90,6 +116,19 @@ function tryBreakFkCycle(cycleNodeIndexes, phaseChanges) {
90
116
  cycleDropTables.push(change);
91
117
  }
92
118
  const cycleTableIds = new Set(cycleDropTables.map((change) => change.table.stableId));
119
+ return injectFkConstraintDropsForDropTables({
120
+ phaseChanges,
121
+ dropTables: cycleDropTables,
122
+ shouldInject: (fk, tableId) => isCrossCycleFkConstraint(fk, tableId, cycleTableIds),
123
+ });
124
+ }
125
+ /**
126
+ * Shared FK-drop injection used by Branch A and Branch C. The caller owns
127
+ * the cycle-specific matcher; this helper only handles the mechanical
128
+ * rewrite: add dedicated `AlterTableDropConstraint` changes and rebuild
129
+ * affected `DropTable`s with updated `externallyDroppedConstraints`.
130
+ */
131
+ function injectFkConstraintDropsForDropTables({ phaseChanges, dropTables, shouldInject, }) {
93
132
  // For each DropTable in the cycle, find every FK whose referenced table
94
133
  // is also in the cycle. Each such FK becomes one injected
95
134
  // `AlterTableDropConstraint` and one entry on the source table's
@@ -100,11 +139,13 @@ function tryBreakFkCycle(cycleNodeIndexes, phaseChanges) {
100
139
  const injectedDropsByTableId = new Map();
101
140
  const updatedExternalsByTableId = new Map();
102
141
  let didMutate = false;
103
- for (const dropTable of cycleDropTables) {
142
+ for (const dropTable of dropTables) {
104
143
  const tableId = dropTable.table.stableId;
105
144
  const existingExternals = new Set(dropTable.externallyDroppedConstraints);
106
145
  let tableMutated = false;
107
- for (const fk of iterCrossCycleFkConstraints(dropTable.table.constraints, tableId, cycleTableIds)) {
146
+ for (const fk of iterFkConstraints(dropTable.table.constraints)) {
147
+ if (!shouldInject(fk, tableId))
148
+ continue;
108
149
  // Skip if a same-table `AlterTableDropConstraint` is already in the
109
150
  // change list — could happen if a previous breaker iteration
110
151
  // injected one, or the diff layer emitted one explicitly.
@@ -159,31 +200,37 @@ function tryBreakFkCycle(cycleNodeIndexes, phaseChanges) {
159
200
  return rewritten;
160
201
  }
161
202
  /**
162
- * Yield FK constraints on `constraints` whose referenced table is also a
163
- * member of the cycle (i.e. an FK strictly between two cycle DropTables).
203
+ * Yield FK constraints on `constraints`.
164
204
  *
165
- * Self-referencing FKs are skipped they create a self-loop in the
166
- * dependency graph which the existing sort-phase handler resolves on its
167
- * own; injecting an `AlterTableDropConstraint` for a self-FK would just
168
- * add noise.
205
+ * Partition clones are skipped because PostgreSQL drops them when the
206
+ * parent constraint is dropped.
169
207
  */
170
- function* iterCrossCycleFkConstraints(constraints, ownTableId, cycleTableIds) {
208
+ function* iterFkConstraints(constraints) {
171
209
  for (const constraint of constraints) {
172
210
  if (constraint.constraint_type !== "f")
173
211
  continue;
174
212
  if (constraint.is_partition_clone)
175
213
  continue;
176
- if (!constraint.foreign_key_schema || !constraint.foreign_key_table) {
177
- continue;
178
- }
179
- const referencedId = stableId.table(constraint.foreign_key_schema, constraint.foreign_key_table);
180
- if (referencedId === ownTableId)
181
- continue;
182
- if (!cycleTableIds.has(referencedId))
183
- continue;
184
214
  yield constraint;
185
215
  }
186
216
  }
217
+ /**
218
+ * True when `constraint` references another DropTable in the cycle.
219
+ *
220
+ * Self-referencing FKs are skipped — they create a self-loop in the
221
+ * dependency graph which the existing sort-phase handler resolves on its
222
+ * own; injecting an `AlterTableDropConstraint` for a self-FK would just
223
+ * add noise.
224
+ */
225
+ function isCrossCycleFkConstraint(constraint, ownTableId, cycleTableIds) {
226
+ if (!constraint.foreign_key_schema || !constraint.foreign_key_table) {
227
+ return false;
228
+ }
229
+ const referencedId = stableId.table(constraint.foreign_key_schema, constraint.foreign_key_table);
230
+ if (referencedId === ownTableId)
231
+ return false;
232
+ return cycleTableIds.has(referencedId);
233
+ }
187
234
  /**
188
235
  * True iff `phaseChanges` already contains an explicit
189
236
  * `AlterTableDropConstraint(table, constraint)` for the given pair —
@@ -267,3 +314,78 @@ function tryBreakPublicationColumnCycle(cycleNodeIndexes, phaseChanges) {
267
314
  });
268
315
  return rewritten;
269
316
  }
317
+ /**
318
+ * Branch C worker — break a publication membership removal cycle where
319
+ * dropped tables form a FK chain ending at a separately dropped referenced
320
+ * constraint.
321
+ */
322
+ function tryBreakPublicationFkConstraintDropCycle(cycleNodeIndexes, phaseChanges) {
323
+ let pubChange = null;
324
+ let terminalConstraintDrop = null;
325
+ const dropTables = [];
326
+ for (const nodeIndex of cycleNodeIndexes) {
327
+ const change = phaseChanges[nodeIndex];
328
+ if (change instanceof AlterPublicationDropTables) {
329
+ if (pubChange !== null)
330
+ return null;
331
+ pubChange = change;
332
+ }
333
+ else if (change instanceof AlterTableDropConstraint) {
334
+ if (terminalConstraintDrop !== null)
335
+ return null;
336
+ terminalConstraintDrop = change;
337
+ }
338
+ else if (change instanceof DropTable) {
339
+ dropTables.push(change);
340
+ }
341
+ else {
342
+ return null;
343
+ }
344
+ }
345
+ if (pubChange === null ||
346
+ terminalConstraintDrop === null ||
347
+ dropTables.length === 0) {
348
+ return null;
349
+ }
350
+ const publicationTableIds = new Set(pubChange.tables.map((table) => stableId.table(table.schema, table.name)));
351
+ if (!publicationTableIds.has(terminalConstraintDrop.table.stableId)) {
352
+ return null;
353
+ }
354
+ for (const dropTable of dropTables) {
355
+ if (!publicationTableIds.has(dropTable.table.stableId))
356
+ return null;
357
+ }
358
+ const cycleDropTableIds = new Set(dropTables.map((change) => change.table.stableId));
359
+ let hasFkToTerminalConstraint = false;
360
+ for (const dropTable of dropTables) {
361
+ for (const fk of iterFkConstraints(dropTable.table.constraints)) {
362
+ if (fkReferencesConstraint(fk, terminalConstraintDrop)) {
363
+ hasFkToTerminalConstraint = true;
364
+ break;
365
+ }
366
+ }
367
+ if (hasFkToTerminalConstraint)
368
+ break;
369
+ }
370
+ if (!hasFkToTerminalConstraint)
371
+ return null;
372
+ return injectFkConstraintDropsForDropTables({
373
+ phaseChanges,
374
+ dropTables,
375
+ shouldInject: (fk, tableId) => isCrossCycleFkConstraint(fk, tableId, cycleDropTableIds) ||
376
+ fkReferencesConstraint(fk, terminalConstraintDrop),
377
+ });
378
+ }
379
+ function fkReferencesConstraint(fk, constraintDrop) {
380
+ if (fk.foreign_key_schema !== constraintDrop.table.schema ||
381
+ fk.foreign_key_table !== constraintDrop.table.name ||
382
+ fk.foreign_key_columns === null) {
383
+ return false;
384
+ }
385
+ return sameOrderedStrings(fk.foreign_key_columns, constraintDrop.constraint.key_columns);
386
+ }
387
+ function sameOrderedStrings(left, right) {
388
+ if (left.length !== right.length)
389
+ return false;
390
+ return left.every((value, index) => value === right[index]);
391
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supabase/pg-delta",
3
- "version": "1.0.0-alpha.22",
3
+ "version": "1.0.0-alpha.24",
4
4
  "description": "PostgreSQL migrations made easy",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -267,6 +267,7 @@ export async function createEmptyCatalog(
267
267
  owner: currentUser,
268
268
  comment: "standard public schema",
269
269
  privileges: [],
270
+ security_labels: [],
270
271
  });
271
272
 
272
273
  return new Catalog({
@@ -41,6 +41,16 @@ const membershipChange = {
41
41
  requires: [],
42
42
  } as unknown as Change;
43
43
 
44
+ const securityLabelChange = {
45
+ objectType: "schema",
46
+ operation: "create",
47
+ scope: "security_label",
48
+ schema: { name: "labeled" },
49
+ securityLabel: { provider: "dummy", label: "classified" },
50
+ requires: ["schema:labeled"],
51
+ creates: ["security_label:schema:labeled:dummy"],
52
+ } as unknown as Change;
53
+
44
54
  describe("evaluatePattern", () => {
45
55
  describe("bare key matching (top-level properties)", () => {
46
56
  test("objectType match", () => {
@@ -242,6 +252,23 @@ describe("evaluatePattern", () => {
242
252
  });
243
253
  });
244
254
 
255
+ describe("security label matching", () => {
256
+ test("provider matches security label changes", () => {
257
+ expect(
258
+ evaluatePattern(
259
+ { scope: "security_label", provider: "dummy" },
260
+ securityLabelChange,
261
+ ),
262
+ ).toBe(true);
263
+ expect(
264
+ evaluatePattern(
265
+ { scope: "security_label", provider: "other" },
266
+ securityLabelChange,
267
+ ),
268
+ ).toBe(false);
269
+ });
270
+ });
271
+
245
272
  describe("composition patterns", () => {
246
273
  test("not negates a pattern", () => {
247
274
  expect(
@@ -105,6 +105,22 @@ export function flattenChange(change: Change): Record<string, FlatValue> {
105
105
  flat[`${prefix}/${subKey}`] = flatVal;
106
106
  }
107
107
  }
108
+ } else if (
109
+ key === "securityLabel" &&
110
+ value &&
111
+ typeof value === "object" &&
112
+ !Array.isArray(value)
113
+ ) {
114
+ // Security labels are change-level metadata, so expose provider/label as
115
+ // bare keys for filters like { scope: "security_label", provider: "..." }.
116
+ for (const [subKey, subValue] of Object.entries(
117
+ value as Record<string, unknown>,
118
+ )) {
119
+ const flatVal = toFlatValue(subValue);
120
+ if (flatVal !== undefined) {
121
+ flat[subKey] = flatVal;
122
+ }
123
+ }
108
124
  } else {
109
125
  const flatVal = toFlatValue(value);
110
126
  if (flatVal !== undefined) {
@@ -5,6 +5,7 @@ import {
5
5
  filterPublicBuiltInDefaults,
6
6
  } from "../base.privilege-diff.ts";
7
7
  import type { ObjectDiffContext } from "../diff-context.ts";
8
+ import { diffSecurityLabels } from "../security-label.types.ts";
8
9
  import { deepEqual, hasNonAlterableChanges } from "../utils.ts";
9
10
  import type { Aggregate } from "./aggregate.model.ts";
10
11
  import { AlterAggregateChangeOwner } from "./changes/aggregate.alter.ts";
@@ -19,6 +20,10 @@ import {
19
20
  RevokeAggregatePrivileges,
20
21
  RevokeGrantOptionAggregatePrivileges,
21
22
  } from "./changes/aggregate.privilege.ts";
23
+ import {
24
+ CreateSecurityLabelOnAggregate,
25
+ DropSecurityLabelOnAggregate,
26
+ } from "./changes/aggregate.security-label.ts";
22
27
  import type { AggregateChange } from "./changes/aggregate.types.ts";
23
28
 
24
29
  export function diffAggregates(
@@ -51,6 +56,14 @@ export function diffAggregates(
51
56
  if (aggregate.comment !== null) {
52
57
  changes.push(new CreateCommentOnAggregate({ aggregate }));
53
58
  }
59
+ for (const label of aggregate.security_labels) {
60
+ changes.push(
61
+ new CreateSecurityLabelOnAggregate({
62
+ aggregate,
63
+ securityLabel: label,
64
+ }),
65
+ );
66
+ }
54
67
 
55
68
  // PRIVILEGES: For created objects, compare against default privileges state
56
69
  // The migration script will run ALTER DEFAULT PRIVILEGES before CREATE (via constraint spec),
@@ -182,6 +195,26 @@ export function diffAggregates(
182
195
  }
183
196
  }
184
197
 
198
+ // SECURITY LABELS
199
+ changes.push(
200
+ ...diffSecurityLabels<
201
+ CreateSecurityLabelOnAggregate | DropSecurityLabelOnAggregate
202
+ >(
203
+ mainAggregate.security_labels,
204
+ branchAggregate.security_labels,
205
+ (securityLabel) =>
206
+ new CreateSecurityLabelOnAggregate({
207
+ aggregate: branchAggregate,
208
+ securityLabel,
209
+ }),
210
+ (securityLabel) =>
211
+ new DropSecurityLabelOnAggregate({
212
+ aggregate: mainAggregate,
213
+ securityLabel,
214
+ }),
215
+ ),
216
+ );
217
+
185
218
  // PRIVILEGES
186
219
  // Filter out PUBLIC's built-in default EXECUTE privilege from main catalog
187
220
  // (PostgreSQL grants it automatically, so we shouldn't compare it)
@@ -6,6 +6,10 @@ import {
6
6
  type PrivilegeProps,
7
7
  privilegePropsSchema,
8
8
  } from "../base.privilege-diff.ts";
9
+ import {
10
+ type SecurityLabelProps,
11
+ securityLabelPropsSchema,
12
+ } from "../security-label.types.ts";
9
13
 
10
14
  const AggregateKindSchema = z.enum([
11
15
  "n", // normal aggregate
@@ -75,6 +79,7 @@ const aggregatePropsSchema = z.object({
75
79
  owner: z.string(),
76
80
  comment: z.string().nullable(),
77
81
  privileges: z.array(privilegePropsSchema),
82
+ security_labels: z.array(securityLabelPropsSchema).default([]).optional(),
78
83
  });
79
84
 
80
85
  type AggregatePrivilegeProps = PrivilegeProps;
@@ -122,6 +127,7 @@ export class Aggregate extends BasePgModel {
122
127
  public readonly owner: AggregateProps["owner"];
123
128
  public readonly comment: AggregateProps["comment"];
124
129
  public readonly privileges: AggregatePrivilegeProps[];
130
+ public readonly security_labels: SecurityLabelProps[];
125
131
 
126
132
  constructor(props: AggregateProps) {
127
133
  super();
@@ -168,6 +174,7 @@ export class Aggregate extends BasePgModel {
168
174
  this.owner = props.owner;
169
175
  this.comment = props.comment;
170
176
  this.privileges = props.privileges;
177
+ this.security_labels = props.security_labels ?? [];
171
178
  }
172
179
 
173
180
  get stableId(): `aggregate:${string}` {
@@ -224,6 +231,7 @@ export class Aggregate extends BasePgModel {
224
231
  owner: this.owner,
225
232
  comment: this.comment,
226
233
  privileges: this.privileges,
234
+ security_labels: this.security_labels,
227
235
  };
228
236
  }
229
237
  }
@@ -294,7 +302,20 @@ select
294
302
  )
295
303
  from lateral aclexplode(COALESCE(p.proacl, acldefault('f', p.proowner))) as x(grantor, grantee, privilege_type, is_grantable)
296
304
  ), '[]'
297
- ) as privileges
305
+ ) as privileges,
306
+ coalesce(
307
+ (
308
+ select json_agg(
309
+ json_build_object('provider', sl.provider, 'label', sl.label)
310
+ order by sl.provider
311
+ )
312
+ from pg_catalog.pg_seclabel sl
313
+ where sl.objoid = p.oid
314
+ and sl.classoid = 'pg_proc'::regclass
315
+ and sl.objsubid = 0
316
+ ),
317
+ '[]'::json
318
+ ) as security_labels
298
319
  from
299
320
  pg_catalog.pg_proc p
300
321
  inner join pg_catalog.pg_aggregate a on a.aggfnoid = p.oid
@@ -3,7 +3,11 @@ import type { Aggregate } from "../aggregate.model.ts";
3
3
 
4
4
  abstract class BaseAggregateChange extends BaseChange {
5
5
  abstract readonly aggregate: Aggregate;
6
- abstract readonly scope: "object" | "comment" | "privilege";
6
+ abstract readonly scope:
7
+ | "object"
8
+ | "comment"
9
+ | "privilege"
10
+ | "security_label";
7
11
  readonly objectType: "aggregate" = "aggregate";
8
12
  }
9
13
 
@@ -0,0 +1,99 @@
1
+ import { quoteLiteral } from "../../base.change.ts";
2
+ import type { SecurityLabelProps } from "../../security-label.types.ts";
3
+ import { stableId } from "../../utils.ts";
4
+ import type { Aggregate } from "../aggregate.model.ts";
5
+ import {
6
+ CreateAggregateChange,
7
+ DropAggregateChange,
8
+ } from "./aggregate.base.ts";
9
+
10
+ export type SecurityLabelAggregate =
11
+ | CreateSecurityLabelOnAggregate
12
+ | DropSecurityLabelOnAggregate;
13
+
14
+ function aggregateIdentity(a: Aggregate): string {
15
+ return `${a.schema}.${a.name}(${a.identityArguments})`;
16
+ }
17
+
18
+ export class CreateSecurityLabelOnAggregate extends CreateAggregateChange {
19
+ public readonly aggregate: Aggregate;
20
+ public readonly securityLabel: SecurityLabelProps;
21
+ public readonly scope = "security_label" as const;
22
+
23
+ constructor(props: {
24
+ aggregate: Aggregate;
25
+ securityLabel: SecurityLabelProps;
26
+ }) {
27
+ super();
28
+ this.aggregate = props.aggregate;
29
+ this.securityLabel = props.securityLabel;
30
+ }
31
+
32
+ get creates() {
33
+ return [
34
+ stableId.securityLabel(
35
+ this.aggregate.stableId,
36
+ this.securityLabel.provider,
37
+ ),
38
+ ];
39
+ }
40
+
41
+ get requires() {
42
+ return [this.aggregate.stableId];
43
+ }
44
+
45
+ serialize(): string {
46
+ return [
47
+ "SECURITY LABEL FOR",
48
+ this.securityLabel.provider,
49
+ "ON AGGREGATE",
50
+ aggregateIdentity(this.aggregate),
51
+ "IS",
52
+ quoteLiteral(this.securityLabel.label),
53
+ ].join(" ");
54
+ }
55
+ }
56
+
57
+ export class DropSecurityLabelOnAggregate extends DropAggregateChange {
58
+ public readonly aggregate: Aggregate;
59
+ public readonly securityLabel: SecurityLabelProps;
60
+ public readonly scope = "security_label" as const;
61
+
62
+ constructor(props: {
63
+ aggregate: Aggregate;
64
+ securityLabel: SecurityLabelProps;
65
+ }) {
66
+ super();
67
+ this.aggregate = props.aggregate;
68
+ this.securityLabel = props.securityLabel;
69
+ }
70
+
71
+ get drops() {
72
+ return [
73
+ stableId.securityLabel(
74
+ this.aggregate.stableId,
75
+ this.securityLabel.provider,
76
+ ),
77
+ ];
78
+ }
79
+
80
+ get requires() {
81
+ return [
82
+ stableId.securityLabel(
83
+ this.aggregate.stableId,
84
+ this.securityLabel.provider,
85
+ ),
86
+ this.aggregate.stableId,
87
+ ];
88
+ }
89
+
90
+ serialize(): string {
91
+ return [
92
+ "SECURITY LABEL FOR",
93
+ this.securityLabel.provider,
94
+ "ON AGGREGATE",
95
+ aggregateIdentity(this.aggregate),
96
+ "IS NULL",
97
+ ].join(" ");
98
+ }
99
+ }