okmodel 0.3.0 → 0.5.0

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 (123) hide show
  1. package/README.md +263 -23
  2. package/dist/contracts/catalog/document.d.ts +26 -2
  3. package/dist/contracts/driver.d.ts +7 -0
  4. package/dist/dialects/pg/ext/citext.js +1 -1
  5. package/dist/dialects/pg/ext/index.js +1 -1
  6. package/dist/dialects/pg/ext/pg-trgm.js +1 -1
  7. package/dist/dialects/pg/index.js +3 -3
  8. package/dist/dialects/pg/introspect.d.ts +6 -0
  9. package/dist/dialects/pg/role/sql.d.ts +21 -4
  10. package/dist/dialects/pg/schema.d.ts +15 -6
  11. package/dist/dialects/pg/table.d.ts +17 -1
  12. package/dist/dialects/pg/view/index.d.ts +20 -6
  13. package/dist/dialects/pg/view/index.js +1 -1
  14. package/dist/dialects/pg/view/scratch.d.ts +2 -1
  15. package/dist/runtime/catalog-cache.d.ts +17 -0
  16. package/dist/runtime/compat-words.d.ts +23 -0
  17. package/dist/runtime/drift-detail.d.ts +20 -4
  18. package/dist/runtime/drift.d.ts +4 -2
  19. package/dist/runtime/pg/bun.d.ts +12 -7
  20. package/dist/runtime/pg/bun.js +5 -2
  21. package/dist/runtime/pg/pg.d.ts +12 -6
  22. package/dist/runtime/pg/pg.js +5 -2
  23. package/dist/runtime/pg/pglite.d.ts +11 -3
  24. package/dist/runtime/pg/pglite.js +5 -2
  25. package/dist/runtime/pg/postgresjs.d.ts +12 -6
  26. package/dist/runtime/pg/postgresjs.js +5 -2
  27. package/dist/runtime/plan.d.ts +3 -1
  28. package/dist/runtime/tenancy/index.js +1 -1
  29. package/dist/runtime/topology.d.ts +191 -0
  30. package/dist/runtime/traits/index.js +7 -6
  31. package/dist/runtime/types.d.ts +59 -2
  32. package/dist/runtime/validate/index.js +2 -2
  33. package/dist/shared/{x6wysh83.js → 3p4b1fg0.js} +3 -5
  34. package/dist/shared/{ty3v3hfk.js → 47xzb9fc.js} +2 -2
  35. package/dist/shared/{fs0ex00p.js → 4ef7mmqf.js} +2 -2
  36. package/dist/shared/{qybh48nd.js → 5fqx9yh4.js} +6 -2
  37. package/dist/shared/5mtf6cj3.js +94 -0
  38. package/dist/shared/{999ade2q.js → 5vdm4sgp.js} +1 -1
  39. package/dist/shared/{bqha5cg6.js → 6p5k20sm.js} +2 -2
  40. package/dist/shared/{t616ynfn.js → 8bwbwy8h.js} +71 -13
  41. package/dist/shared/{th14ygv2.js → 8ktwd3rh.js} +29 -1
  42. package/dist/shared/{wekze34d.js → 8xsbagad.js} +74 -2
  43. package/dist/shared/{bryqk9k9.js → 922qbgnw.js} +3 -3
  44. package/dist/shared/{meck53jv.js → 990a291j.js} +6 -5
  45. package/dist/shared/{jns2wx4t.js → 9tgmr1gh.js} +1 -1
  46. package/dist/shared/9yn6a2vd.js +11 -0
  47. package/dist/shared/{2ssqrv66.js → ayz64vzj.js} +1037 -181
  48. package/dist/shared/{k34e0xf2.js → bt9sfq0z.js} +16 -14
  49. package/dist/shared/{scwxrpx2.js → bzsjtd7c.js} +84 -15
  50. package/dist/shared/{m9tv7xn7.js → ckjbyrt9.js} +4 -4
  51. package/dist/shared/{ydaz1sv3.js → dxjp14rk.js} +10 -2
  52. package/dist/shared/{49ypnfwt.js → fancm5pt.js} +24 -229
  53. package/dist/shared/{6bazvqwm.js → fb00y8fp.js} +2 -2
  54. package/dist/shared/{1tfdfvhh.js → fyqkk1m0.js} +6 -4
  55. package/dist/shared/g516zdsb.js +905 -0
  56. package/dist/shared/{jkc6ws1p.js → g7ez901n.js} +83 -2
  57. package/dist/shared/{kgnc8grt.js → gdh9mrtp.js} +1 -1
  58. package/dist/shared/{p24x2334.js → h4nqvjkp.js} +5 -3
  59. package/dist/shared/{edwqqryd.js → h8ne16fq.js} +1 -1
  60. package/dist/shared/htnaj77v.js +17 -0
  61. package/dist/shared/jdgqqx31.js +72 -0
  62. package/dist/shared/{6w5j844a.js → jsd07prz.js} +2 -2
  63. package/dist/shared/{m78s7898.js → k7cc9nzk.js} +24 -7
  64. package/dist/shared/kdkxh3kb.js +758 -0
  65. package/dist/shared/nkc9sfqb.js +1161 -0
  66. package/dist/shared/{xva8v7qk.js → p7pfx624.js} +5 -5
  67. package/dist/shared/{xrfnq5k8.js → qgvt8nx1.js} +11 -2
  68. package/dist/shared/{ne02ng5c.js → qy9rdq0g.js} +9 -5
  69. package/dist/shared/{3ekj9x92.js → r6pj4fq2.js} +5 -3
  70. package/dist/shared/r73pt594.js +426 -0
  71. package/dist/shared/rasvvzgz.js +21 -0
  72. package/dist/shared/{01yr20s5.js → sf8m6tfy.js} +2 -2
  73. package/dist/shared/{hcmdtwgh.js → szm0cksa.js} +4 -1
  74. package/dist/shared/{axhk06q1.js → tq9afp8d.js} +1 -1
  75. package/dist/shared/{xw1d69vd.js → wqem4jed.js} +6 -6
  76. package/dist/shared/wxn4b7pp.js +252 -0
  77. package/dist/shared/xdpj5kkm.js +2203 -0
  78. package/dist/shared/xhh1srw3.js +589 -0
  79. package/dist/shared/{mt5wswjz.js → xsq7v6wp.js} +2 -2
  80. package/dist/shared/{7fvtrkwr.js → ycatkxmq.js} +1 -1
  81. package/dist/shared/ykv7wh5b.js +14 -0
  82. package/dist/shared/{fptpmvxp.js → z2rnngj4.js} +26 -21
  83. package/dist/tooling/cli.js +46 -17
  84. package/dist/tooling/migrate/apply.d.ts +61 -5
  85. package/dist/tooling/migrate/backfill.d.ts +136 -0
  86. package/dist/tooling/migrate/check.d.ts +65 -0
  87. package/dist/tooling/migrate/classify.d.ts +108 -0
  88. package/dist/tooling/migrate/commands.d.ts +2 -2
  89. package/dist/tooling/migrate/config.d.ts +22 -0
  90. package/dist/tooling/migrate/doctor.d.ts +3 -0
  91. package/dist/tooling/migrate/drift.d.ts +18 -2
  92. package/dist/tooling/migrate/estimate.d.ts +50 -0
  93. package/dist/tooling/migrate/extensions.d.ts +2 -0
  94. package/dist/tooling/migrate/files.d.ts +19 -0
  95. package/dist/tooling/migrate/index.d.ts +3 -2
  96. package/dist/tooling/migrate/index.js +69 -1
  97. package/dist/tooling/migrate/lint-rules.d.ts +51 -0
  98. package/dist/tooling/migrate/lint.d.ts +71 -0
  99. package/dist/tooling/migrate/managed.d.ts +1 -1
  100. package/dist/tooling/migrate/plan.d.ts +64 -9
  101. package/dist/tooling/migrate/project.d.ts +42 -7
  102. package/dist/tooling/migrate/provision.d.ts +18 -0
  103. package/dist/tooling/migrate/reference.d.ts +40 -0
  104. package/dist/tooling/migrate/runner.d.ts +108 -0
  105. package/dist/tooling/migrate/safe.d.ts +77 -0
  106. package/dist/tooling/migrate/seed.d.ts +20 -0
  107. package/dist/tooling/migrate/snapshot.d.ts +78 -0
  108. package/dist/tooling/migrate/status.d.ts +25 -6
  109. package/dist/tooling/testing/factories.d.ts +117 -0
  110. package/dist/tooling/testing/facts.d.ts +75 -0
  111. package/dist/tooling/testing/index.d.ts +89 -0
  112. package/dist/tooling/testing/index.js +8 -0
  113. package/dist/tooling/testing/isolation.d.ts +30 -0
  114. package/dist/tooling/testing/random.d.ts +40 -0
  115. package/dist/tooling/testing/record.d.ts +38 -0
  116. package/package.json +6 -2
  117. package/dist/shared/5nmdenb6.js +0 -1229
  118. package/dist/shared/dbjej1tw.js +0 -456
  119. package/dist/shared/g4f5kcx9.js +0 -37
  120. package/dist/shared/qbxzvwwc.js +0 -88
  121. package/dist/shared/zwmqxb0g.js +0 -79
  122. /package/dist/shared/{75xkjv0t.js → fa9vt245.js} +0 -0
  123. /package/dist/shared/{0jc3ffzf.js → zvbjc949.js} +0 -0
@@ -2,7 +2,7 @@ import {
2
2
  creationOrder2,
3
3
  renameColumn2,
4
4
  renameTable2
5
- } from "./t616ynfn.js";
5
+ } from "./8bwbwy8h.js";
6
6
  import {
7
7
  quoteIdent,
8
8
  ownedByView,
@@ -15,16 +15,16 @@ import {
15
15
  identitySequence,
16
16
  createTableSql,
17
17
  qualify
18
- } from "./wekze34d.js";
18
+ } from "./8xsbagad.js";
19
19
  import {
20
20
  isExactVersion,
21
21
  compareExtensionVersions,
22
22
  sameExtensionDefinition
23
- } from "./th14ygv2.js";
23
+ } from "./8ktwd3rh.js";
24
24
  import {
25
25
  isDomain,
26
26
  sameEnumLabels
27
- } from "./qbxzvwwc.js";
27
+ } from "./g7ez901n.js";
28
28
  import {
29
29
  quoteLiteral
30
30
  } from "./94ez8kzh.js";
@@ -41,9 +41,74 @@ import {
41
41
  OkmError2
42
42
  } from "./52szzakm.js";
43
43
 
