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