44
+ // src/tooling/migrate/type-name.ts
45
+ function canonicalTypeName(typeName) {
46
+ const suffix = arraySuffix(typeName);
47
+ const scalar = suffix.length === 0 ? typeName : typeName.slice(0, -suffix.length);
48
+ return canonicalScalar(scalar) + suffix;
49
+ }
50
+ function arraySuffix(typeName) {
51
+ let end = typeName.length;
52
+ let suffix = "";
53
+ while (end >= 2 && typeName.endsWith("[]", end)) {
54
+ suffix = `[]${suffix}`;
55
+ end -= 2;
56
+ }
57
+ return suffix;
58
+ }
59
+ function canonicalScalar(typeName) {
60
+ const varying = /^(?:varchar|character varying)(\(\d+\))$/.exec(typeName);
61
+ if (varying !== null)
62
+ return `character varying${varying[1] ?? ""}`;
63
+ const fixed = /^(?:char|character)(\(\d+\))$/.exec(typeName);
64
+ if (fixed !== null)
65
+ return `character${fixed[1] ?? ""}`;
66
+ const timed = /^(timestamp with time zone|timestamp without time zone|time with time zone|time without time zone|timestamptz|timestamp|timetz|time)(\(\d+\))?$/.exec(typeName);
67
+ if (timed !== null)
68
+ return canonicalTime(timed[1] ?? "", timed[2] ?? "");
69
+ const numeric = /^(?:decimal|numeric)(\(\d+(?:,\d+)?\))?$/.exec(typeName);
70
+ if (numeric !== null)
71
+ return `numeric${numeric[1] ?? ""}`;
72
+ switch (typeName) {
73
+ case "int":
74
+ case "int4":
75
+ case "integer":
76
+ return "integer";
77
+ case "int8":
78
+ case "bigint":
79
+ return "bigint";
80
+ case "int2":
81
+ case "smallint":
82
+ return "smallint";
83
+ case "bool":
84
+ case "boolean":
85
+ return "boolean";
86
+ case "float8":
87
+ case "double precision":
88
+ return "double precision";
89
+ case "float4":
90
+ case "real":
91
+ return "real";
92
+ default:
93
+ return typeName;
94
+ }
95
+ }
96
+ function canonicalTime(name, precision) {
97
+ if (name === "timestamptz" || name === "timestamp with time zone") {
98
+ return `timestamp${precision} with time zone`;
99
+ }
100
+ if (name === "timestamp" || name === "timestamp without time zone") {
101
+ return `timestamp${precision} without time zone`;
102
+ }
103
+ if (name === "timetz" || name === "time with time zone") {
104
+ return `time${precision} with time zone`;
105
+ }
106
+ return `time${precision} without time zone`;
107
+ }
108
+
44
109
  // src/dialects/pg/role/sql.ts
45
110
  var PRIVILEGE = /^(select|insert|update|delete|execute|usage)$/;
46
- function privilegeSql(before, after, schema) {
111
+ function privilegeSql(before, after, schema, recreated = new Set) {
47
112
  const beforeBy = index(before);
48
113
  const afterBy = index(after);
49
114
  const revoke = [];
@@ -53,10 +118,12 @@ function privilegeSql(before, after, schema) {
53
118
  for (const [key, object] of beforeBy) {
54
119
  if (afterBy.has(key))
55
120
  continue;
56
- if (object.kind === "grant")
57
- revoke.push(grantStatement(object, schema, true));
58
- if (object.kind === "defaultPrivilege")
59
- revoke.push(defaultStatement(object, true));
121
+ if (object.kind === "grant") {
122
+ revoke.push({ sql: grantStatement(object, schema, true), kind: "revoke" });
123
+ }
124
+ if (object.kind === "defaultPrivilege") {
125
+ revoke.push({ sql: defaultStatement(object, schema, true), kind: "revoke-default" });
126
+ }
60
127
  }
61
128
  for (const [key, object] of afterBy) {
62
129
  const previous = beforeBy.get(key);
@@ -64,22 +131,28 @@ function privilegeSql(before, after, schema) {
64
131
  if (object.owner !== "managed")
65
132
  continue;
66
133
  if (previous === undefined) {
67
- roles.push(createRoleSql(object));
134
+ roles.push({ sql: createRoleSql(object), kind: "create-role" });
68
135
  continue;
69
136
  }
70
137
  if (previous.kind !== "role" || previous.owner !== "managed")
71
138
  continue;
72
139
  if (previous.definition.login !== object.definition.login || previous.definition.inherit !== object.definition.inherit) {
73
- roles.push(alterRoleSql(object));
140
+ roles.push({ sql: alterRoleSql(object), kind: "alter-role" });
74
141
  }
75
142
  continue;
76
143
  }
144
+ if (object.kind === "grant") {
145
+ const target = grantTargetKey(object.identity.object);
146
+ if (previous !== undefined && (target === undefined || !recreated.has(target)))
147
+ continue;
148
+ grant.push({ sql: grantStatement(object, schema, false), kind: "grant" });
149
+ continue;
150
+ }
77
151
  if (previous !== undefined)
78
152
  continue;
79
- if (object.kind === "defaultPrivilege")
80
- defaults.push(defaultStatement(object, false));
81
- if (object.kind === "grant")
82
- grant.push(grantStatement(object, schema, false));
153
+ if (object.kind === "defaultPrivilege") {
154
+ defaults.push({ sql: defaultStatement(object, schema, false), kind: "grant-default" });
155
+ }
83
156
  }
84
157
  return { revoke, prepare: [...roles, ...defaults], grant };
85
158
  }
@@ -90,6 +163,9 @@ function createdRoleName(sql) {
90
163
  return quoted.replaceAll('""', '"');
91
164
  return match?.[2];
92
165
  }
166
+ function createRoleOnce(sql) {
167
+ return `do $okm$ begin ${sql}; exception when duplicate_object or unique_violation then null; end $okm$`;
168
+ }
93
169
  function changesRole(sql) {
94
170
  return /^\s*(create|alter)\s+role\b/i.test(sql);
95
171
  }
@@ -122,23 +198,39 @@ function grantStatement(object, schema, revoke) {
122
198
  const direction = revoke ? "from" : "to";
123
199
  return `${verb} ${privilege} on ${grantTarget(object.identity.object, schema)} ${direction} ${quoteIdent(object.identity.role)}`;
124
200
  }
125
- function defaultStatement(object, revoke) {
201
+ function defaultStatement(object, schema, revoke) {
126
202
  const privilege = object.identity.privilege.toLowerCase();
127
203
  assertPrivilege(privilege);
128
204
  const verb = revoke ? "revoke" : "grant";
129
205
  const direction = revoke ? "from" : "to";
130
- const namespace = object.identity.namespace.form === "static" ? object.identity.namespace.name : "public";
131
- return `alter default privileges for role ${quoteIdent(object.identity.forRole)} in schema ${quoteIdent(namespace)} ${verb} ${privilege} on ${objectKindSql(object.identity.objectKind)} ${direction} ${quoteIdent(object.identity.grantee)}`;
206
+ return `alter default privileges for role ${quoteIdent(object.identity.forRole)} in schema ${quoteIdent(schema)} ${verb} ${privilege} on ${objectKindSql(object.identity.objectKind)} ${direction} ${quoteIdent(object.identity.grantee)}`;
207
+ }
208
+ function grantTargetKey(object) {
209
+ if (object.kind === "table" || object.kind === "view" || object.kind === "materializedView" || object.kind === "sequence") {
210
+ return identityKey2({ kind: object.kind, namespace: object.namespace, name: object.name });
211
+ }
212
+ if (object.kind !== "function")
213
+ return;
214
+ const open = object.name.lastIndexOf("(");
215
+ if (open < 0 || !object.name.endsWith(")"))
216
+ return;
217
+ const name = object.name.slice(0, open);
218
+ const args = object.name.slice(open + 1, -1);
219
+ return identityKey2({
220
+ kind: "function",
221
+ namespace: object.namespace,
222
+ name,
223
+ argTypes: args.length === 0 ? [] : args.split(",")
224
+ });
132
225
  }
133
226
  function grantTarget(object, schema) {
134
- const name = object.namespace.form === "static" ? object.namespace.name : schema;
135
227
  if (object.kind === "sequence")
136
- return `sequence ${qualify(name, object.name)}`;
228
+ return `sequence ${qualify(schema, object.name)}`;
137
229
  if (object.kind === "function")
138
- return functionTarget(object.name, name);
230
+ return functionTarget(object.name, schema);
139
231
  if (object.kind === "namespace")
140
232
  return `schema ${quoteIdent(object.name)}`;
141
- return `table ${qualify(name, object.name)}`;
233
+ return `table ${qualify(schema, object.name)}`;
142
234
  }
143
235
  function functionTarget(name, schema) {
144
236
  const open = name.lastIndexOf("(");
@@ -165,6 +257,247 @@ function assertPrivilege(privilege) {
165
257
  }
166
258
  }
167
259
 
260
+ // src/tooling/migrate/backfill.ts
261
+ var DEFAULT_BACKFILL_BATCH_SIZE = 1000;
262
+ var DEFAULT_BACKFILL_PAUSE_MS = 0;
263
+ var DEFAULT_BACKFILL_STATEMENT_TIMEOUT_MS = 30000;
264
+ var HEADER = /^-- backfill table=(\S+) key=(\S+) batch=(\d+)$/;
265
+ var CAST_TYPE = /^[A-Za-z_][\w$ ]*(?:\([^)]*\))?(?:\[\])*$/;
266
+ function batchSizeField(batchSize) {
267
+ return batchSize === undefined ? {} : { batchSize };
268
+ }
269
+ function resolveBatchSize(requested) {
270
+ const batch = requested ?? DEFAULT_BACKFILL_BATCH_SIZE;
271
+ if (!Number.isInteger(batch) || batch < 1) {
272
+ throw new OkmError2("invalid", "backfill.batchSize must be an integer of at least 1.", {
273
+ fix: {
274
+ summary: "Set defineConfig({ backfill: { batchSize } }) to a positive integer."
275
+ }
276
+ });
277
+ }
278
+ return batch;
279
+ }
280
+ function formatBackfillHeader(spec) {
281
+ return `-- backfill table=${spec.table} key=${spec.key.join(",")} batch=${String(spec.batch)}`;
282
+ }
283
+ function parseBackfillHeader(line) {
284
+ if (!line.startsWith("-- backfill "))
285
+ return;
286
+ const match = HEADER.exec(line);
287
+ const table = match?.[1];
288
+ const keyText = match?.[2];
289
+ const batchText = match?.[3];
290
+ const key = keyText === undefined ? [] : splitKey(keyText);
291
+ const batch = batchText === undefined ? Number.NaN : Number(batchText);
292
+ if (table === undefined || key.length === 0 || !Number.isInteger(batch) || batch < 1) {
293
+ throw new OkmError2("invalid", "A backfill header needs table=<table> key=<col> batch=<n>.", {
294
+ fix: {
295
+ summary: "Write -- backfill table=<table> key=<col[,col]> batch=<n> with a batch of at least 1."
296
+ }
297
+ });
298
+ }
299
+ return { table, key, batch };
300
+ }
301
+ function primaryKeyColumns(catalogs, table) {
302
+ for (const source of catalogs) {
303
+ const columns = keyOn(source, table);
304
+ if (columns !== undefined)
305
+ return columns;
306
+ }
307
+ throw new OkmError2("OKM1546", `Table ${table} has no primary key, so it cannot be backfilled.`, {
308
+ fix: {
309
+ summary: "Add a primary key. A backfill walks that key and does not scan the table."
310
+ }
311
+ });
312
+ }
313
+ function keyRangePredicate(columns) {
314
+ for (const column of columns)
315
+ assertCastType(column.dataType);
316
+ const lower = compareKey(columns, "$1", ">");
317
+ const upper = compareKey(columns, "$2", "<=");
318
+ return `($1::text is null or ${lower}) and ($2::text is null or ${upper})`;
319
+ }
320
+ function aboutBatchCount(rows, batch) {
321
+ if (!Number.isFinite(rows) || batch < 1)
322
+ return 0;
323
+ const rounded = Math.max(0, Math.round(rows));
324
+ if (rounded === 0)
325
+ return 0;
326
+ return Math.ceil(rounded / batch);
327
+ }
328
+ function keyOn(source, table) {
329
+ const constraint = source.objects.find((object) => object.kind === "constraint" && object.definition.constraintKind === "primaryKey" && object.identity.parent.name === table);
330
+ if (constraint === undefined || constraint.kind !== "constraint")
331
+ return;
332
+ const columns = [];
333
+ for (const name of constraint.definition.columns) {
334
+ const column = source.objects.find((object) => object.kind === "column" && object.identity.parent.name === table && object.identity.name === name);
335
+ if (column === undefined || column.kind !== "column") {
336
+ throw new OkmError2("OKM1546", `Table ${table} primary key column ${name} is not in the catalog.`, { fix: { summary: "Keep the primary key columns on the table." } });
337
+ }
338
+ columns.push({ quoted: quoteIdent(name), dataType: column.definition.dataType });
339
+ }
340
+ if (columns.length === 0)
341
+ return;
342
+ return columns;
343
+ }
344
+ function boundaryQuery(table, columns) {
345
+ for (const column of columns)
346
+ checkedCast(column.dataType);
347
+ const order = columns.map((column) => column.quoted).join(", ");
348
+ const first = columns[0];
349
+ const projected = columns.length === 1 && first !== undefined ? `${first.quoted}::text` : `jsonb_build_array(${columns.map((column) => `${column.quoted}::text`).join(", ")})::text`;
350
+ const lower = `($1::text is null or ${compareKey(columns, "$1", ">")})`;
351
+ return `select ${projected} as okm_key from ${table} where ${lower} order by ${order} offset $2::bigint limit 1`;
352
+ }
353
+ function checkedCast(dataType) {
354
+ assertCastType(dataType);
355
+ return dataType;
356
+ }
357
+ function compareKey(columns, param, operator) {
358
+ const first = columns[0];
359
+ if (first === undefined)
360
+ return "false";
361
+ if (columns.length === 1) {
362
+ return `${first.quoted} ${operator} ${param}::${first.dataType}`;
363
+ }
364
+ const left = columns.map((column) => column.quoted).join(", ");
365
+ const right = columns.map((column, index) => `((${param}::jsonb)->>${String(index)})::${column.dataType}`).join(", ");
366
+ return `(${left}) ${operator} (${right})`;
367
+ }
368
+ function assertCastType(dataType) {
369
+ if (CAST_TYPE.test(dataType))
370
+ return;
371
+ throw new OkmError2("OKM1546", `Primary key type ${dataType} cannot be used as a backfill bound.`, {
372
+ fix: {
373
+ summary: "Use an orderable scalar key. A backfill casts each bound to the key type."
374
+ }
375
+ });
376
+ }
377
+ function splitKey(text) {
378
+ const names = [];
379
+ let index = 0;
380
+ while (index < text.length) {
381
+ if (text[index] === '"') {
382
+ let value = "";
383
+ index += 1;
384
+ let closed = false;
385
+ while (index < text.length) {
386
+ const char = text[index];
387
+ if (char === '"' && text[index + 1] === '"') {
388
+ value += '"';
389
+ index += 2;
390
+ continue;
391
+ }
392
+ if (char === '"') {
393
+ index += 1;
394
+ closed = true;
395
+ break;
396
+ }
397
+ value += char ?? "";
398
+ index += 1;
399
+ }
400
+ if (!closed)
401
+ return [];
402
+ names.push(quoteIdent(value));
403
+ if (text[index] === ",")
404
+ index += 1;
405
+ continue;
406
+ }
407
+ const comma = text.indexOf(",", index);
408
+ const end = comma === -1 ? text.length : comma;
409
+ const raw = text.slice(index, end);
410
+ if (raw.length === 0)
411
+ return [];
412
+ names.push(quoteIdent(raw));
413
+ index = comma === -1 ? text.length : comma + 1;
414
+ }
415
+ return names;
416
+ }
417
+
418
+ // src/tooling/migrate/classify.ts
419
+ var STEP_CLASS = {
420
+ "create-table": "expand",
421
+ "drop-table": "contract",
422
+ "add-column": "expand",
423
+ "add-column-required": "contract",
424
+ "drop-column": "contract",
425
+ "set-column-type": "contract",
426
+ "set-not-null": "contract",
427
+ "drop-not-null": "expand",
428
+ "set-default": "expand",
429
+ "drop-default": "contract",
430
+ "add-identity": "expand",
431
+ "drop-identity": "contract",
432
+ "set-identity-always": "contract",
433
+ "set-identity-by-default": "expand",
434
+ "add-constraint": "expand",
435
+ "validate-constraint": "expand",
436
+ "drop-constraint": "contract",
437
+ "drop-not-null-check": "expand",
438
+ "rename-constraint": "contract",
439
+ "widen-check": "expand",
440
+ "narrow-check": "contract",
441
+ "create-index": "expand",
442
+ "drop-index": "contract",
443
+ "rename-index": "contract",
444
+ "create-sequence": "expand",
445
+ "drop-sequence": "contract",
446
+ "create-enum": "expand",
447
+ "recreate-enum": "contract",
448
+ "add-enum-value": "expand",
449
+ "rename-enum": "contract",
450
+ "drop-enum": "contract",
451
+ "set-enum-column": "contract",
452
+ "create-domain": "expand",
453
+ "drop-domain": "contract",
454
+ "add-domain-check": "expand",
455
+ "validate-domain-check": "expand",
456
+ "drop-domain-check": "contract",
457
+ "rename-domain-check": "expand",
458
+ "create-extension": "expand",
459
+ "drop-extension": "contract",
460
+ "update-extension": "expand",
461
+ "move-extension": "contract",
462
+ "create-function": "expand",
463
+ "drop-function": "contract",
464
+ "replace-function": "expand",
465
+ "create-trigger": "expand",
466
+ "drop-trigger": "contract",
467
+ "create-view": "expand",
468
+ "drop-view": "contract",
469
+ "replace-view": "expand",
470
+ "create-matview": "expand",
471
+ "drop-matview": "contract",
472
+ "refresh-matview": "expand",
473
+ "create-role": "expand",
474
+ "alter-role": "contract",
475
+ grant: "expand",
476
+ revoke: "contract",
477
+ "grant-default": "expand",
478
+ "revoke-default": "contract",
479
+ "rename-table": "contract",
480
+ "rename-column": "contract",
481
+ "rename-sequence": "contract",
482
+ "backfill-expand": "expand",
483
+ "backfill-contract": "contract",
484
+ "raw-sql": "unclassified"
485
+ };
486
+ var STEP_KINDS = Object.keys(STEP_CLASS);
487
+ function classOf(kind) {
488
+ return STEP_CLASS[kind];
489
+ }
490
+ function strictestClass(classes) {
491
+ if (classes.includes("contract"))
492
+ return "contract";
493
+ if (classes.includes("unclassified"))
494
+ return "unclassified";
495
+ return "expand";
496
+ }
497
+ function isStepKind(value) {
498
+ return Object.hasOwn(STEP_CLASS, value);
499
+ }
500
+
168
501
  // src/tooling/migrate/extensions.ts
169
502
  var CREATE_EXTENSION = /^\s*create\s+extension\s+(?:"([^"]+)"|([A-Za-z_][\w$]*))/i;
170
503
  var UPDATE_EXTENSION = /^\s*alter\s+extension\s+(?:"([^"]+)"|([A-Za-z_][\w$]*))\s+update\s+to\s+'([^']*)'/i;
@@ -255,13 +588,7 @@ function alterExtension(before, after) {
255
588
  }
256
589
  });
257
590
  }
258
- steps.push({
259
- sql: `alter extension ${name} set schema ${quoteIdent(after.definition.schema)}`,
260
- class: "contract",
261
- action: "ddl",
262
- lock: "ACCESS EXCLUSIVE",
263
- transactional: true
264
- });
591
+ steps.push(extensionStep(`alter extension ${name} set schema ${quoteIdent(after.definition.schema)}`, "move-extension"));
265
592
  }
266
593
  const previous = before.definition.version;
267
594
  const next = after.definition.version;
@@ -274,15 +601,27 @@ function alterExtension(before, after) {
274
601
  }
275
602
  });
276
603
  }
277
- steps.push({
278
- sql: `alter extension ${name} update to ${quoteLiteral(next)}`,
279
- class: "expand",
604
+ steps.push(extensionStep(`alter extension ${name} update to ${quoteLiteral(next)}`, "update-extension", {
605
+ path: "unverified"
606
+ }));
607
+ return steps;
608
+ }
609
+ function extensionStep(sql, kind, extra) {
610
+ const classification = classOf(kind);
611
+ if (classification === "unclassified") {
612
+ throw new OkmError2("invalid", `Extension step ${kind} has no class.`, {
613
+ fix: { summary: "Classify the extension step as expand or contract." }
614
+ });
615
+ }
616
+ return {
617
+ sql,
618
+ kind,
619
+ class: classification,
280
620
  action: "ddl",
281
621
  lock: "ACCESS EXCLUSIVE",
282
622
  transactional: true,
283
- path: "unverified"
284
- });
285
- return steps;
623
+ ...extra?.path !== undefined ? { path: extra.path } : {}
624
+ };
286
625
  }
287
626
  async function availableExtensions(connection, names) {
288
627
  const listed = await connection.execute(`select name from pg_available_extensions where name in (${names.map((_, index) => `$${String(index + 1)}`).join(", ")})`, names);
@@ -306,7 +645,7 @@ async function updatePath(connection, name, source, target) {
306
645
  }
307
646
 
308
647
  // src/tooling/migrate/managed.ts
309
- var MANAGED_TABLES = new Set(["okm_meta", "okm_history"]);
648
+ var MANAGED_TABLES = new Set(["okm_meta", "okm_history", "okm_backfill"]);
310
649
  function omitManagedObjects(source) {
311
650
  let dropped = false;
312
651
  const objects = [];
@@ -348,72 +687,293 @@ function ownedByMigration(object) {
348
687
  }
349
688
  }
350
689
  function managedSequence(name) {
351
- return name === "okm_meta" || name === "okm_history" || name.startsWith("okm_meta_") || name.startsWith("okm_history_");
690
+ return name === "okm_meta" || name === "okm_history" || name === "okm_backfill" || name.startsWith("okm_meta_") || name.startsWith("okm_history_") || name.startsWith("okm_backfill_");
352
691
  }
353
692
 
354
- // src/tooling/migrate/type-name.ts
355
- function canonicalTypeName(typeName) {
356
- const suffix = arraySuffix(typeName);
357
- const scalar = suffix.length === 0 ? typeName : typeName.slice(0, -suffix.length);
358
- return canonicalScalar(scalar) + suffix;
693
+ // src/tooling/migrate/safe.ts
694
+ var ACCESS = "ACCESS EXCLUSIVE";
695
+ var SHARE_UPDATE = "SHARE UPDATE EXCLUSIVE";
696
+ var ROW = "ROW EXCLUSIVE";
697
+ var VOLATILE_BUILTIN = new Set([
698
+ "clock_timestamp",
699
+ "gen_random_uuid",
700
+ "nextval",
701
+ "random",
702
+ "random_normal",
703
+ "setval",
704
+ "timeofday",
705
+ "uuidv7"
706
+ ]);
707
+ var STABLE_BUILTIN = new Set([
708
+ "current_catalog",
709
+ "current_date",
710
+ "current_role",
711
+ "current_schema",
712
+ "current_time",
713
+ "current_timestamp",
714
+ "current_user",
715
+ "localtime",
716
+ "localtimestamp",
717
+ "now",
718
+ "session_user",
719
+ "statement_timestamp",
720
+ "transaction_timestamp"
721
+ ]);
722
+ function stepsForExistingTable(object, schema, catalogs, batchSize) {
723
+ if (object.kind === "index")
724
+ return [concurrentIndexStep(object, schema)];
725
+ if (object.kind === "constraint")
726
+ return constraintSteps(object, schema);
727
+ if (object.kind === "column" && volatileDefault(object.definition.defaultExpression, catalogs)) {
728
+ return volatileColumnSteps(object, schema, catalogs, batchSize);
729
+ }
730
+ return;
359
731
  }
360
- function arraySuffix(typeName) {
361
- let end = typeName.length;
362
- let suffix = "";
363
- while (end >= 2 && typeName.endsWith("[]", end)) {
364
- suffix = `[]${suffix}`;
365
- end -= 2;
732
+ function notNullSteps(column, schema, setKind, catalogs) {
733
+ const tableName = column.identity.parent.name;
734
+ const columnName = column.identity.name;
735
+ const table = qualify(schema, tableName);
736
+ const quotedColumn = quoteIdent(columnName);
737
+ const constraint = quoteIdent(notNullConstraintName(tableName, columnName, catalogs));
738
+ return [
739
+ {
740
+ sql: `alter table ${table} add constraint ${constraint} check (${quotedColumn} is not null) not valid`,
741
+ kind: "add-constraint",
742
+ action: "ddl",
743
+ lock: ACCESS,
744
+ transactional: true
745
+ },
746
+ {
747
+ sql: `alter table ${table} validate constraint ${constraint}`,
748
+ kind: "validate-constraint",
749
+ action: "ddl",
750
+ lock: SHARE_UPDATE,
751
+ transactional: true
752
+ },
753
+ {
754
+ sql: `alter table ${table} alter column ${quotedColumn} set not null`,
755
+ kind: setKind,
756
+ action: "ddl",
757
+ lock: ACCESS,
758
+ transactional: true
759
+ },
760
+ {
761
+ sql: `alter table ${table} drop constraint ${constraint}`,
762
+ kind: "drop-not-null-check",
763
+ action: "ddl",
764
+ lock: ACCESS,
765
+ transactional: true
766
+ }
767
+ ];
768
+ }
769
+ function concurrentIndexSql(sql) {
770
+ const unique = "create unique index ";
771
+ const plain = "create index ";
772
+ if (sql.startsWith(unique))
773
+ return `create unique index concurrently ${sql.slice(unique.length)}`;
774
+ if (sql.startsWith(plain))
775
+ return `create index concurrently ${sql.slice(plain.length)}`;
776
+ return sql;
777
+ }
778
+ function concurrentDropIndexSql(sql) {
779
+ const prefix = "drop index ";
780
+ if (!sql.startsWith(prefix))
781
+ return sql;
782
+ return `drop index concurrently ${sql.slice(prefix.length)}`;
783
+ }
784
+ function volatileDefault(expression, catalogs) {
785
+ if (expression === undefined)
786
+ return false;
787
+ const known = catalogVolatility(catalogs);
788
+ for (const name of functionCalls(expression)) {
789
+ const lower = name.toLowerCase();
790
+ if (VOLATILE_BUILTIN.has(lower))
791
+ return true;
792
+ if (STABLE_BUILTIN.has(lower))
793
+ continue;
794
+ if (known.get(lower) === "volatile")
795
+ return true;
366
796
  }
367
- return suffix;
797
+ return false;
368
798
  }
369
- function canonicalScalar(typeName) {
370
- const varying = /^(?:varchar|character varying)(\(\d+\))$/.exec(typeName);
371
- if (varying !== null)
372
- return `character varying${varying[1] ?? ""}`;
373
- const fixed = /^(?:char|character)(\(\d+\))$/.exec(typeName);
374
- if (fixed !== null)
375
- return `character${fixed[1] ?? ""}`;
376
- const timed = /^(timestamp with time zone|timestamp without time zone|time with time zone|time without time zone|timestamptz|timestamp|timetz|time)(\(\d+\))?$/.exec(typeName);
377
- if (timed !== null)
378
- return canonicalTime(timed[1] ?? "", timed[2] ?? "");
379
- const numeric = /^(?:decimal|numeric)(\(\d+(?:,\d+)?\))?$/.exec(typeName);
380
- if (numeric !== null)
381
- return `numeric${numeric[1] ?? ""}`;
382
- switch (typeName) {
383
- case "int":
384
- case "int4":
385
- case "integer":
386
- return "integer";
387
- case "int8":
388
- case "bigint":
389
- return "bigint";
390
- case "int2":
391
- case "smallint":
392
- return "smallint";
393
- case "bool":
394
- case "boolean":
395
- return "boolean";
396
- case "float8":
397
- case "double precision":
398
- return "double precision";
399
- case "float4":
400
- case "real":
401
- return "real";
402
- default:
403
- return typeName;
799
+ function concurrentIndexStep(object, schema) {
800
+ const plain = createObjectSql(object, schema) ?? "";
801
+ return {
802
+ sql: concurrentIndexSql(plain),
803
+ kind: "create-index",
804
+ action: "ddl",
805
+ lock: SHARE_UPDATE,
806
+ transactional: false
807
+ };
808
+ }
809
+ function constraintSteps(object, schema) {
810
+ const kind = object.definition.constraintKind;
811
+ if (kind === "unique" || kind === "primaryKey")
812
+ return usingIndexSteps(object, schema);
813
+ return notValidSteps(object, schema);
814
+ }
815
+ function notValidSteps(object, schema) {
816
+ const plain = createObjectSql(object, schema);
817
+ if (plain === undefined)
818
+ return [];
819
+ const table = qualify(schema, object.identity.parent.name);
820
+ const name = quoteIdent(object.identity.name);
821
+ const referenced = object.definition.references;
822
+ const referencedLock = referenced === undefined ? undefined : qualify(schema, referenced.parent.name);
823
+ return [
824
+ {
825
+ sql: `${plain} not valid`,
826
+ kind: "add-constraint",
827
+ action: "ddl",
828
+ lock: referencedLock === undefined ? ACCESS : `${ACCESS}; SHARE ROW EXCLUSIVE on ${referencedLock}`,
829
+ transactional: true
830
+ },
831
+ {
832
+ sql: `alter table ${table} validate constraint ${name}`,
833
+ kind: "validate-constraint",
834
+ action: "ddl",
835
+ lock: referencedLock === undefined ? SHARE_UPDATE : `${SHARE_UPDATE}; ROW SHARE on ${referencedLock}`,
836
+ transactional: true
837
+ }
838
+ ];
839
+ }
840
+ function usingIndexSteps(object, schema) {
841
+ const table = qualify(schema, object.identity.parent.name);
842
+ const name = quoteIdent(object.identity.name);
843
+ const columns = object.definition.columns.map((column) => quoteIdent(column)).join(", ");
844
+ const nulls = object.definition.nullsNotDistinct ? " nulls not distinct" : "";
845
+ const verb = object.definition.constraintKind === "primaryKey" ? "primary key" : "unique";
846
+ const defer = object.definition.deferrable ? ` deferrable initially ${object.definition.initially}` : "";
847
+ return [
848
+ {
849
+ sql: `create unique index concurrently ${name} on ${table} (${columns})${nulls}`,
850
+ kind: "create-index",
851
+ action: "ddl",
852
+ lock: SHARE_UPDATE,
853
+ transactional: false
854
+ },
855
+ {
856
+ sql: `alter table ${table} add constraint ${name} ${verb} using index ${name}${defer}`,
857
+ kind: "add-constraint",
858
+ action: "ddl",
859
+ lock: ACCESS,
860
+ transactional: true
861
+ }
862
+ ];
863
+ }
864
+ function volatileColumnSteps(column, schema, catalogs, batchSize) {
865
+ const tableName = column.identity.parent.name;
866
+ const table = qualify(schema, tableName);
867
+ const name = quoteIdent(column.identity.name);
868
+ const collate = column.definition.collation === undefined ? "" : ` collate ${quoteIdent(column.definition.collation)}`;
869
+ const expression = column.definition.defaultExpression ?? "";
870
+ const key = primaryKeyColumns(catalogs, tableName);
871
+ const backfill = {
872
+ table,
873
+ key: key.map((item) => item.quoted),
874
+ batch: batchSize
875
+ };
876
+ const steps = [
877
+ {
878
+ sql: `alter table ${table} add column ${name} ${column.definition.dataType}${collate}`,
879
+ kind: "add-column",
880
+ action: "ddl",
881
+ lock: ACCESS,
882
+ transactional: true
883
+ },
884
+ {
885
+ sql: `alter table ${table} alter column ${name} set default ${expression}`,
886
+ kind: "set-default",
887
+ action: "ddl",
888
+ lock: ACCESS,
889
+ transactional: true
890
+ },
891
+ {
892
+ sql: `update ${table} set ${name} = ${expression} where ${name} is null and ${keyRangePredicate(key)}`,
893
+ kind: "backfill-expand",
894
+ action: "backfill",
895
+ lock: ROW,
896
+ transactional: false,
897
+ backfill
898
+ }
899
+ ];
900
+ if (!column.definition.nullable) {
901
+ steps.push(...notNullSteps(column, schema, "add-column", catalogs));
404
902
  }
903
+ return steps;
405
904
  }
406
- function canonicalTime(name, precision) {
407
- if (name === "timestamptz" || name === "timestamp with time zone") {
408
- return `timestamp${precision} with time zone`;
905
+ function notNullConstraintName(table, column, catalogs) {
906
+ const taken = new Set;
907
+ for (const source of catalogs) {
908
+ for (const object of source.objects) {
909
+ if (object.kind === "constraint" || object.kind === "index")
910
+ taken.add(object.identity.name);
911
+ }
409
912
  }
410
- if (name === "timestamp" || name === "timestamp without time zone") {
411
- return `timestamp${precision} without time zone`;
913
+ const base = fitIdentifier2(`${table}_${column}_notnull`);
914
+ if (!taken.has(base))
915
+ return base;
916
+ return fitIdentifier2(`${table}_${column}_notnull_okm`);
917
+ }
918
+ function catalogVolatility(catalogs) {
919
+ const map = new Map;
920
+ for (const source of catalogs) {
921
+ for (const object of source.objects) {
922
+ if (object.kind !== "function")
923
+ continue;
924
+ const name = object.identity.name.toLowerCase();
925
+ const next = object.definition.volatility;
926
+ const current = map.get(name);
927
+ if (current === "volatile" || next === "volatile")
928
+ map.set(name, "volatile");
929
+ else if (current === undefined)
930
+ map.set(name, next);
931
+ }
412
932
  }
413
- if (name === "timetz" || name === "time with time zone") {
414
- return `time${precision} with time zone`;
933
+ return map;
934
+ }
935
+ function functionCalls(expression) {
936
+ const names = [];
937
+ let index = 0;
938
+ while (index < expression.length) {
939
+ const char = expression[index];
940
+ if (char === "'") {
941
+ index += 1;
942
+ while (index < expression.length) {
943
+ if (expression[index] === "'" && expression[index + 1] === "'") {
944
+ index += 2;
945
+ continue;
946
+ }
947
+ if (expression[index] === "'") {
948
+ index += 1;
949
+ break;
950
+ }
951
+ index += 1;
952
+ }
953
+ continue;
954
+ }
955
+ if (char !== undefined && /[A-Za-z_]/.test(char)) {
956
+ const start = index;
957
+ index += 1;
958
+ while (index < expression.length && /[\w$]/.test(expression[index] ?? ""))
959
+ index += 1;
960
+ let name = expression.slice(start, index);
961
+ if (expression[index] === ".") {
962
+ index += 1;
963
+ const qualifier = index;
964
+ while (index < expression.length && /[\w$]/.test(expression[index] ?? ""))
965
+ index += 1;
966
+ name = expression.slice(qualifier, index);
967
+ }
968
+ while (expression[index] === " ")
969
+ index += 1;
970
+ if (expression[index] === "(")
971
+ names.push(name);
972
+ continue;
973
+ }
974
+ index += 1;
415
975
  }
416
- return `time${precision} without time zone`;
976
+ return names;
417
977
  }
418
978
 
419
979
  // src/tooling/migrate/values.ts
@@ -458,10 +1018,10 @@ function pair(table, column, value) {
458
1018
  }
459
1019
 
460
1020
  // src/tooling/migrate/plan.ts
461
- var ACCESS = "ACCESS EXCLUSIVE";
1021
+ var ACCESS2 = "ACCESS EXCLUSIVE";
462
1022
  var SHARE = "SHARE";
463
- var SHARE_UPDATE = "SHARE UPDATE EXCLUSIVE";
464
- var ROW = "ROW EXCLUSIVE";
1023
+ var SHARE_UPDATE2 = "SHARE UPDATE EXCLUSIVE";
1024
+ var ROW2 = "ROW EXCLUSIVE";
465
1025
  function planMigration(input) {
466
1026
  const request = {
467
1027
  ...input,
@@ -469,6 +1029,7 @@ function planMigration(input) {
469
1029
  after: omitManagedObjects(input.after)
470
1030
  };
471
1031
  const schema = request.schema ?? "public";
1032
+ const batchSize = resolveBatchSize(request.batchSize);
472
1033
  const renames = request.renames ?? [];
473
1034
  const replacements = request.replacements ?? [];
474
1035
  assertNoChains(replacements);
@@ -532,15 +1093,20 @@ function planMigration(input) {
532
1093
  refuseRoutineDrops(beforeBy, afterBy, dropKeys);
533
1094
  omitOwnedSequences(createKeys, afterBy, request.after);
534
1095
  omitOwnedSequences(dropKeys, beforeBy, renamed);
535
- const privileges = privilegeSql(renamed.objects, request.after.objects, schema);
1096
+ const recreated = new Set;
1097
+ for (const key of createKeys) {
1098
+ if (dropKeys.has(key))
1099
+ recreated.add(key);
1100
+ }
1101
+ const privileges = privilegeSql(renamed.objects, request.after.objects, schema, recreated);
536
1102
  const steps = [];
537
1103
  steps.push(...renameSteps(request.before, renames, schema));
538
- for (const sql of privileges.revoke)
539
- steps.push(step(sql, "contract", "ddl", ACCESS));
1104
+ for (const item of privileges.revoke)
1105
+ steps.push(step(item.sql, item.kind, "ddl", ACCESS2));
540
1106
  steps.push(...keptNames.map((item) => renameShapeStep(item, schema)));
541
1107
  steps.push(...enumAddSteps(enums));
542
1108
  steps.push(...domainCheckSteps(beforeBy, afterBy, schema));
543
- steps.push(...expandBackfills(picklists, enums, replacements, schema));
1109
+ steps.push(...expandBackfills(picklists, enums, replacements, schema, [renamed, request.after], batchSize));
544
1110
  const droppedTables = new Set([...dropKeys].map((key) => beforeBy.get(key)).filter((object) => object?.kind === "table").map((object) => object.identity.name));
545
1111
  const deferred = new Set(picklists.map((change) => identityKey2(change.before.identity)));
546
1112
  const typeDrops = [];
@@ -551,9 +1117,19 @@ function planMigration(input) {
551
1117
  if (!dropKeys.has(key) || deferred.has(key) || covered(object, droppedTables))
552
1118
  return;
553
1119
  const sql = dropObjectSql(object, schema);
554
- if (sql === undefined)
1120
+ const kind = dropKind(object);
1121
+ if (sql === undefined || kind === undefined)
1122
+ return;
1123
+ if (object.kind === "index" && hasTable(renamed, object.identity.parent.name)) {
1124
+ steps.push({
1125
+ ...step(concurrentDropIndexSql(sql), "drop-index", "ddl", SHARE_UPDATE2, false, [
1126
+ object.identity.parent.name
1127
+ ]),
1128
+ safeRewrite: true
1129
+ });
555
1130
  return;
556
- steps.push(step(sql, "contract", "ddl", ACCESS));
1131
+ }
1132
+ steps.push(step(sql, kind, "ddl", ACCESS2));
557
1133
  };
558
1134
  for (const object of reverse)
559
1135
  if (object.kind === "trigger")
@@ -585,31 +1161,36 @@ function planMigration(input) {
585
1161
  }
586
1162
  emitDrop(object);
587
1163
  }
588
- for (const object of typeDrops) {
589
- const sql = dropObjectSql(object, schema);
590
- if (sql === undefined)
591
- continue;
592
- steps.push(step(sql, "contract", "ddl", ACCESS));
593
- }
594
- for (const object of extensionDrops) {
595
- const sql = dropObjectSql(object, schema);
596
- if (sql === undefined)
597
- continue;
598
- steps.push(step(sql, "contract", "ddl", ACCESS));
599
- }
1164
+ for (const object of typeDrops)
1165
+ pushDrop(steps, object, schema);
1166
+ for (const object of extensionDrops)
1167
+ pushDrop(steps, object, schema);
600
1168
  steps.push(...extensionAlterSteps(beforeBy, afterBy, dropKeys));
601
1169
  for (const change of alters) {
602
- for (const sql of alterColumnSql(alignedColumnType(change.before, change.after), change.after, schema)) {
603
- steps.push(step(sql, alterClass(sql), "ddl", ACCESS));
1170
+ const aligned = alignedColumnType(change.before, change.after);
1171
+ const statements = alterColumnSql(aligned, change.after, schema);
1172
+ const kinds = columnAlterKinds(aligned, change.after);
1173
+ for (let index = 0;index < statements.length; index += 1) {
1174
+ const sql = statements[index];
1175
+ const kind = kinds[index];
1176
+ if (sql === undefined || kind === undefined)
1177
+ continue;
1178
+ if (kind === "set-not-null") {
1179
+ pushSafe(steps, notNullSteps(change.after, schema, "set-not-null", [renamed, request.after]));
1180
+ continue;
1181
+ }
1182
+ steps.push(step(sql, kind, "ddl", ACCESS2));
604
1183
  }
605
1184
  const identitySql = identityChangeSql(change.before, change.after, schema);
606
- if (identitySql !== undefined)
607
- steps.push(step(identitySql, "expand", "ddl", ACCESS));
1185
+ const identity = identityKind(change.before, change.after);
1186
+ if (identitySql !== undefined && identity !== undefined) {
1187
+ steps.push(step(identitySql, identity, "ddl", ACCESS2));
1188
+ }
608
1189
  }
609
1190
  const createdTables = new Set([...createKeys].map((key) => afterBy.get(key)).filter((object) => object?.kind === "table").map((object) => object.identity.name));
610
1191
  const emitted = new Set;
611
- for (const sql of privileges.prepare)
612
- steps.push(step(sql, "expand", "ddl", ACCESS));
1192
+ for (const item of privileges.prepare)
1193
+ steps.push(step(item.sql, item.kind, "ddl", ACCESS2));
613
1194
  for (const object of creationOrder2(request.after)) {
614
1195
  if (object.kind !== "extension")
615
1196
  continue;
@@ -618,9 +1199,10 @@ function planMigration(input) {
618
1199
  continue;
619
1200
  emitted.add(key);
620
1201
  const sql = createObjectSql(object, schema);
621
- if (sql === undefined)
1202
+ const kind = createKind(object, false);
1203
+ if (sql === undefined || kind === undefined)
622
1204
  continue;
623
- steps.push(step(sql, "expand", "ddl", ACCESS));
1205
+ steps.push(step(sql, kind, "ddl", ACCESS2));
624
1206
  }
625
1207
  for (const object of creationOrder2(request.after)) {
626
1208
  if (object.kind !== "type")
@@ -630,9 +1212,10 @@ function planMigration(input) {
630
1212
  continue;
631
1213
  emitted.add(key);
632
1214
  const sql = createObjectSql(object, schema);
633
- if (sql === undefined)
1215
+ const kind = createKind(object, false);
1216
+ if (sql === undefined || kind === undefined)
634
1217
  continue;
635
- steps.push(step(sql, "expand", "ddl", ACCESS));
1218
+ steps.push(step(sql, kind, "ddl", ACCESS2));
636
1219
  }
637
1220
  for (const object of creationOrder2(request.after)) {
638
1221
  if (object.kind === "type" || object.kind === "extension" || object.kind === "function" || object.kind === "trigger" || object.kind === "view" || object.kind === "materializedView" || ownedByView(object)) {
@@ -650,13 +1233,21 @@ function planMigration(input) {
650
1233
  continue;
651
1234
  emitted.add(key);
652
1235
  if (object.kind === "table") {
653
- steps.push(step(createTableSql(object, request.after, schema), "expand", "ddl", ACCESS));
1236
+ steps.push(step(createTableSql(object, request.after, schema), "create-table", "ddl", ACCESS2));
654
1237
  continue;
655
1238
  }
656
1239
  const sql = createObjectSql(object, schema);
657
- if (sql === undefined)
1240
+ const kind = createKind(object, tableIsNew);
1241
+ if (sql === undefined || kind === undefined)
658
1242
  continue;
659
- steps.push(step(sql, "expand", "ddl", object.kind === "index" ? SHARE : ACCESS));
1243
+ if (!tableIsNew && parent !== undefined && hasTable(renamed, parent)) {
1244
+ const safe = stepsForExistingTable(object, schema, [renamed, request.after], batchSize);
1245
+ if (safe !== undefined) {
1246
+ pushSafe(steps, safe);
1247
+ continue;
1248
+ }
1249
+ }
1250
+ steps.push(step(sql, kind, "ddl", object.kind === "index" ? SHARE : ACCESS2));
660
1251
  }
661
1252
  for (const change of replaces) {
662
1253
  if (sameDefinition(change.before, change.after))
@@ -665,11 +1256,13 @@ function planMigration(input) {
665
1256
  if (dropKeys.has(key) && createKeys.has(key))
666
1257
  continue;
667
1258
  const sql = change.kind === "function" ? functionSql(change.after, schema, true) : viewSql(change.after, schema, true);
1259
+ const kind = change.kind === "function" ? "replace-function" : "replace-view";
668
1260
  steps.push({
669
1261
  sql,
670
- class: "expand",
1262
+ kind,
1263
+ class: classOf(kind),
671
1264
  action: "ddl",
672
- lock: ACCESS,
1265
+ lock: ACCESS2,
673
1266
  transactional: true,
674
1267
  behavior: "change"
675
1268
  });
@@ -683,9 +1276,10 @@ function planMigration(input) {
683
1276
  continue;
684
1277
  emitted.add(key);
685
1278
  const sql = createObjectSql(object, schema);
686
- if (sql === undefined)
1279
+ const created = createKind(object, false);
1280
+ if (sql === undefined || created === undefined)
687
1281
  continue;
688
- steps.push(step(sql, "expand", "ddl", ACCESS));
1282
+ steps.push(step(sql, created, "ddl", ACCESS2));
689
1283
  }
690
1284
  }
691
1285
  for (const object of creationOrder2(request.after)) {
@@ -696,9 +1290,10 @@ function planMigration(input) {
696
1290
  continue;
697
1291
  emitted.add(key);
698
1292
  const sql = createObjectSql(object, schema);
699
- if (sql === undefined)
1293
+ const kind = createKind(object, false);
1294
+ if (sql === undefined || kind === undefined)
700
1295
  continue;
701
- steps.push(step(sql, "expand", "ddl", SHARE));
1296
+ steps.push(step(sql, kind, "ddl", SHARE));
702
1297
  }
703
1298
  for (const object of creationOrder2(request.after)) {
704
1299
  if (object.kind !== "materializedView")
@@ -707,18 +1302,18 @@ function planMigration(input) {
707
1302
  if (!createKeys.has(key))
708
1303
  continue;
709
1304
  assertConcurrentRefresh(object, request.after);
710
- steps.push(step(refreshMaterializedViewSql(object, schema, false), "expand", "backfill", ACCESS));
1305
+ steps.push(step(refreshMaterializedViewSql(object, schema, false), "refresh-matview", "backfill", ACCESS2));
711
1306
  }
712
- for (const sql of privileges.grant)
713
- steps.push(step(sql, "expand", "ddl", ACCESS));
714
- steps.push(...contractSwaps(picklists, enums, replacements, schema));
1307
+ for (const item of privileges.grant)
1308
+ steps.push(step(item.sql, item.kind, "ddl", ACCESS2));
1309
+ steps.push(...contractSwaps(picklists, enums, replacements, schema, [renamed, request.after], batchSize));
715
1310
  return {
716
1311
  name: request.name ?? "migration",
717
1312
  class: overall(steps),
718
1313
  steps
719
1314
  };
720
1315
  }
721
- function formatPlan(plan) {
1316
+ function formatPlan(plan, lockText) {
722
1317
  const lines = [`-- class: ${plan.class}`, `-- name: ${plan.name}`, ""];
723
1318
  if (plan.steps.length === 0) {
724
1319
  lines.push("-- no steps");
@@ -728,14 +1323,21 @@ function formatPlan(plan) {
728
1323
  }
729
1324
  for (const item of plan.steps) {
730
1325
  lines.push(`-- class: ${item.class}`);
1326
+ if (item.kind !== undefined)
1327
+ lines.push(`-- kind: ${item.kind}`);
731
1328
  lines.push(`-- action: ${item.action}`);
732
- lines.push(`-- lock: ${item.lock}`);
1329
+ lines.push(`-- lock: ${lockText === undefined ? item.lock : lockText(item)}`);
1330
+ if (item.backfill !== undefined)
1331
+ lines.push(formatBackfillHeader(item.backfill));
733
1332
  if (!item.transactional)
734
1333
  lines.push("-- transactional: false");
735
1334
  if (item.path !== undefined)
736
1335
  lines.push(`-- path: ${item.path}`);
737
1336
  if (item.behavior === "change")
738
1337
  lines.push("-- behavior: change");
1338
+ for (const allow of item.allows ?? []) {
1339
+ lines.push(`-- okm-allow ${allow.code}: ${allow.reason}`);
1340
+ }
739
1341
  lines.push(`${item.sql};`);
740
1342
  lines.push("");
741
1343
  }
@@ -765,46 +1367,71 @@ function parsePlan(text) {
765
1367
  if (lines[index] === "-- no steps")
766
1368
  break;
767
1369
  let stepClass = "expand";
1370
+ let sawClass = false;
1371
+ let stepKind;
768
1372
  let action = "ddl";
769
1373
  let lock = "";
770
1374
  let transactional = true;
1375
+ let backfill;
771
1376
  let path;
772
1377
  let behavior;
1378
+ const allows = [];
773
1379
  const sql = [];
774
1380
  while (index < lines.length && (lines[index] ?? "") !== "") {
775
1381
  const line = lines[index] ?? "";
776
1382
  index += 1;
777
- if (line.startsWith("-- class: "))
1383
+ if (line.startsWith("-- class: ")) {
778
1384
  stepClass = readClass(line.slice("-- class: ".length));
779
- else if (line.startsWith("-- action: "))
1385
+ sawClass = true;
1386
+ } else if (line.startsWith("-- kind: ")) {
1387
+ const kind = line.slice("-- kind: ".length);
1388
+ if (isStepKind(kind))
1389
+ stepKind = kind;
1390
+ } else if (line.startsWith("-- action: "))
780
1391
  action = line.endsWith("backfill") ? "backfill" : "ddl";
781
1392
  else if (line.startsWith("-- lock: "))
782
1393
  lock = line.slice("-- lock: ".length);
1394
+ else if (line.startsWith("-- backfill "))
1395
+ backfill = parseBackfillHeader(line);
783
1396
  else if (line === "-- transactional: false")
784
1397
  transactional = false;
785
1398
  else if (line === "-- path: unverified")
786
1399
  path = "unverified";
787
1400
  else if (line === "-- behavior: change")
788
1401
  behavior = "change";
1402
+ else if (line.startsWith("-- okm-allow"))
1403
+ allows.push(readAllow(line));
789
1404
  else if (!line.startsWith("--"))
790
1405
  sql.push(line);
791
1406
  }
792
1407
  const statement = sql.join(`
793
1408
  `).replace(/;\s*$/, "");
794
1409
  if (statement.length > 0) {
1410
+ const kind = sawClass ? stepKind : "raw-sql";
1411
+ const classification = sawClass ? stepClass : "unclassified";
795
1412
  steps.push({
796
1413
  sql: statement,
797
- class: stepClass,
1414
+ class: classification,
1415
+ ...kind !== undefined ? { kind } : {},
798
1416
  action,
799
1417
  lock,
800
1418
  transactional,
1419
+ ...backfill !== undefined ? { backfill } : {},
801
1420
  ...path !== undefined ? { path } : {},
802
- ...behavior !== undefined ? { behavior } : {}
1421
+ ...behavior !== undefined ? { behavior } : {},
1422
+ ...allows.length > 0 ? { allows } : {}
803
1423
  });
804
1424
  }
805
1425
  }
806
1426
  return { name, class: steps.length === 0 ? planClass : overall(steps), steps };
807
1427
  }
1428
+ var ALLOW = /^-- okm-allow\s+(OKM\d+)\s*(?::\s*(.*))?$/;
1429
+ function readAllow(line) {
1430
+ const match = ALLOW.exec(line);
1431
+ if (match === null)
1432
+ return { code: "", reason: "" };
1433
+ return { code: match[1] ?? "", reason: (match[2] ?? "").trim() };
1434
+ }
808
1435
  function readClass(value) {
809
1436
  if (value === "contract" || value === "unclassified")
810
1437
  return value;
@@ -856,7 +1483,8 @@ function renameSteps(source, renames, schema) {
856
1483
  if (rename.kind !== "table" || !hasTable(source, rename.from))
857
1484
  continue;
858
1485
  tables.set(rename.to, rename.from);
859
- steps.push(step(`alter table ${qualify(schema, rename.from)} rename to ${quoteIdent(rename.to)}`, "contract", "ddl", ACCESS));
1486
+ steps.push(step(`alter table ${qualify(schema, rename.from)} rename to ${quoteIdent(rename.to)}`, "rename-table", "ddl", ACCESS2));
1487
+ steps.push(...sequenceRenames(source, rename, schema));
860
1488
  }
861
1489
  for (const rename of renames) {
862
1490
  if (rename.kind !== "column")
@@ -864,7 +1492,25 @@ function renameSteps(source, renames, schema) {
864
1492
  const previousTable = tables.get(rename.table) ?? rename.table;
865
1493
  if (!hasColumn(source, previousTable, rename.from))
866
1494
  continue;
867
- steps.push(step(`alter table ${qualify(schema, rename.table)} rename column ${quoteIdent(rename.from)} to ${quoteIdent(rename.to)}`, "contract", "ddl", ACCESS));
1495
+ steps.push(step(`alter table ${qualify(schema, rename.table)} rename column ${quoteIdent(rename.from)} to ${quoteIdent(rename.to)}`, "rename-column", "ddl", ACCESS2));
1496
+ }
1497
+ return steps;
1498
+ }
1499
+ function sequenceRenames(source, rename, schema) {
1500
+ const steps = [];
1501
+ for (const object of source.objects) {
1502
+ if (object.kind !== "column" || object.definition.identity === undefined)
1503
+ continue;
1504
+ if (object.identity.parent.name !== rename.from)
1505
+ continue;
1506
+ const fromName = fitIdentifier2(`${rename.from}_${object.identity.name}_seq`);
1507
+ const toName = fitIdentifier2(`${rename.to}_${object.identity.name}_seq`);
1508
+ if (fromName === toName)
1509
+ continue;
1510
+ const owned = source.objects.some((item) => item.kind === "sequence" && item.identity.name === fromName);
1511
+ if (!owned)
1512
+ continue;
1513
+ steps.push(step(`alter sequence ${qualify(schema, fromName)} rename to ${quoteIdent(toName)}`, "rename-sequence", "ddl", ACCESS2));
868
1514
  }
869
1515
  return steps;
870
1516
  }
@@ -913,12 +1559,14 @@ function withoutNameKey(value) {
913
1559
  }
914
1560
  function renameShapeStep(pair, schema) {
915
1561
  if (pair.from.kind === "index" && pair.to.kind === "index") {
916
- return step(`alter index ${qualify(schema, pair.from.identity.name)} rename to ${quoteIdent(pair.to.identity.name)}`, "contract", "ddl", ACCESS);
1562
+ return step(`alter index ${qualify(schema, pair.from.identity.name)} rename to ${quoteIdent(pair.to.identity.name)}`, "rename-index", "ddl", ACCESS2);
917
1563
  }
918
1564
  if (pair.from.kind !== "constraint" || pair.to.kind !== "constraint") {
919
- return step("select 1", "unclassified", "ddl", ACCESS);
1565
+ throw new OkmError2("invalid", "A same-shape rename is an index or a constraint.", {
1566
+ fix: { summary: "The planner only renames an index or a constraint in place." }
1567
+ });
920
1568
  }
921
- return step(`alter table ${qualify(schema, pair.from.identity.parent.name)} rename constraint ${quoteIdent(pair.from.identity.name)} to ${quoteIdent(pair.to.identity.name)}`, "contract", "ddl", ACCESS);
1569
+ return step(`alter table ${qualify(schema, pair.from.identity.parent.name)} rename constraint ${quoteIdent(pair.from.identity.name)} to ${quoteIdent(pair.to.identity.name)}`, "rename-constraint", "ddl", ACCESS2);
922
1570
  }
923
1571
  function assertUnambiguous(beforeBy, afterBy) {
924
1572
  const dropped = new Map;
@@ -1092,24 +1740,25 @@ function enumAddSteps(enums) {
1092
1740
  if (change.kind !== "add")
1093
1741
  continue;
1094
1742
  for (const sql of change.statements) {
1095
- steps.push(step(sql, "expand", "ddl", ACCESS, false));
1743
+ steps.push(step(sql, "add-enum-value", "ddl", ACCESS2, false));
1096
1744
  }
1097
1745
  }
1098
1746
  return steps;
1099
1747
  }
1100
- function expandBackfills(picklists, enums, replacements, schema) {
1101
- return dataSteps(picklists, enums, replacements, schema, "expand");
1748
+ function expandBackfills(picklists, enums, replacements, schema, catalogs, batchSize) {
1749
+ return dataSteps(picklists, enums, replacements, schema, catalogs, batchSize, "expand");
1102
1750
  }
1103
- function contractSwaps(picklists, enums, replacements, schema) {
1751
+ function contractSwaps(picklists, enums, replacements, schema, catalogs, batchSize) {
1104
1752
  const steps = [];
1105
- steps.push(...dataSteps(picklists, enums, replacements, schema, "contract"));
1753
+ steps.push(...dataSteps(picklists, enums, replacements, schema, catalogs, batchSize, "contract"));
1106
1754
  for (const change of picklists) {
1107
1755
  const table = qualify(schema, change.after.identity.parent.name);
1108
1756
  const name = quoteIdent(change.after.identity.name);
1109
- steps.push(step(`alter table ${table} drop constraint ${name}`, change.removed.length > 0 ? "contract" : "expand", "ddl", ACCESS));
1757
+ const kind = change.removed.length > 0 ? "narrow-check" : "widen-check";
1758
+ steps.push(step(`alter table ${table} drop constraint ${name}`, kind, "ddl", ACCESS2));
1110
1759
  const expression = change.after.definition.expression ?? "true";
1111
- steps.push(step(`alter table ${table} add constraint ${name} check (${expression}) not valid`, change.removed.length > 0 ? "contract" : "expand", "ddl", ACCESS));
1112
- steps.push(step(`alter table ${table} validate constraint ${name}`, change.removed.length > 0 ? "contract" : "expand", "ddl", SHARE_UPDATE));
1760
+ steps.push(step(`alter table ${table} add constraint ${name} check (${expression}) not valid`, kind, "ddl", ACCESS2));
1761
+ steps.push(step(`alter table ${table} validate constraint ${name}`, kind, "ddl", SHARE_UPDATE2));
1113
1762
  }
1114
1763
  for (const change of enums) {
1115
1764
  if (change.kind !== "replace")
@@ -1117,19 +1766,20 @@ function contractSwaps(picklists, enums, replacements, schema) {
1117
1766
  const type = qualify(schema, change.name);
1118
1767
  const old = quoteIdent(`${change.name}_old`);
1119
1768
  const labels = change.labels.map((label) => quoteLiteral(label)).join(", ");
1120
- steps.push(step(`alter type ${type} rename to ${old}`, "contract", "ddl", ACCESS));
1121
- steps.push(step(`create type ${type} as enum (${labels})`, "contract", "ddl", ACCESS));
1769
+ steps.push(step(`alter type ${type} rename to ${old}`, "rename-enum", "ddl", ACCESS2));
1770
+ steps.push(step(`create type ${type} as enum (${labels})`, "recreate-enum", "ddl", ACCESS2));
1122
1771
  for (const column of change.columns) {
1123
1772
  const table = qualify(schema, column.table);
1124
1773
  const name = quoteIdent(column.column);
1125
- steps.push(step(`alter table ${table} alter column ${name} type ${type} using ${name}::text::${type}`, "contract", "ddl", ACCESS));
1774
+ steps.push(step(`alter table ${table} alter column ${name} type ${type} using ${name}::text::${type}`, "set-enum-column", "ddl", ACCESS2));
1126
1775
  }
1127
- steps.push(step(`drop type ${qualify(schema, `${change.name}_old`)}`, "contract", "ddl", ACCESS));
1776
+ steps.push(step(`drop type ${qualify(schema, `${change.name}_old`)}`, "drop-enum", "ddl", ACCESS2));
1128
1777
  }
1129
1778
  return steps;
1130
1779
  }
1131
- function dataSteps(picklists, enums, replacements, schema, phase) {
1780
+ function dataSteps(picklists, enums, replacements, schema, catalogs, batchSize, phase) {
1132
1781
  const steps = [];
1782
+ const kind = phase === "expand" ? "backfill-expand" : "backfill-contract";
1133
1783
  for (const change of picklists) {
1134
1784
  if (change.removed.length === 0)
1135
1785
  continue;
@@ -1138,7 +1788,7 @@ function dataSteps(picklists, enums, replacements, schema, phase) {
1138
1788
  const replacement = findReplacement(replacements, table, change.column, value);
1139
1789
  if (replacement === undefined)
1140
1790
  continue;
1141
- steps.push(step(updateSql(schema, table, change.column, value, replacement.to), phase, "backfill", ROW));
1791
+ steps.push(backfillStep(updateSql(schema, table, change.column, value, replacement.to, catalogs, batchSize), kind));
1142
1792
  }
1143
1793
  }
1144
1794
  for (const change of enums) {
@@ -1151,7 +1801,7 @@ function dataSteps(picklists, enums, replacements, schema, phase) {
1151
1801
  const replacement = findReplacement(replacements, column.table, column.column, value);
1152
1802
  if (replacement === undefined)
1153
1803
  continue;
1154
- steps.push(step(updateSql(schema, column.table, column.column, value, replacement.to, true), phase, "backfill", ROW));
1804
+ steps.push(backfillStep(updateSql(schema, column.table, column.column, value, replacement.to, catalogs, batchSize, true), kind));
1155
1805
  }
1156
1806
  }
1157
1807
  }
@@ -1271,10 +1921,10 @@ function domainCheckSteps(beforeBy, afterBy, schema) {
1271
1921
  const current = quoteIdent(fitIdentifier2(`${name}_check`));
1272
1922
  const upcoming = quoteIdent(fitIdentifier2(`${name}_check_next`));
1273
1923
  const expression = next.definition.check;
1274
- steps.push(step(`alter domain ${type} add constraint ${upcoming} check (${expression}) not valid`, "expand", "ddl", ACCESS));
1275
- steps.push(step(`alter domain ${type} validate constraint ${upcoming}`, "expand", "ddl", ACCESS));
1276
- steps.push(step(`alter domain ${type} drop constraint ${current}`, "contract", "ddl", ACCESS));
1277
- steps.push(step(`alter domain ${type} rename constraint ${upcoming} to ${current}`, "expand", "ddl", ACCESS));
1924
+ steps.push(step(`alter domain ${type} add constraint ${upcoming} check (${expression}) not valid`, "add-domain-check", "ddl", ACCESS2));
1925
+ steps.push(step(`alter domain ${type} validate constraint ${upcoming}`, "validate-domain-check", "ddl", ACCESS2));
1926
+ steps.push(step(`alter domain ${type} drop constraint ${current}`, "drop-domain-check", "ddl", ACCESS2));
1927
+ steps.push(step(`alter domain ${type} rename constraint ${upcoming} to ${current}`, "rename-domain-check", "ddl", ACCESS2));
1278
1928
  }
1279
1929
  return steps;
1280
1930
  }
@@ -1448,6 +2098,40 @@ function omitOwnedSequences(keys, by, source) {
1448
2098
  keys.delete(key);
1449
2099
  }
1450
2100
  }
2101
+ var TRAILING_CAST = /^(.*)::((?:timestamp|time)(?:\(\d+\))? (?:with|without) time zone|character varying(?:\(\d+\))?|[A-Za-z_][\w$]*(?:\([^)]*\))?)$/;
2102
+ function canonicalDefault(expression, dataType) {
2103
+ if (expression === undefined)
2104
+ return {};
2105
+ const match = TRAILING_CAST.exec(expression.trim());
2106
+ const body = match?.[1]?.trim();
2107
+ const cast = match?.[2];
2108
+ if (body === undefined || body.length === 0 || cast === undefined)
2109
+ return { defaultExpression: expression };
2110
+ if (!defaultCastMatches(cast, dataType))
2111
+ return { defaultExpression: expression };
2112
+ return { defaultExpression: body };
2113
+ }
2114
+ function defaultCastMatches(cast, dataType) {
2115
+ if (canonicalTypeName(cast) === canonicalTypeName(dataType))
2116
+ return true;
2117
+ const castBase = reprintedDefaultBase(cast);
2118
+ const columnBase = reprintedDefaultBase(dataType);
2119
+ return castBase !== undefined && castBase === columnBase;
2120
+ }
2121
+ function reprintedDefaultBase(typeName) {
2122
+ const raw = typeName.trim();
2123
+ if (/^(?:bpchar|character|char)(?:\(\d+\))?$/.test(raw))
2124
+ return "character";
2125
+ const canonical = canonicalTypeName(raw);
2126
+ if (/^character varying(?:\(\d+\))?$/.test(canonical))
2127
+ return "character varying";
2128
+ if (/^character(?:\(\d+\))?$/.test(canonical))
2129
+ return "character";
2130
+ const time = /^(timestamp|time)(?:\(\d+\))? (with time zone|without time zone)$/.exec(canonical);
2131
+ if (time !== null)
2132
+ return `${time[1] ?? ""} ${time[2] ?? ""}`;
2133
+ return;
2134
+ }
1451
2135
  function sameDefinition(left, right) {
1452
2136
  if (left.kind === "materializedView" && right.kind === "materializedView") {
1453
2137
  return stable(canonicalColumns(left.definition.columns)) === stable(canonicalColumns(right.definition.columns)) && left.definition.query === right.definition.query;
@@ -1457,6 +2141,11 @@ function sameDefinition(left, right) {
1457
2141
  function forComparison(object) {
1458
2142
  switch (object.kind) {
1459
2143
  case "column":
2144
+ return {
2145
+ ...object.definition,
2146
+ dataType: canonicalTypeName(object.definition.dataType),
2147
+ ...canonicalDefault(object.definition.defaultExpression, object.definition.dataType)
2148
+ };
1460
2149
  case "sequence":
1461
2150
  return { ...object.definition, dataType: canonicalTypeName(object.definition.dataType) };
1462
2151
  case "view":
@@ -1528,22 +2217,30 @@ function anchoredParent(object) {
1528
2217
  }
1529
2218
  return;
1530
2219
  }
1531
- function alterClass(sql) {
1532
- if (sql.includes("set data type") || sql.includes("set not null"))
1533
- return "contract";
1534
- return "expand";
1535
- }
1536
2220
  function overall(steps) {
1537
- if (steps.some((item) => item.class === "contract"))
1538
- return "contract";
1539
- if (steps.some((item) => item.class === "unclassified"))
1540
- return "unclassified";
1541
- return "expand";
2221
+ return strictestClass(steps.map((item) => item.class));
1542
2222
  }
1543
- function updateSql(schema, table, column, from, to, cast = false) {
2223
+ function updateSql(schema, table, column, from, to, catalogs, batchSize, cast = false) {
2224
+ const key = primaryKeyColumns(catalogs, table);
2225
+ const qualified = qualify(schema, table);
1544
2226
  const value = to === null ? "null" : quoteLiteral(to);
1545
2227
  const compare = cast ? `${quoteIdent(column)}::text` : quoteIdent(column);
1546
- return `update ${qualify(schema, table)} set ${quoteIdent(column)} = ${value} where ${compare} = ${quoteLiteral(from)}`;
2228
+ const where = `${compare} = ${quoteLiteral(from)}`;
2229
+ const backfill = {
2230
+ table: qualified,
2231
+ key: key.map((item) => item.quoted),
2232
+ batch: batchSize
2233
+ };
2234
+ return {
2235
+ sql: `update ${qualified} set ${quoteIdent(column)} = ${value} where ${where} and ${keyRangePredicate(key)}`,
2236
+ backfill
2237
+ };
2238
+ }
2239
+ function backfillStep(planned, kind) {
2240
+ return {
2241
+ ...step(planned.sql, kind, "backfill", ROW2, false),
2242
+ backfill: planned.backfill
2243
+ };
1547
2244
  }
1548
2245
  function hasTable(source, name) {
1549
2246
  return source.objects.some((object) => object.kind === "table" && object.identity.name === name);
@@ -1554,8 +2251,167 @@ function hasColumn(source, table, name) {
1554
2251
  function index2(source) {
1555
2252
  return source.objects.map((object) => ({ key: identityKey2(object.identity), object }));
1556
2253
  }
1557
- function step(sql, classification, action, lock, transactional = true) {
1558
- return { sql, class: classification, action, lock, transactional };
2254
+ function step(sql, kind, action, lock, transactional = true, extraTables) {
2255
+ const tables = touchedTables(sql, lock, extraTables);
2256
+ return {
2257
+ sql,
2258
+ kind,
2259
+ class: classOf(kind),
2260
+ action,
2261
+ lock,
2262
+ transactional,
2263
+ ...tables.length > 0 ? { tables } : {}
2264
+ };
2265
+ }
2266
+ function pushSafe(steps, safe) {
2267
+ for (const item of safe) {
2268
+ steps.push({
2269
+ ...step(item.sql, item.kind, item.action, item.lock, item.transactional),
2270
+ safeRewrite: true,
2271
+ ...item.backfill !== undefined ? { backfill: item.backfill } : {}
2272
+ });
2273
+ }
2274
+ }
2275
+ var TABLE_PREFIX = /\b(?:create|alter|drop)\s+table(?:\s+if\s+exists)?\s+|\bupdate\s+|\binsert\s+into\s+|\breferences\s+|\bon\s+table\s+|\bon\s+/gi;
2276
+ function touchedTables(sql, lock, extra) {
2277
+ const names = [];
2278
+ const text = `${sql}
2279
+ ${lock}`;
2280
+ TABLE_PREFIX.lastIndex = 0;
2281
+ for (let match = TABLE_PREFIX.exec(text);match !== null; match = TABLE_PREFIX.exec(text)) {
2282
+ const found = readQualified(text, match.index + match[0].length);
2283
+ if (found !== undefined)
2284
+ pushTable(names, found.name);
2285
+ }
2286
+ for (const name of extra ?? [])
2287
+ pushTable(names, name);
2288
+ return names;
2289
+ }
2290
+ function pushTable(names, name) {
2291
+ if (!names.includes(name))
2292
+ names.push(name);
2293
+ }
2294
+ function readQualified(text, start) {
2295
+ const schema = readIdent(text, start);
2296
+ if (schema === undefined)
2297
+ return;
2298
+ if (text[schema.end] !== "." || text[schema.end + 1] !== '"')
2299
+ return;
2300
+ const name = readIdent(text, schema.end + 1);
2301
+ if (name === undefined || text[name.end] === ".")
2302
+ return;
2303
+ return name;
2304
+ }
2305
+ function readIdent(text, start) {
2306
+ if (text[start] !== '"')
2307
+ return;
2308
+ let value = "";
2309
+ let index = start + 1;
2310
+ while (index < text.length) {
2311
+ const char = text[index];
2312
+ if (char === '"') {
2313
+ if (text[index + 1] === '"') {
2314
+ value += '"';
2315
+ index += 2;
2316
+ continue;
2317
+ }
2318
+ return { name: value, end: index + 1 };
2319
+ }
2320
+ value += char ?? "";
2321
+ index += 1;
2322
+ }
2323
+ return;
2324
+ }
2325
+ function pushDrop(steps, object, schema) {
2326
+ const sql = dropObjectSql(object, schema);
2327
+ const kind = dropKind(object);
2328
+ if (sql === undefined || kind === undefined)
2329
+ return;
2330
+ steps.push(step(sql, kind, "ddl", ACCESS2));
2331
+ }
2332
+ function createKind(object, tableIsNew) {
2333
+ switch (object.kind) {
2334
+ case "table":
2335
+ return "create-table";
2336
+ case "column":
2337
+ return requiredColumn(object) && !tableIsNew ? "add-column-required" : "add-column";
2338
+ case "index":
2339
+ return "create-index";
2340
+ case "constraint":
2341
+ return "add-constraint";
2342
+ case "sequence":
2343
+ return "create-sequence";
2344
+ case "type":
2345
+ return isDomain(object.definition) ? "create-domain" : "create-enum";
2346
+ case "extension":
2347
+ return "create-extension";
2348
+ case "function":
2349
+ return "create-function";
2350
+ case "trigger":
2351
+ return "create-trigger";
2352
+ case "view":
2353
+ return "create-view";
2354
+ case "materializedView":
2355
+ return "create-matview";
2356
+ default:
2357
+ return;
2358
+ }
2359
+ }
2360
+ function dropKind(object) {
2361
+ switch (object.kind) {
2362
+ case "table":
2363
+ return "drop-table";
2364
+ case "column":
2365
+ return "drop-column";
2366
+ case "index":
2367
+ return "drop-index";
2368
+ case "constraint":
2369
+ return "drop-constraint";
2370
+ case "sequence":
2371
+ return "drop-sequence";
2372
+ case "type":
2373
+ return isDomain(object.definition) ? "drop-domain" : "drop-enum";
2374
+ case "extension":
2375
+ return "drop-extension";
2376
+ case "function":
2377
+ return "drop-function";
2378
+ case "trigger":
2379
+ return "drop-trigger";
2380
+ case "view":
2381
+ return "drop-view";
2382
+ case "materializedView":
2383
+ return "drop-matview";
2384
+ default:
2385
+ return;
2386
+ }
2387
+ }
2388
+ function requiredColumn(column) {
2389
+ const definition = column.definition;
2390
+ return !definition.nullable && definition.defaultExpression === undefined && definition.identity === undefined && definition.generated === undefined;
2391
+ }
2392
+ function columnAlterKinds(before, after) {
2393
+ const kinds = [];
2394
+ if (before.definition.dataType !== after.definition.dataType || before.definition.collation !== after.definition.collation) {
2395
+ kinds.push("set-column-type");
2396
+ }
2397
+ if (before.definition.nullable !== after.definition.nullable) {
2398
+ kinds.push(after.definition.nullable ? "drop-not-null" : "set-not-null");
2399
+ }
2400
+ if (before.definition.defaultExpression !== after.definition.defaultExpression) {
2401
+ kinds.push(after.definition.defaultExpression === undefined ? "drop-default" : "set-default");
2402
+ }
2403
+ return kinds;
2404
+ }
2405
+ function identityKind(before, after) {
2406
+ if (stable(before.definition.identity) === stable(after.definition.identity))
2407
+ return;
2408
+ const next = after.definition.identity;
2409
+ const previous = before.definition.identity;
2410
+ if (next === undefined)
2411
+ return "drop-identity";
2412
+ if (previous === undefined)
2413
+ return "add-identity";
2414
+ return next.always ? "set-identity-always" : "set-identity-by-default";
1559
2415
  }
1560
2416
  function stable(value) {
1561
2417
  return JSON.stringify(order(value));
@@ -1573,4 +2429,4 @@ function order(value) {
1573
2429
  return value;
1574
2430
  }
1575
2431
 
1576
- export { createdRoleName, changesRole, assertExtensionsAvailable, omitManagedObjects, parseReplace, planMigration, formatPlan, parsePlan, staleRenames };
2432
+ export { canonicalTypeName, createdRoleName, createRoleOnce, changesRole, DEFAULT_BACKFILL_PAUSE_MS, DEFAULT_BACKFILL_STATEMENT_TIMEOUT_MS, batchSizeField, aboutBatchCount, boundaryQuery, checkedCast, classOf, strictestClass, assertExtensionsAvailable, omitManagedObjects, parseReplace, planMigration, formatPlan, parsePlan, staleRenames };