pg-migration-engine 0.1.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/LICENSE +35 -0
  2. package/README.md +304 -0
  3. package/package.json +52 -0
  4. package/src/behavioral/behavioral-applier.js +374 -0
  5. package/src/behavioral/behavioral-extractor.js +266 -0
  6. package/src/behavioral/behavioral-puller.js +35 -0
  7. package/src/behavioral/index.js +4 -0
  8. package/src/behavioral/phase-sorter.js +24 -0
  9. package/src/constants.js +97 -0
  10. package/src/ddl-generator/alter-generator.js +1512 -0
  11. package/src/ddl-generator/comment-generator.js +95 -0
  12. package/src/ddl-generator/create-generator.js +1426 -0
  13. package/src/ddl-generator/drop-generator.js +194 -0
  14. package/src/ddl-generator/generator.js +78 -0
  15. package/src/ddl-generator/grant-generator.js +31 -0
  16. package/src/ddl-generator/index.js +3 -0
  17. package/src/ddl-generator/pg-version.js +15 -0
  18. package/src/ddl-generator/rename-generator.js +88 -0
  19. package/src/ddl-generator/safe-patterns.js +241 -0
  20. package/src/differ/change-classifier.js +192 -0
  21. package/src/differ/dependency-resolver.js +523 -0
  22. package/src/differ/index.js +12 -0
  23. package/src/differ/object-matcher.js +510 -0
  24. package/src/differ/property-differ.js +1134 -0
  25. package/src/differ/risk-tagger.js +561 -0
  26. package/src/differ/schema-differ.js +309 -0
  27. package/src/differ/utils/levenshtein.js +210 -0
  28. package/src/differ/utils/path-builder.js +198 -0
  29. package/src/differ/utils/type-compatibility.js +472 -0
  30. package/src/errors.js +147 -0
  31. package/src/executor/drift-detector.js +221 -0
  32. package/src/executor/index.js +7 -0
  33. package/src/executor/lock-manager.js +308 -0
  34. package/src/executor/migration-executor.js +1249 -0
  35. package/src/executor/progress-tracker.js +81 -0
  36. package/src/executor/recovery-manager.js +47 -0
  37. package/src/executor/snapshot-manager.js +22 -0
  38. package/src/executor/sql-splitter.js +120 -0
  39. package/src/executor/transaction-manager.js +288 -0
  40. package/src/index.js +483 -0
  41. package/src/introspection/index.js +4 -0
  42. package/src/introspection/introspector.js +250 -0
  43. package/src/introspection/queries/access-methods.js +29 -0
  44. package/src/introspection/queries/casts.js +38 -0
  45. package/src/introspection/queries/collations.js +51 -0
  46. package/src/introspection/queries/comments.js +66 -0
  47. package/src/introspection/queries/constraints.js +65 -0
  48. package/src/introspection/queries/conversions.js +39 -0
  49. package/src/introspection/queries/databases.js +43 -0
  50. package/src/introspection/queries/default-privileges.js +37 -0
  51. package/src/introspection/queries/event-triggers.js +43 -0
  52. package/src/introspection/queries/extensions.js +21 -0
  53. package/src/introspection/queries/foreign-data.js +136 -0
  54. package/src/introspection/queries/functions.js +75 -0
  55. package/src/introspection/queries/grants.js +38 -0
  56. package/src/introspection/queries/index.js +33 -0
  57. package/src/introspection/queries/indexes.js +99 -0
  58. package/src/introspection/queries/inheritance.js +24 -0
  59. package/src/introspection/queries/multiranges.js +37 -0
  60. package/src/introspection/queries/operators.js +180 -0
  61. package/src/introspection/queries/partitions.js +42 -0
  62. package/src/introspection/queries/pg18-19.js +43 -0
  63. package/src/introspection/queries/policies.js +35 -0
  64. package/src/introspection/queries/procedural-languages.js +36 -0
  65. package/src/introspection/queries/publications.js +114 -0
  66. package/src/introspection/queries/roles.js +66 -0
  67. package/src/introspection/queries/rules.js +49 -0
  68. package/src/introspection/queries/sequences.js +37 -0
  69. package/src/introspection/queries/statistics.js +74 -0
  70. package/src/introspection/queries/subscriptions.js +99 -0
  71. package/src/introspection/queries/tables.js +151 -0
  72. package/src/introspection/queries/tablespaces.js +36 -0
  73. package/src/introspection/queries/text-search.js +146 -0
  74. package/src/introspection/queries/triggers.js +61 -0
  75. package/src/introspection/queries/types.js +106 -0
  76. package/src/introspection/queries/views.js +146 -0
  77. package/src/introspection/translator.js +1283 -0
  78. package/src/introspection/version-detector.js +25 -0
  79. package/src/planner/backfill-planner.js +36 -0
  80. package/src/planner/dry-run.js +21 -0
  81. package/src/planner/index.js +6 -0
  82. package/src/planner/migration-planner.js +356 -0
  83. package/src/planner/smart-migrator.js +82 -0
  84. package/src/planner/step-sequencer.js +61 -0
  85. package/src/planner/type-registry.js +47 -0
  86. package/src/risk/compatibility-checker.js +29 -0
  87. package/src/risk/data-loss-checker.js +32 -0
  88. package/src/risk/destructive-checker.js +38 -0
  89. package/src/risk/index.js +6 -0
  90. package/src/risk/lock-analyzer.js +39 -0
  91. package/src/risk/recommendations.js +44 -0
  92. package/src/risk/risk-engine.js +25 -0
  93. package/src/storage/index.js +5 -0
  94. package/src/storage/memory-storage.js +87 -0
  95. package/src/storage/migration-table.js +423 -0
  96. package/src/storage/rollback-generator.js +344 -0
  97. package/src/types/changes.js +136 -0
  98. package/src/types/connection.js +17 -0
  99. package/src/types/execution.js +107 -0
  100. package/src/types/index.js +1 -0
  101. package/src/types/migration.js +67 -0
  102. package/src/types/risk.js +32 -0
  103. package/src/types/schema.d.ts +1004 -0
  104. package/src/types/schema.js +561 -0
@@ -0,0 +1,1512 @@
1
+ /**
2
+ * Generate ALTER DDL for property changes.
3
+ */
4
+
5
+ export function generateAlterSql(change) {
6
+ const objectKey = change.objectKey;
7
+ const property = change.property;
8
+ const currentValue = change.currentValue;
9
+ const desiredValue = change.desiredValue;
10
+ const before = change.before;
11
+ const after = change.after;
12
+ const changeType = change.changeType;
13
+
14
+ if (property === 'comment') {
15
+ return generateGenericCommentSql(change);
16
+ }
17
+ if (property === 'privileges') {
18
+ if (change.objectType === 'table' && change.metadata?.grantsToMake) {
19
+ // let it fall through to generateAlterTableSql
20
+ } else {
21
+ return generateGenericPrivilegesSql(change);
22
+ }
23
+ }
24
+
25
+ switch (change.objectType) {
26
+ case 'table':
27
+ return generateAlterTableSql(change);
28
+
29
+ case 'column':
30
+ return generateAlterColumnSql(change);
31
+
32
+ case 'constraint':
33
+ return generateAlterConstraintSql(change);
34
+
35
+ case 'index':
36
+ return generateAlterIndexSql(change);
37
+
38
+ case 'function':
39
+ case 'procedure':
40
+ return generateAlterFunctionSql(change);
41
+
42
+ case 'sequence':
43
+ return generateAlterSequenceSql(change);
44
+
45
+ case 'view':
46
+ return generateAlterViewSql(change);
47
+
48
+ case 'trigger':
49
+ return generateAlterTriggerSql(change);
50
+
51
+ case 'policy':
52
+ return generateAlterPolicySql(change);
53
+
54
+ case 'eventTrigger':
55
+ return generateAlterEventTriggerSql(change);
56
+
57
+ case 'type':
58
+ case 'domain':
59
+ return generateAlterTypeSql(change);
60
+
61
+ case 'materializedView':
62
+ return generateAlterMaterializedViewSql(change);
63
+
64
+ case 'defaultPrivileges':
65
+ return generateAlterDefaultPrivilegesSql(change);
66
+
67
+ case 'conversion':
68
+ return generateAlterConversionSql(change);
69
+
70
+ case 'foreignServer':
71
+ return generateAlterForeignServerSql(change);
72
+
73
+ case 'foreignDataWrapper':
74
+ return generateAlterForeignDataWrapperSql(change);
75
+
76
+ case 'userMapping':
77
+ return generateAlterUserMappingSql(change);
78
+
79
+ case 'foreignTable':
80
+ return generateAlterForeignTableSql(change);
81
+
82
+ case 'textSearchConfig':
83
+ return generateAlterTextSearchConfigSql(change);
84
+
85
+ case 'textSearchDict':
86
+ return generateAlterTextSearchDictSql(change);
87
+
88
+ case 'operator':
89
+ return generateAlterOperatorSql(change);
90
+
91
+ case 'operatorClass':
92
+ return generateAlterOperatorClassSql(change);
93
+
94
+ case 'operatorFamily':
95
+ return generateAlterOperatorFamilySql(change);
96
+
97
+ case 'publication':
98
+ return generateAlterPublicationSql(change);
99
+
100
+ case 'subscription':
101
+ return generateAlterSubscriptionSql(change);
102
+
103
+ case 'statistics':
104
+ return generateAlterStatisticsSql(change);
105
+
106
+ case 'collation':
107
+ return generateAlterCollationSql(change);
108
+
109
+ case 'language':
110
+ return generateAlterLanguageSql(change);
111
+
112
+ case 'accessMethod':
113
+ return generateAlterAccessMethodSql(change);
114
+
115
+ case 'extension':
116
+ return generateAlterExtensionSql(change);
117
+
118
+ default:
119
+ return generateGenericAlterSql(change);
120
+ }
121
+ }
122
+
123
+ function generateAlterTableSql(change) {
124
+ const property = change.property;
125
+ const value = change.desiredValue;
126
+ const tableKey = change.objectKey;
127
+
128
+ switch (property) {
129
+ case 'tablespace':
130
+ return `ALTER TABLE ${tableKey} SET TABLESPACE ${ident(value)};`;
131
+ case 'unlogged':
132
+ return value ? `ALTER TABLE ${tableKey} SET UNLOGGED;` : `ALTER TABLE ${tableKey} SET LOGGED;`;
133
+ case 'rlsEnabled':
134
+ return value ? `ALTER TABLE ${tableKey} ENABLE ROW LEVEL SECURITY;` : `ALTER TABLE ${tableKey} DISABLE ROW LEVEL SECURITY;`;
135
+ case 'rlsForced':
136
+ return value ? `ALTER TABLE ${tableKey} FORCE ROW LEVEL SECURITY;` : `ALTER TABLE ${tableKey} NO FORCE ROW LEVEL SECURITY;`;
137
+
138
+ case 'replicaIdentity':
139
+ if (value === 'default') return `ALTER TABLE ${tableKey} REPLICA IDENTITY DEFAULT;`;
140
+ if (value === 'full') return `ALTER TABLE ${tableKey} REPLICA IDENTITY FULL;`;
141
+ if (value === 'nothing') return `ALTER TABLE ${tableKey} REPLICA IDENTITY NOTHING;`;
142
+ if (typeof value === 'string' && value.startsWith('index:')) {
143
+ const idxName = value.split(':')[1];
144
+ return `ALTER TABLE ${tableKey} REPLICA IDENTITY USING INDEX ${ident(idxName)};`;
145
+ }
146
+ return `ALTER TABLE ${tableKey} REPLICA IDENTITY ${value.toUpperCase()};`;
147
+
148
+ case 'hasOids':
149
+ return value ? `ALTER TABLE ${tableKey} SET WITH OIDS;` : `ALTER TABLE ${tableKey} SET WITHOUT OIDS;`;
150
+
151
+ case 'storageParameters': {
152
+ const currentParams = change.currentValue || {};
153
+ const desiredParams = change.desiredValue || {};
154
+ const toSet = [];
155
+ const toReset = [];
156
+
157
+ for (const [k, v] of Object.entries(desiredParams)) {
158
+ if (currentParams[k] !== v) {
159
+ toSet.push(`${k}=${v}`);
160
+ }
161
+ }
162
+ for (const k of Object.keys(currentParams)) {
163
+ if (desiredParams[k] === undefined) {
164
+ toReset.push(k);
165
+ }
166
+ }
167
+
168
+ const stmts = [];
169
+ if (toSet.length > 0) {
170
+ stmts.push(`ALTER TABLE ${tableKey} SET (${toSet.join(', ')});`);
171
+ }
172
+ if (toReset.length > 0) {
173
+ stmts.push(`ALTER TABLE ${tableKey} RESET (${toReset.join(', ')});`);
174
+ }
175
+ return stmts.join('\n');
176
+ }
177
+
178
+ case 'inheritsFrom': {
179
+ const currentParents = change.currentValue || [];
180
+ const desiredParents = change.desiredValue || [];
181
+ const inheritStmts = [];
182
+
183
+ for (const parent of desiredParents) {
184
+ if (!currentParents.includes(parent)) {
185
+ inheritStmts.push(`ALTER TABLE ${tableKey} INHERIT ${parent};`);
186
+ }
187
+ }
188
+ for (const parent of currentParents) {
189
+ if (!desiredParents.includes(parent)) {
190
+ inheritStmts.push(`ALTER TABLE ${tableKey} NO INHERIT ${parent};`);
191
+ }
192
+ }
193
+ return inheritStmts.join('\n');
194
+ }
195
+
196
+ case 'partitionParent': {
197
+ const cur = change.currentValue || {};
198
+ const des = change.desiredValue || {};
199
+ const stmts = [];
200
+
201
+ if (cur.parent) {
202
+ const curParentParts = cur.parent.split('.');
203
+ const curParentRef = curParentParts.map(ident).join('.');
204
+ stmts.push(`ALTER TABLE ${curParentRef} DETACH PARTITION ${tableKey};`);
205
+ }
206
+ if (des.parent && des.bound) {
207
+ const desParentParts = des.parent.split('.');
208
+ const desParentRef = desParentParts.map(ident).join('.');
209
+ stmts.push(`ALTER TABLE ${desParentRef} ATTACH PARTITION ${tableKey} ${des.bound};`);
210
+ }
211
+ return stmts.join('\n');
212
+ }
213
+
214
+ case 'comment':
215
+ if (value === null || value === undefined || value === '') {
216
+ return `COMMENT ON TABLE ${tableKey} IS NULL;`;
217
+ }
218
+ return `COMMENT ON TABLE ${tableKey} IS '${value.replace(/'/g, "''")}';`;
219
+
220
+ case 'privileges': {
221
+ const { grantsToMake = [], revokesToMake = [] } = change.metadata || {};
222
+ const stmts = [];
223
+
224
+ for (const r of revokesToMake) {
225
+ stmts.push(`REVOKE ${r.privilege} ON TABLE ${tableKey} FROM ${ident(r.grantee)};`);
226
+ }
227
+ for (const g of grantsToMake) {
228
+ let sql = `GRANT ${g.privilege} ON TABLE ${tableKey} TO ${ident(g.grantee)}`;
229
+ if (g.isGrantable) sql += ' WITH GRANT OPTION';
230
+ stmts.push(sql + ';');
231
+ }
232
+ return stmts.join('\n');
233
+ }
234
+
235
+ case 'parallelWorkers':
236
+ return `ALTER TABLE ${tableKey} SET (parallel_workers=${value});`;
237
+ case 'owner':
238
+ return `ALTER TABLE ${tableKey} OWNER TO ${ident(value)};`;
239
+ case 'accessMethod': {
240
+ const method = value !== undefined && value !== null ? value : change.after?.accessMethod;
241
+ return `ALTER TABLE ${tableKey} SET ACCESS METHOD ${ident(method)};`;
242
+ }
243
+ default:
244
+ return `-- Unsupported table property: ${property}`;
245
+ }
246
+ }
247
+
248
+ function generateAlterColumnSql(change) {
249
+ const parts = change.objectKey.split('.');
250
+ const col = parts.pop();
251
+ const table = parts.join('.');
252
+ const property = change.property;
253
+
254
+ switch (property) {
255
+ case 'dataType':
256
+ const using = change.usingExpression || `${col}::${change.desiredValue}`;
257
+ return `ALTER TABLE ${table} ALTER COLUMN ${ident(col)} TYPE ${change.desiredValue} USING ${using};`;
258
+
259
+ case 'isNullable':
260
+ if (change.desiredValue) {
261
+ return `ALTER TABLE ${table} ALTER COLUMN ${ident(col)} DROP NOT NULL;`;
262
+ }
263
+ return `ALTER TABLE ${table} ALTER COLUMN ${ident(col)} SET NOT NULL;`;
264
+
265
+ case 'defaultValue':
266
+ if (change.desiredValue === null || change.desiredValue === undefined) {
267
+ return `ALTER TABLE ${table} ALTER COLUMN ${ident(col)} DROP DEFAULT;`;
268
+ }
269
+ return `ALTER TABLE ${table} ALTER COLUMN ${ident(col)} SET DEFAULT ${change.desiredValue};`;
270
+
271
+ case 'isIdentity':
272
+ if (change.desiredValue) {
273
+ const after = change.after || {};
274
+ const opts = after.identityOptions || {};
275
+ const rawMode = after.identityMode || after.identityType || (after.isAlwaysIdentity ? 'ALWAYS' : 'BY DEFAULT');
276
+ const mode = rawMode === 'BY_DEFAULT' ? 'BY DEFAULT' : rawMode;
277
+ let sql = `ALTER TABLE ${table} ALTER COLUMN ${ident(col)} ADD GENERATED ${mode} AS IDENTITY`;
278
+ const seqOpts = [];
279
+ const start = opts.start ?? after.identityStart;
280
+ const increment = opts.increment ?? after.identityIncrement;
281
+ const min = opts.min ?? opts.minimum ?? after.identityMinimum;
282
+ const max = opts.max ?? opts.maximum ?? after.identityMaximum;
283
+ const cache = opts.cache ?? after.identityCache;
284
+ const cycle = opts.cycle ?? after.identityCycle;
285
+ if (increment != null) seqOpts.push(`INCREMENT ${increment}`);
286
+ if (start != null) seqOpts.push(`START ${start}`);
287
+ if (min != null) seqOpts.push(`MINVALUE ${min}`);
288
+ if (max != null) seqOpts.push(`MAXVALUE ${max}`);
289
+ if (cache != null) seqOpts.push(`CACHE ${cache}`);
290
+ if (cycle) seqOpts.push('CYCLE');
291
+ if (seqOpts.length > 0) sql += ` (${seqOpts.join(', ')})`;
292
+ return sql + ';';
293
+ }
294
+ return `ALTER TABLE ${table} ALTER COLUMN ${ident(col)} DROP IDENTITY;`;
295
+
296
+ case 'identityOptions':
297
+ return `ALTER TABLE ${table} ALTER COLUMN ${ident(col)} SET ${change.desiredValue || ''};`;
298
+
299
+ // Identity sequence options — PostgreSQL: ALTER COLUMN ... SET sequence_option
300
+ case 'identityStart':
301
+ return `ALTER TABLE ${table} ALTER COLUMN ${ident(col)} SET START ${change.desiredValue};`;
302
+ case 'identityIncrement':
303
+ return `ALTER TABLE ${table} ALTER COLUMN ${ident(col)} SET INCREMENT ${change.desiredValue};`;
304
+ case 'identityMinimum':
305
+ return `ALTER TABLE ${table} ALTER COLUMN ${ident(col)} SET MINVALUE ${change.desiredValue};`;
306
+ case 'identityMaximum':
307
+ return `ALTER TABLE ${table} ALTER COLUMN ${ident(col)} SET MAXVALUE ${change.desiredValue};`;
308
+ case 'identityCache':
309
+ return `ALTER TABLE ${table} ALTER COLUMN ${ident(col)} SET CACHE ${change.desiredValue};`;
310
+ case 'identityCycle':
311
+ return `ALTER TABLE ${table} ALTER COLUMN ${ident(col)} SET ${change.desiredValue ? 'CYCLE' : 'NO CYCLE'};`;
312
+ case 'identityMode':
313
+ case 'identityType':
314
+ return `ALTER TABLE ${table} ALTER COLUMN ${ident(col)} SET GENERATED ${change.desiredValue === 'BY_DEFAULT' || change.desiredValue === 'BY DEFAULT' ? 'BY DEFAULT' : 'ALWAYS'};`;
315
+
316
+ case 'isGenerated': {
317
+ const genExpr = change.after?.generatedExpression ?? change.generatedExpression;
318
+ if (change.desiredValue && genExpr) {
319
+ return `ALTER TABLE ${table} ALTER COLUMN ${ident(col)} SET GENERATED ALWAYS AS (${genExpr}) STORED;`;
320
+ }
321
+ return `ALTER TABLE ${table} ALTER COLUMN ${ident(col)} DROP EXPRESSION;`;
322
+ }
323
+
324
+ case 'generatedExpression':
325
+ if (change.desiredValue) {
326
+ return `ALTER TABLE ${table} ALTER COLUMN ${ident(col)} SET GENERATED ALWAYS AS (${change.desiredValue}) STORED;`;
327
+ }
328
+ return `ALTER TABLE ${table} ALTER COLUMN ${ident(col)} DROP EXPRESSION;`;
329
+
330
+ case 'comment':
331
+ if (change.desiredValue === null || change.desiredValue === '') {
332
+ return `COMMENT ON COLUMN ${table}.${ident(col)} IS NULL;`;
333
+ }
334
+ return `COMMENT ON COLUMN ${table}.${ident(col)} IS '${escapeString(change.desiredValue)}';`;
335
+
336
+ case 'storage':
337
+ if (change.desiredValue === null || change.desiredValue === undefined) {
338
+ return `ALTER TABLE ${table} ALTER COLUMN ${ident(col)} RESET STORAGE;`;
339
+ }
340
+ return `ALTER TABLE ${table} ALTER COLUMN ${ident(col)} SET STORAGE ${change.desiredValue.toUpperCase()};`;
341
+
342
+ case 'statistics':
343
+ if (change.desiredValue === null || change.desiredValue === undefined || change.desiredValue === -1) {
344
+ return `ALTER TABLE ${table} ALTER COLUMN ${ident(col)} RESET STATISTICS;`;
345
+ }
346
+ return `ALTER TABLE ${table} ALTER COLUMN ${ident(col)} SET STATISTICS ${change.desiredValue};`;
347
+
348
+ case 'compression':
349
+ if (change.desiredValue === null || change.desiredValue === undefined) {
350
+ return `ALTER TABLE ${table} ALTER COLUMN ${ident(col)} RESET COMPRESSION;`;
351
+ }
352
+ return `ALTER TABLE ${table} ALTER COLUMN ${ident(col)} SET COMPRESSION ${change.desiredValue.toUpperCase()};`;
353
+
354
+ case 'foreignOptions':
355
+ return `ALTER TABLE ${table} ALTER COLUMN ${ident(col)} OPTIONS (${Object.entries(change.desiredValue || {}).map(([k, v]) => `${k} '${escapeString(v)}'`).join(', ')});`;
356
+
357
+ case 'collation': {
358
+ const colInfo = change.after || change.desired || {};
359
+ const dataType = colInfo.dataType || change.before?.dataType || 'text';
360
+ return `ALTER TABLE ${table} ALTER COLUMN ${ident(col)} TYPE ${dataType} COLLATE ${change.desiredValue};`;
361
+ }
362
+
363
+ default:
364
+ return `-- Unsupported column property: ${property}`;
365
+ }
366
+ }
367
+
368
+ function generateAlterConstraintSql(change) {
369
+ const con = change.after || {};
370
+ const tableKey = change.objectKey.split('.').slice(0, -1).join('.') || con._tableKey;
371
+ const conName = change.name || change.objectKey.split('.').pop();
372
+
373
+ switch (change.property) {
374
+ case 'isValidated':
375
+ if (change.desiredValue === true) {
376
+ return `ALTER TABLE ${tableKey} VALIDATE CONSTRAINT ${ident(conName)};`;
377
+ }
378
+ break;
379
+
380
+ case 'deferrable':
381
+ if (change.desiredValue) {
382
+ return `ALTER TABLE ${tableKey} ALTER CONSTRAINT ${ident(conName)} DEFERRABLE;`;
383
+ }
384
+ return `ALTER TABLE ${tableKey} ALTER CONSTRAINT ${ident(conName)} NOT DEFERRABLE;`;
385
+
386
+ case 'initiallyDeferred':
387
+ case 'deferred':
388
+ return `ALTER TABLE ${tableKey} ALTER CONSTRAINT ${ident(conName)} INITIALLY ${change.desiredValue ? 'DEFERRED' : 'IMMEDIATE'};`;
389
+
390
+ case 'indexTablespace':
391
+ const idxName = con.indexName || `${conName}_idx`;
392
+ return `ALTER INDEX ${idxName} SET TABLESPACE ${ident(change.desiredValue)};`;
393
+
394
+ default:
395
+ // Exclusion constraint specific
396
+ if (con.constraintType === 'EXCLUSION' || con.type === 'EXCLUSION') {
397
+ return `-- Exclusion constraint modification requires recreation: ${change.property}`;
398
+ }
399
+ return `-- Constraint modification requires recreation: ${change.property}`;
400
+ }
401
+ return '';
402
+ }
403
+
404
+ function generateAlterIndexSql(change) {
405
+ const idxKey = change.objectKey;
406
+ const property = change.property;
407
+
408
+ switch (property) {
409
+ case 'tablespace':
410
+ return `ALTER INDEX ${idxKey} SET TABLESPACE ${ident(change.desiredValue)};`;
411
+ case 'fillfactor':
412
+ return `ALTER INDEX ${idxKey} SET (fillfactor=${change.desiredValue});`;
413
+ case 'owner':
414
+ return `ALTER INDEX ${idxKey} OWNER TO ${ident(change.desiredValue)};`;
415
+ case 'comment':
416
+ if (change.desiredValue === null || change.desiredValue === undefined || change.desiredValue === '') {
417
+ return `COMMENT ON INDEX ${idxKey} IS NULL;`;
418
+ }
419
+ return `COMMENT ON INDEX ${idxKey} IS '${escapeString(change.desiredValue)}';`;
420
+ case 'reindex':
421
+ return `REINDEX INDEX ${idxKey};`;
422
+ default:
423
+ return `-- Index modification requires recreation for: ${property}`;
424
+ }
425
+ }
426
+
427
+ function generateAlterFunctionSql(change) {
428
+ const fn = change.after || change.before || {};
429
+ const name = change.objectKey;
430
+ const argTypesList = fn.argumentTypes || fn.arguments || change.before?.argumentTypes || change.before?.arguments || [];
431
+ const argsStr = Array.isArray(argTypesList) ? argTypesList.join(', ') : '';
432
+
433
+ if (change.changeType === 'ALTER' && change.property === 'source') {
434
+ return `CREATE OR REPLACE ${change.objectType === 'procedure' ? 'PROCEDURE' : 'FUNCTION'} ${name}(${argsStr})${fn.returnType ? ` RETURNS ${fn.returnType}` : ''} LANGUAGE ${fn.language || 'sql'} AS $$${fn.source || ''}$$;`;
435
+ }
436
+
437
+ let sql = `ALTER ${change.objectType === 'procedure' ? 'PROCEDURE' : 'FUNCTION'} ${name}(${argsStr})`;
438
+
439
+ switch (change.property) {
440
+ case 'volatility':
441
+ return sql + ' ' + change.desiredValue + ';';
442
+ case 'isNullCall':
443
+ return sql + (change.desiredValue ? ' CALLED ON NULL INPUT;' : ' RETURNS NULL ON NULL INPUT;');
444
+ case 'securityType':
445
+ return sql + (change.desiredValue === 'DEFINER' ? ' SECURITY DEFINER;' : ' SECURITY INVOKER;');
446
+ case 'cost':
447
+ return sql + ` SET cost=${change.desiredValue};`;
448
+ case 'parallelSafety':
449
+ return sql + ` SET parallel=${change.desiredValue};`;
450
+ case 'owner':
451
+ return sql + ` OWNER TO ${ident(change.desiredValue)};`;
452
+ case 'schema':
453
+ return sql + ` SET SCHEMA ${ident(change.desiredValue)};`;
454
+ case 'comment':
455
+ if (change.desiredValue === null || change.desiredValue === '') {
456
+ return `COMMENT ON ${change.objectType === 'procedure' ? 'PROCEDURE' : 'FUNCTION'} ${name}(${argsStr}) IS NULL;`;
457
+ }
458
+ return `COMMENT ON ${change.objectType === 'procedure' ? 'PROCEDURE' : 'FUNCTION'} ${name}(${argsStr}) IS '${escapeString(change.desiredValue)}';`;
459
+ default:
460
+ return `-- Unsupported function property: ${change.property}`;
461
+ }
462
+ }
463
+
464
+ function generateAlterSequenceSql(change) {
465
+ const seqKey = change.objectKey;
466
+ const property = change.property;
467
+
468
+ switch (property) {
469
+ case 'increment':
470
+ return `ALTER SEQUENCE ${seqKey} INCREMENT ${change.desiredValue};`;
471
+ case 'minimumValue':
472
+ case 'minValue':
473
+ return `ALTER SEQUENCE ${seqKey} MINVALUE ${change.desiredValue};`;
474
+ case 'maximumValue':
475
+ case 'maxValue':
476
+ return `ALTER SEQUENCE ${seqKey} MAXVALUE ${change.desiredValue};`;
477
+ case 'cacheSize':
478
+ case 'cache':
479
+ return `ALTER SEQUENCE ${seqKey} CACHE ${change.desiredValue};`;
480
+ case 'isCycled':
481
+ case 'cycle':
482
+ return change.desiredValue ? `ALTER SEQUENCE ${seqKey} CYCLE;` : `ALTER SEQUENCE ${seqKey} NO CYCLE;`;
483
+ case 'startValue':
484
+ case 'currentValue':
485
+ return `ALTER SEQUENCE ${seqKey} RESTART WITH ${change.desiredValue};`;
486
+ case 'schema':
487
+ return `ALTER SEQUENCE ${seqKey} SET SCHEMA ${ident(change.desiredValue)};`;
488
+ case 'owner':
489
+ return `ALTER SEQUENCE ${seqKey} OWNER TO ${ident(change.desiredValue)};`;
490
+ case 'comment':
491
+ if (change.desiredValue === null || change.desiredValue === undefined || change.desiredValue === '') {
492
+ return `COMMENT ON SEQUENCE ${seqKey} IS NULL;`;
493
+ }
494
+ return `COMMENT ON SEQUENCE ${seqKey} IS '${escapeString(change.desiredValue)}';`;
495
+ case 'ownedBy': {
496
+ if (change.desiredValue === null || change.desiredValue === undefined || change.desiredValue === 'NONE') {
497
+ return `ALTER SEQUENCE ${seqKey} OWNED BY NONE;`;
498
+ }
499
+ const ownedParts = change.desiredValue.split('.');
500
+ const formattedOwnedBy = ownedParts.map(ident).join('.');
501
+ return `ALTER SEQUENCE ${seqKey} OWNED BY ${formattedOwnedBy};`;
502
+ }
503
+ default:
504
+ return `-- Unsupported sequence property: ${change.property}`;
505
+ }
506
+ }
507
+
508
+ function generateAlterViewSql(change) {
509
+ const viewKey = change.objectKey;
510
+
511
+ switch (change.property) {
512
+ case 'definition':
513
+ return `CREATE OR REPLACE VIEW ${viewKey} AS ${change.desiredValue};`;
514
+ case 'checkOption':
515
+ if (change.desiredValue && change.desiredValue !== 'NONE') {
516
+ return `ALTER VIEW ${viewKey} SET (check_option=${change.desiredValue.toLowerCase()});`;
517
+ }
518
+ return `ALTER VIEW ${viewKey} RESET (check_option);`;
519
+ case 'owner':
520
+ return `ALTER VIEW ${viewKey} OWNER TO ${ident(change.desiredValue)};`;
521
+ case 'schema':
522
+ return `ALTER VIEW ${viewKey} SET SCHEMA ${ident(change.desiredValue)};`;
523
+ case 'securityBarrier':
524
+ return `ALTER VIEW ${viewKey} ${change.desiredValue ? 'SET (security_barrier=true)' : 'SET (security_barrier=false)'};`;
525
+ case 'securityInvoker':
526
+ return `ALTER VIEW ${viewKey} ${change.desiredValue ? 'SET (security_invoker=true)' : 'SET (security_invoker=false)'};`;
527
+ default:
528
+ return `-- Unsupported view property: ${change.property}`;
529
+ }
530
+ }
531
+
532
+ function generateAlterTriggerSql(change) {
533
+ if (change.property === 'enabled') {
534
+ const trigKey = change.objectKey;
535
+ const schema = change.schema;
536
+ const name = change.name;
537
+ const table = change.after?.tableName || change.objectKey.split('.').slice(0, -1).join('.');
538
+
539
+ switch (change.desiredValue) {
540
+ case 'ENABLED':
541
+ case true:
542
+ return `ALTER TABLE ${table} ENABLE TRIGGER ${ident(name)};`;
543
+ case 'DISABLED':
544
+ case false:
545
+ return `ALTER TABLE ${table} DISABLE TRIGGER ${ident(name)};`;
546
+ case 'REPLICA':
547
+ return `ALTER TABLE ${table} ENABLE REPLICA TRIGGER ${ident(name)};`;
548
+ case 'ALWAYS':
549
+ return `ALTER TABLE ${table} ENABLE ALWAYS TRIGGER ${ident(name)};`;
550
+ }
551
+ }
552
+
553
+ return `-- Trigger modification requires recreation for: ${change.property}`;
554
+ }
555
+
556
+ function generateAlterPolicySql(change) {
557
+ const policyKey = change.objectKey;
558
+ const parts = policyKey.split('.');
559
+ const policyName = parts.pop();
560
+ const tableName = parts.join('.');
561
+ const property = change.property;
562
+
563
+ switch (property) {
564
+ case 'roles':
565
+ const roles = (change.desiredValue || []).map(r => r === 'PUBLIC' ? 'PUBLIC' : ident(r)).join(', ');
566
+ return `ALTER POLICY ${ident(policyName)} ON ${tableName} TO ${roles};`;
567
+ case 'using':
568
+ return `ALTER POLICY ${ident(policyName)} ON ${tableName} USING (${change.desiredValue || 'true'});`;
569
+ case 'withCheck':
570
+ return `ALTER POLICY ${ident(policyName)} ON ${tableName} WITH CHECK (${change.desiredValue});`;
571
+ default:
572
+ return `-- Policy modification requires recreation for: ${change.property}`;
573
+ }
574
+ }
575
+
576
+ function generateAlterTypeSql(change) {
577
+ const typeKey = change.objectKey;
578
+ const property = change.property;
579
+ const typeKind = change.before?.kind || change.after?.kind;
580
+
581
+ if (property === 'enumValues' || property === 'labels') {
582
+ const beforeList = change.before?.labels || change.before?.enumValues || [];
583
+ const afterList = change.after?.labels || change.after?.enumValues || [];
584
+ const stmts = [];
585
+
586
+ if (beforeList.length > 0 && afterList.length > 0) {
587
+ const removedLabels = beforeList.filter(l => !afterList.includes(l));
588
+ const addedLabels = afterList.filter(l => !beforeList.includes(l));
589
+
590
+ if (removedLabels.length === 1 && addedLabels.length === 1) {
591
+ stmts.push(`ALTER TYPE ${typeKey} RENAME VALUE '${removedLabels[0]}' TO '${addedLabels[0]}';`);
592
+ } else {
593
+ for (const val of addedLabels) {
594
+ const newIdx = afterList.indexOf(val);
595
+ let posClause = '';
596
+ if (newIdx > 0) {
597
+ const prevLabel = afterList[newIdx - 1];
598
+ if (beforeList.includes(prevLabel)) {
599
+ posClause = ` AFTER '${prevLabel}'`;
600
+ }
601
+ } else if (newIdx === 0 && afterList.length > 1) {
602
+ const nextLabel = afterList[1];
603
+ if (beforeList.includes(nextLabel)) {
604
+ posClause = ` BEFORE '${nextLabel}'`;
605
+ }
606
+ }
607
+ stmts.push(`ALTER TYPE ${typeKey} ADD VALUE '${val}'${posClause};`);
608
+ }
609
+ for (const val of removedLabels) {
610
+ stmts.push(`-- WARNING: PostgreSQL does not support removing enum values.\n-- The enum value '${val}' cannot be dropped.\n-- Consider recreating the enum type if removal is required.`);
611
+ }
612
+ }
613
+ } else {
614
+ const added = change.desiredValue?.added || [];
615
+ const removed = change.desiredValue?.removed || [];
616
+ if (added.length > 0) {
617
+ for (const val of added) {
618
+ stmts.push(`ALTER TYPE ${typeKey} ADD VALUE '${val}';`);
619
+ }
620
+ }
621
+ if (removed.length > 0) {
622
+ for (const val of removed) {
623
+ stmts.push(`-- WARNING: PostgreSQL does not support removing enum values.\n-- The enum value '${val}' cannot be dropped.\n-- Consider recreating the enum type if removal is required.`);
624
+ }
625
+ }
626
+ }
627
+ return stmts.join('\n');
628
+ }
629
+
630
+ if (typeKind === 'domain' || change.objectType === 'domain') {
631
+ return generateAlterDomainSql(change, typeKey);
632
+ }
633
+
634
+ if (typeKind === 'composite' || change.objectType === 'composite') {
635
+ return generateAlterCompositeSql(change, typeKey);
636
+ }
637
+
638
+ if (typeKind === 'range' || change.objectType === 'range') {
639
+ return generateAlterRangeSql(change, typeKey);
640
+ }
641
+
642
+ switch (property) {
643
+ case 'owner':
644
+ return `ALTER TYPE ${typeKey} OWNER TO ${ident(change.desiredValue)};`;
645
+ case 'schema':
646
+ return `ALTER TYPE ${typeKey} SET SCHEMA ${ident(change.desiredValue)};`;
647
+ case 'comment':
648
+ if (change.desiredValue === null || change.desiredValue === '') {
649
+ return `COMMENT ON TYPE ${typeKey} IS NULL;`;
650
+ }
651
+ return `COMMENT ON TYPE ${typeKey} IS '${escapeString(change.desiredValue)}';`;
652
+ default:
653
+ return `-- Unsupported type property: ${change.property}`;
654
+ }
655
+ }
656
+
657
+ function generateAlterDomainSql(change, typeKey) {
658
+ const property = change.property;
659
+
660
+ switch (property) {
661
+ case 'notNull':
662
+ if (change.desiredValue === true) {
663
+ return `ALTER DOMAIN ${typeKey} SET NOT NULL;`;
664
+ }
665
+ return `ALTER DOMAIN ${typeKey} DROP NOT NULL;`;
666
+ case 'default':
667
+ if (change.desiredValue === null || change.desiredValue === undefined) {
668
+ return `ALTER DOMAIN ${typeKey} DROP DEFAULT;`;
669
+ }
670
+ return `ALTER DOMAIN ${typeKey} SET DEFAULT ${change.desiredValue};`;
671
+ case 'check':
672
+ if (change.desiredValue === null || change.desiredValue === undefined) {
673
+ const constraintName = change.before?.checkName || `${typeKey.replace(/\./g, '_')}_check`;
674
+ return `ALTER DOMAIN ${typeKey} DROP CONSTRAINT ${ident(constraintName)};`;
675
+ }
676
+ const checkName = change.after?.checkName || `${typeKey.replace(/\./g, '_')}_check`;
677
+ return `ALTER DOMAIN ${typeKey} ADD CONSTRAINT ${ident(checkName)} CHECK (${change.desiredValue});`;
678
+ case 'owner':
679
+ return `ALTER DOMAIN ${typeKey} OWNER TO ${ident(change.desiredValue)};`;
680
+ case 'schema':
681
+ return `ALTER DOMAIN ${typeKey} SET SCHEMA ${ident(change.desiredValue)};`;
682
+ default:
683
+ return `-- Unsupported domain property: ${property}`;
684
+ }
685
+ }
686
+
687
+ function generateAlterCompositeSql(change, typeKey) {
688
+ const property = change.property;
689
+
690
+ switch (property) {
691
+ case 'addAttribute':
692
+ const attr = change.desiredValue;
693
+ return `ALTER TYPE ${typeKey} ADD ATTRIBUTE ${ident(attr.name)} ${attr.dataType};`;
694
+ case 'dropAttribute':
695
+ return `ALTER TYPE ${typeKey} DROP ATTRIBUTE ${ident(change.desiredValue)};`;
696
+ case 'alterAttribute':
697
+ const alterAttr = change.desiredValue;
698
+ return `ALTER TYPE ${typeKey} ALTER ATTRIBUTE ${ident(alterAttr.name)} SET DATA TYPE ${alterAttr.dataType};`;
699
+ case 'renameAttribute':
700
+ return `ALTER TYPE ${typeKey} RENAME ATTRIBUTE ${ident(change.currentValue)} TO ${ident(change.desiredValue)};`;
701
+ case 'owner':
702
+ return `ALTER TYPE ${typeKey} OWNER TO ${ident(change.desiredValue)};`;
703
+ case 'schema':
704
+ return `ALTER TYPE ${typeKey} SET SCHEMA ${ident(change.desiredValue)};`;
705
+ default:
706
+ return `-- Unsupported composite type property: ${property}`;
707
+ }
708
+ }
709
+
710
+ function generateAlterRangeSql(change, typeKey) {
711
+ const property = change.property;
712
+
713
+ switch (property) {
714
+ case 'canonical':
715
+ return `ALTER TYPE ${typeKey} SET (${change.desiredValue ? `canonical = ${ident(change.desiredValue)}` : ''});`;
716
+ case 'subtypeDiff':
717
+ return `ALTER TYPE ${typeKey} SET (subtype_diff = ${ident(change.desiredValue)});`;
718
+ case 'owner':
719
+ return `ALTER TYPE ${typeKey} OWNER TO ${ident(change.desiredValue)};`;
720
+ case 'schema':
721
+ return `ALTER TYPE ${typeKey} SET SCHEMA ${ident(change.desiredValue)};`;
722
+ default:
723
+ return `-- Unsupported range type property: ${property}`;
724
+ }
725
+ }
726
+
727
+ function generateGenericAlterSql(change) {
728
+ const objKey = change.objectKey;
729
+ const property = change.property;
730
+ const desiredValue = change.desiredValue;
731
+
732
+ switch (property) {
733
+ case 'owner':
734
+ return `ALTER ${change.objectType.toUpperCase()} ${objKey} OWNER TO ${ident(desiredValue)};`;
735
+ case 'schema':
736
+ return `ALTER ${change.objectType.toUpperCase()} ${objKey} SET SCHEMA ${ident(desiredValue)};`;
737
+ case 'comment':
738
+ if (desiredValue === null || desiredValue === '') {
739
+ return `COMMENT ON ${change.objectType.toUpperCase()} ${objKey} IS NULL;`;
740
+ }
741
+ return `COMMENT ON ${change.objectType.toUpperCase()} ${objKey} IS '${escapeString(desiredValue)}';`;
742
+ default:
743
+ return `-- Unsupported ${change.objectType} property: ${property}`;
744
+ }
745
+ }
746
+
747
+ function ident(name) {
748
+ if (!name) return '';
749
+ if (typeof name !== 'string') name = String(name);
750
+ if (name.includes('"') || name.includes(' ')) {
751
+ return `"${name.replace(/"/g, '""')}"`;
752
+ }
753
+ return `"${name}"`;
754
+ }
755
+
756
+ function escapeString(str) {
757
+ if (typeof str !== 'string') str = String(str);
758
+ return str.replace(/'/g, "''");
759
+ }
760
+
761
+ function generateAlterDefaultPrivilegesSql(change) {
762
+ const beforeDp = change.before || {};
763
+ const afterDp = change.after || {};
764
+
765
+ const schema = afterDp.schema || beforeDp.schema || change.schema;
766
+ const role = afterDp.role || afterDp.forRole || beforeDp.role || beforeDp.forRole;
767
+ const objType = (afterDp.objectType || afterDp.object_type || beforeDp.objectType || beforeDp.object_type || 'TABLES').toUpperCase();
768
+
769
+ let sqlPrefix = 'ALTER DEFAULT PRIVILEGES';
770
+ if (schema && schema !== 'public') {
771
+ sqlPrefix += ` IN SCHEMA ${ident(schema)}`;
772
+ }
773
+ if (role) {
774
+ sqlPrefix += ` FOR ROLE ${ident(role)}`;
775
+ }
776
+
777
+ const stmts = [];
778
+
779
+ const parseAclItemLocal = (item, type) => {
780
+ const parts = item.split('/');
781
+ const left = parts[0];
782
+ const eqIdx = left.indexOf('=');
783
+ let grantee = 'PUBLIC';
784
+ let privsStr = left;
785
+ if (eqIdx !== -1) {
786
+ grantee = left.substring(0, eqIdx) || 'PUBLIC';
787
+ privsStr = left.substring(eqIdx + 1);
788
+ }
789
+
790
+ const charMap = {
791
+ r: 'SELECT',
792
+ w: 'UPDATE',
793
+ a: 'INSERT',
794
+ d: 'DELETE',
795
+ D: 'TRUNCATE',
796
+ x: 'REFERENCES',
797
+ t: 'TRIGGER',
798
+ X: 'EXECUTE',
799
+ U: 'USAGE',
800
+ C: 'CREATE',
801
+ c: 'CONNECT',
802
+ T: 'TEMPORARY'
803
+ };
804
+
805
+ const privs = [];
806
+ let isGrantable = false;
807
+ for (let i = 0; i < privsStr.length; i++) {
808
+ const c = privsStr[i];
809
+ if (c === '*') {
810
+ isGrantable = true;
811
+ continue;
812
+ }
813
+ const mapped = charMap[c];
814
+ if (mapped) privs.push(mapped);
815
+ }
816
+
817
+ return { grantee, privileges: privs.join(', '), isGrantable };
818
+ };
819
+
820
+ const beforeAcl = beforeDp.acl || [];
821
+ if (beforeAcl.length > 0) {
822
+ for (const item of beforeAcl) {
823
+ const { grantee, privileges } = parseAclItemLocal(item, objType);
824
+ stmts.push(`${sqlPrefix} REVOKE ${privileges || 'ALL'} ON ${objType} FROM ${ident(grantee)};`);
825
+ }
826
+ }
827
+
828
+ const afterAcl = afterDp.acl || [];
829
+ if (afterAcl.length > 0) {
830
+ for (const item of afterAcl) {
831
+ const { grantee, privileges, isGrantable } = parseAclItemLocal(item, objType);
832
+ let stmt = `${sqlPrefix} GRANT ${privileges || 'ALL'} ON ${objType} TO ${ident(grantee)}`;
833
+ if (isGrantable) stmt += ' WITH GRANT OPTION';
834
+ stmts.push(stmt + ';');
835
+ }
836
+ }
837
+
838
+ return stmts.join('\n');
839
+ }
840
+
841
+ function generateAlterConversionSql(change) {
842
+ const objectKey = change.objectKey;
843
+ const property = change.property;
844
+
845
+ switch (property) {
846
+ case 'schema':
847
+ return `ALTER CONVERSION ${objectKey} SET SCHEMA ${ident(change.desiredValue)};`;
848
+ case 'owner':
849
+ return `ALTER CONVERSION ${objectKey} OWNER TO ${ident(change.desiredValue)};`;
850
+ default:
851
+ return `-- Unsupported conversion property: ${property}`;
852
+ }
853
+ }
854
+
855
+ function generateAlterForeignServerSql(change) {
856
+ const objectKey = change.objectKey;
857
+ const property = change.property;
858
+
859
+ switch (property) {
860
+ case 'version':
861
+ if (change.desiredValue === null) {
862
+ return `ALTER SERVER ${ident(objectKey)} VERSION 'none';`;
863
+ }
864
+ return `ALTER SERVER ${ident(objectKey)} VERSION '${change.desiredValue}';`;
865
+
866
+ case 'options':
867
+ const opts = change.desiredValue || {};
868
+ const addOpts = Object.entries(opts).map(([k, v]) => {
869
+ if (k === 'password') return `ADD ${k} '[REDACTED]'`;
870
+ return `ADD ${k} '${v}'`;
871
+ });
872
+ return `ALTER SERVER ${ident(objectKey)} OPTIONS (${addOpts.join(', ')});`;
873
+
874
+ case 'owner':
875
+ return `ALTER SERVER ${ident(objectKey)} OWNER TO ${ident(change.desiredValue)};`;
876
+
877
+ default:
878
+ return `-- Unsupported foreign server property: ${property}`;
879
+ }
880
+ }
881
+
882
+ function generateAlterForeignDataWrapperSql(change) {
883
+ const objectKey = change.objectKey;
884
+ const property = change.property;
885
+
886
+ switch (property) {
887
+ case 'handler':
888
+ if (change.desiredValue === null) {
889
+ return `ALTER FOREIGN DATA WRAPPER ${ident(objectKey)} NO HANDLER;`;
890
+ }
891
+ return `ALTER FOREIGN DATA WRAPPER ${ident(objectKey)} HANDLER ${change.desiredValue};`;
892
+
893
+ case 'validator':
894
+ if (change.desiredValue === null) {
895
+ return `ALTER FOREIGN DATA WRAPPER ${ident(objectKey)} NO VALIDATOR;`;
896
+ }
897
+ return `ALTER FOREIGN DATA WRAPPER ${ident(objectKey)} VALIDATOR ${change.desiredValue};`;
898
+
899
+ case 'options':
900
+ const opts = change.desiredValue || {};
901
+ const addOpts = Object.entries(opts).map(([k, v]) => {
902
+ if (k === 'password') return `ADD ${k} '[REDACTED]'`;
903
+ return `ADD ${k} '${v}'`;
904
+ });
905
+ return `ALTER FOREIGN DATA WRAPPER ${ident(objectKey)} OPTIONS (${addOpts.join(', ')});`;
906
+
907
+ case 'owner':
908
+ return `ALTER FOREIGN DATA WRAPPER ${ident(objectKey)} OWNER TO ${ident(change.desiredValue)};`;
909
+
910
+ default:
911
+ return `-- Unsupported FDW property: ${property}`;
912
+ }
913
+ }
914
+
915
+ function generateAlterUserMappingSql(change) {
916
+ const objectKey = change.objectKey;
917
+ const user = change.before?.user || 'PUBLIC';
918
+ const server = change.before?.server;
919
+ const property = change.property;
920
+
921
+ switch (property) {
922
+ case 'options':
923
+ const opts = change.desiredValue || {};
924
+ const addOpts = Object.entries(opts).map(([k, v]) => {
925
+ if (k === 'password') return `ADD ${k} '[REDACTED]'`;
926
+ return `ADD ${k} '${v}'`;
927
+ });
928
+ return `ALTER USER MAPPING FOR ${user === 'PUBLIC' ? 'PUBLIC' : ident(user)} SERVER ${ident(server)} OPTIONS (${addOpts.join(', ')});`;
929
+
930
+ default:
931
+ return `-- Unsupported user mapping property: ${property}`;
932
+ }
933
+ }
934
+
935
+ function generateAlterForeignTableSql(change) {
936
+ const objectKey = change.objectKey;
937
+ const property = change.property;
938
+
939
+ switch (property) {
940
+ case 'addColumn':
941
+ const addCol = change.desiredValue;
942
+ return `ALTER FOREIGN TABLE ${objectKey} ADD COLUMN ${ident(addCol.name)} ${addCol.dataType};`;
943
+
944
+ case 'dropColumn':
945
+ return `ALTER FOREIGN TABLE ${objectKey} DROP COLUMN ${ident(change.desiredValue)};`;
946
+
947
+ case 'alterColumn':
948
+ const alterCol = change.desiredValue;
949
+ return `ALTER FOREIGN TABLE ${objectKey} ALTER COLUMN ${ident(alterCol.name)} SET DATA TYPE ${alterCol.dataType};`;
950
+
951
+ case 'options':
952
+ const opts = change.desiredValue || {};
953
+ const optStr = Object.entries(opts).map(([k, v]) => `${k} '${v}'`).join(', ');
954
+ return `ALTER FOREIGN TABLE ${objectKey} OPTIONS (${optStr});`;
955
+
956
+ case 'owner':
957
+ return `ALTER FOREIGN TABLE ${objectKey} OWNER TO ${ident(change.desiredValue)};`;
958
+
959
+ default:
960
+ return `-- Unsupported foreign table property: ${property}`;
961
+ }
962
+ }
963
+
964
+ function generateAlterTextSearchConfigSql(change) {
965
+ const objectKey = change.objectKey;
966
+ const property = change.property;
967
+
968
+ switch (property) {
969
+ case 'addMapping':
970
+ const mapping = change.desiredValue;
971
+ return `ALTER TEXT SEARCH CONFIGURATION ${objectKey} ADD MAPPING FOR ${Array.isArray(mapping.tokens) ? mapping.tokens.join(', ') : mapping.tokens} WITH ${Array.isArray(mapping.dictionaries) ? mapping.dictionaries.join(', ') : mapping.dictionaries};`;
972
+
973
+ case 'dropMapping':
974
+ return `ALTER TEXT SEARCH CONFIGURATION ${objectKey} DROP MAPPING ${Array.isArray(change.desiredValue) ? change.desiredValue.join(', ') : change.desiredValue};`;
975
+
976
+ case 'alterMapping':
977
+ const alterMapping = change.desiredValue;
978
+ return `ALTER TEXT SEARCH CONFIGURATION ${objectKey} ALTER MAPPING FOR ${Array.isArray(alterMapping.tokens) ? alterMapping.tokens.join(', ') : alterMapping.tokens} WITH ${Array.isArray(alterMapping.dictionaries) ? alterMapping.dictionaries.join(', ') : alterMapping.dictionaries};`;
979
+
980
+ case 'owner':
981
+ return `ALTER TEXT SEARCH CONFIGURATION ${objectKey} OWNER TO ${ident(change.desiredValue)};`;
982
+
983
+ case 'schema':
984
+ return `ALTER TEXT SEARCH CONFIGURATION ${objectKey} SET SCHEMA ${ident(change.desiredValue)};`;
985
+
986
+ default:
987
+ return `-- Unsupported text search config property: ${property}`;
988
+ }
989
+ }
990
+
991
+ function generateAlterTextSearchDictSql(change) {
992
+ const objectKey = change.objectKey;
993
+ const property = change.property;
994
+
995
+ switch (property) {
996
+ case 'options':
997
+ const opts = change.desiredValue || {};
998
+ const optStr = Object.entries(opts).map(([k, v]) => `${k} = '${v}'`).join(', ');
999
+ return `ALTER TEXT SEARCH DICTIONARY ${objectKey} (${optStr});`;
1000
+
1001
+ case 'owner':
1002
+ return `ALTER TEXT SEARCH DICTIONARY ${objectKey} OWNER TO ${ident(change.desiredValue)};`;
1003
+
1004
+ case 'schema':
1005
+ return `ALTER TEXT SEARCH DICTIONARY ${objectKey} SET SCHEMA ${ident(change.desiredValue)};`;
1006
+
1007
+ default:
1008
+ return `-- Unsupported text search dict property: ${property}`;
1009
+ }
1010
+ }
1011
+
1012
+ function generateAlterOperatorFamilySql(change) {
1013
+ const objectKey = change.objectKey;
1014
+ const accessMethod = change.before?.accessMethod || 'btree';
1015
+ const property = change.property;
1016
+
1017
+ switch (property) {
1018
+ case 'addOperator':
1019
+ const addOp = change.desiredValue;
1020
+ return `ALTER OPERATOR FAMILY ${objectKey} USING ${accessMethod} ADD OPERATOR ${addOp.number} ${addOp.name}${addOp.forType ? ` FOR ORDER BY ${addOp.forType}` : ''};`;
1021
+
1022
+ case 'dropOperator':
1023
+ const dropOp = change.desiredValue;
1024
+ return `ALTER OPERATOR FAMILY ${objectKey} USING ${accessMethod} DROP OPERATOR ${dropOp.number} ${dropOp.name};`;
1025
+
1026
+ case 'addFunction':
1027
+ const addFn = change.desiredValue;
1028
+ return `ALTER OPERATOR FAMILY ${objectKey} USING ${accessMethod} ADD FUNCTION ${addFn.number} ${addFn.name}${addFn.forType ? ` FOR ORDER BY ${addFn.forType}` : ''};`;
1029
+
1030
+ case 'dropFunction':
1031
+ const dropFn = change.desiredValue;
1032
+ return `ALTER OPERATOR FAMILY ${objectKey} USING ${accessMethod} DROP FUNCTION ${dropFn.number} ${dropFn.name};`;
1033
+
1034
+ case 'owner':
1035
+ return `ALTER OPERATOR FAMILY ${objectKey} USING ${accessMethod} OWNER TO ${ident(change.desiredValue)};`;
1036
+ case 'schema':
1037
+ return `ALTER OPERATOR FAMILY ${objectKey} USING ${accessMethod} SET SCHEMA ${ident(change.desiredValue)};`;
1038
+
1039
+ default:
1040
+ return `-- Unsupported operator family property: ${property}`;
1041
+ }
1042
+ }
1043
+
1044
+ function generateAlterPublicationSql(change) {
1045
+ const objectKey = change.objectKey;
1046
+ const property = change.property;
1047
+
1048
+ switch (property) {
1049
+ case 'addTable':
1050
+ const addTables = Array.isArray(change.desiredValue) ? change.desiredValue : [change.desiredValue];
1051
+ const addTableStrs = addTables.map(t => {
1052
+ let tableRef = t.schema ? `${ident(t.schema)}.${ident(t.name)}` : ident(t.name);
1053
+ if (t.columns) tableRef += ` (${t.columns.map(c => ident(c)).join(', ')})`;
1054
+ return tableRef;
1055
+ });
1056
+ return `ALTER PUBLICATION ${ident(objectKey)} ADD TABLE ${addTableStrs.join(', ')};`;
1057
+
1058
+ case 'dropTable':
1059
+ const dropTables = Array.isArray(change.desiredValue) ? change.desiredValue : [change.desiredValue];
1060
+ const dropTableStrs = dropTables.map(t => t.schema ? `${ident(t.schema)}.${ident(t.name)}` : ident(t.name));
1061
+ return `ALTER PUBLICATION ${ident(objectKey)} DROP TABLE ${dropTableStrs.join(', ')};`;
1062
+
1063
+ case 'setTable':
1064
+ const setTables = Array.isArray(change.desiredValue) ? change.desiredValue : [change.desiredValue];
1065
+ const setTableStrs = setTables.map(t => t.schema ? `${ident(t.schema)}.${ident(t.name)}` : ident(t.name));
1066
+ return `ALTER PUBLICATION ${ident(objectKey)} SET TABLE ${setTableStrs.join(', ')};`;
1067
+
1068
+ case 'publish':
1069
+ return `ALTER PUBLICATION ${ident(objectKey)} SET (publish = '${change.desiredValue}');`;
1070
+
1071
+ case 'owner':
1072
+ return `ALTER PUBLICATION ${ident(objectKey)} OWNER TO ${ident(change.desiredValue)};`;
1073
+
1074
+ default:
1075
+ return `-- Unsupported publication property: ${property}`;
1076
+ }
1077
+ }
1078
+
1079
+ function generateAlterSubscriptionSql(change) {
1080
+ const objectKey = change.objectKey;
1081
+ const property = change.property;
1082
+
1083
+ switch (property) {
1084
+ case 'connection':
1085
+ case 'conninfo':
1086
+ return `ALTER SUBSCRIPTION ${ident(objectKey)} CONNECTION '${change.desiredValue}';`;
1087
+
1088
+ case 'setPublication':
1089
+ case 'publications':
1090
+ const pubs = Array.isArray(change.desiredValue) ? change.desiredValue.join(', ') : change.desiredValue;
1091
+ return `ALTER SUBSCRIPTION ${ident(objectKey)} SET PUBLICATION ${pubs};`;
1092
+
1093
+ case 'addPublication':
1094
+ const addPubs = Array.isArray(change.desiredValue) ? change.desiredValue.join(', ') : change.desiredValue;
1095
+ return `ALTER SUBSCRIPTION ${ident(objectKey)} ADD PUBLICATION ${addPubs};`;
1096
+
1097
+ case 'dropPublication':
1098
+ const dropPubs = Array.isArray(change.desiredValue) ? change.desiredValue.join(', ') : change.desiredValue;
1099
+ return `ALTER SUBSCRIPTION ${ident(objectKey)} DROP PUBLICATION ${dropPubs};`;
1100
+
1101
+ case 'refresh':
1102
+ return `ALTER SUBSCRIPTION ${ident(objectKey)} REFRESH PUBLICATION;`;
1103
+
1104
+ case 'enable':
1105
+ return `ALTER SUBSCRIPTION ${ident(objectKey)} ENABLE;`;
1106
+
1107
+ case 'disable':
1108
+ return `ALTER SUBSCRIPTION ${ident(objectKey)} DISABLE;`;
1109
+
1110
+ case 'enabled':
1111
+ return `ALTER SUBSCRIPTION ${ident(objectKey)} ${change.desiredValue ? 'ENABLE' : 'DISABLE'};`;
1112
+
1113
+ case 'syncCommit':
1114
+ const syncVal = typeof change.desiredValue === 'boolean' ? (change.desiredValue ? "'on'" : "'off'") : `'${change.desiredValue}'`;
1115
+ return `ALTER SUBSCRIPTION ${ident(objectKey)} SET (synchronous_commit = ${syncVal});`;
1116
+
1117
+ case 'binaryTransfer':
1118
+ return `ALTER SUBSCRIPTION ${ident(objectKey)} SET (binary = ${change.desiredValue ? 'true' : 'false'});`;
1119
+
1120
+ case 'streaming':
1121
+ const streamVal = typeof change.desiredValue === 'boolean' ? (change.desiredValue ? "'on'" : "'off'") : `'${change.desiredValue}'`;
1122
+ return `ALTER SUBSCRIPTION ${ident(objectKey)} SET (streaming = ${streamVal});`;
1123
+
1124
+ case 'twoPhase':
1125
+ return `ALTER SUBSCRIPTION ${ident(objectKey)} SET (two_phase = ${change.desiredValue ? 'true' : 'false'});`;
1126
+
1127
+ case 'disableOnError':
1128
+ return `ALTER SUBSCRIPTION ${ident(objectKey)} SET (disable_on_error = ${change.desiredValue ? 'true' : 'false'});`;
1129
+
1130
+ case 'origin':
1131
+ const originVal = typeof change.desiredValue === 'boolean' ? (change.desiredValue ? "'any'" : "'none'") : `'${change.desiredValue}'`;
1132
+ return `ALTER SUBSCRIPTION ${ident(objectKey)} SET (origin = ${originVal});`;
1133
+
1134
+ case 'slotName':
1135
+ const slotVal = change.desiredValue ? `'${change.desiredValue}'` : 'NONE';
1136
+ return `ALTER SUBSCRIPTION ${ident(objectKey)} SET (slot_name = ${slotVal});`;
1137
+
1138
+ case 'set':
1139
+ const opts = change.desiredValue || {};
1140
+ const optStr = Object.entries(opts).map(([k, v]) => `${k} = ${typeof v === 'boolean' ? v : `'${v}'`}`).join(', ');
1141
+ return `ALTER SUBSCRIPTION ${ident(objectKey)} SET (${optStr});`;
1142
+
1143
+ case 'owner':
1144
+ return `ALTER SUBSCRIPTION ${ident(objectKey)} OWNER TO ${ident(change.desiredValue)};`;
1145
+
1146
+ default:
1147
+ return `-- Unsupported subscription property: ${property}`;
1148
+ }
1149
+ }
1150
+
1151
+ function generateAlterStatisticsSql(change) {
1152
+ const objectKey = change.objectKey;
1153
+ const property = change.property;
1154
+
1155
+ switch (property) {
1156
+ case 'setColumns':
1157
+ return `ALTER STATISTICS ${objectKey} SET STATISTICS ${change.desiredValue};`;
1158
+
1159
+ case 'columns':
1160
+ case 'setStatistics':
1161
+ case 'statisticsTarget':
1162
+ return `ALTER STATISTICS ${objectKey} SET STATISTICS ${change.desiredValue};`;
1163
+
1164
+ case 'owner':
1165
+ return `ALTER STATISTICS ${objectKey} OWNER TO ${ident(change.desiredValue)};`;
1166
+
1167
+ case 'schema':
1168
+ return `ALTER STATISTICS ${objectKey} SET SCHEMA ${ident(change.desiredValue)};`;
1169
+
1170
+ default:
1171
+ return `-- Unsupported statistics property: ${property}`;
1172
+ }
1173
+ }
1174
+
1175
+ function generateAlterCollationSql(change) {
1176
+ const objectKey = change.objectKey;
1177
+ const property = change.property;
1178
+
1179
+ switch (property) {
1180
+ case 'schema':
1181
+ return `ALTER COLLATION ${objectKey} SET SCHEMA ${ident(change.desiredValue)};`;
1182
+
1183
+ case 'owner':
1184
+ return `ALTER COLLATION ${objectKey} OWNER TO ${ident(change.desiredValue)};`;
1185
+
1186
+ case 'refresh':
1187
+ return `ALTER COLLATION ${objectKey} REFRESH VERSION;`;
1188
+
1189
+ default:
1190
+ return `-- Unsupported collation property: ${property}`;
1191
+ }
1192
+ }
1193
+
1194
+ function generateAlterLanguageSql(change) {
1195
+ const objectKey = change.objectKey;
1196
+ const property = change.property;
1197
+
1198
+ switch (property) {
1199
+ case 'owner':
1200
+ return `ALTER LANGUAGE ${ident(objectKey)} OWNER TO ${ident(change.desiredValue)};`;
1201
+
1202
+ default:
1203
+ return `-- Unsupported language property: ${property}`;
1204
+ }
1205
+ }
1206
+
1207
+ function parseStorageParams(p) {
1208
+ if (!p) return {};
1209
+ if (typeof p === 'object') return p;
1210
+ if (typeof p === 'string') {
1211
+ const obj = {};
1212
+ p.split(',').forEach(item => {
1213
+ const eq = item.indexOf('=');
1214
+ if (eq !== -1) {
1215
+ const k = item.substring(0, eq).trim();
1216
+ const v = item.substring(eq + 1).trim();
1217
+ obj[k] = isNaN(v) ? v : Number(v);
1218
+ }
1219
+ });
1220
+ return obj;
1221
+ }
1222
+ return {};
1223
+ }
1224
+
1225
+ function generateAlterMaterializedViewSql(change) {
1226
+ const objectKey = change.objectKey;
1227
+ const property = change.property;
1228
+
1229
+ switch (property) {
1230
+ case 'storageOptions':
1231
+ case 'storageParameters':
1232
+ case 'storageSettings': {
1233
+ const currentParams = parseStorageParams(change.currentValue);
1234
+ const desiredParams = parseStorageParams(change.desiredValue);
1235
+ const toSet = [];
1236
+ const toReset = [];
1237
+
1238
+ for (const [k, v] of Object.entries(desiredParams)) {
1239
+ if (currentParams[k] !== v) {
1240
+ toSet.push(`${k}=${v}`);
1241
+ }
1242
+ }
1243
+ for (const k of Object.keys(currentParams)) {
1244
+ if (desiredParams[k] === undefined) {
1245
+ toReset.push(k);
1246
+ }
1247
+ }
1248
+
1249
+ const stmts = [];
1250
+ if (toSet.length > 0) {
1251
+ stmts.push(`ALTER MATERIALIZED VIEW ${objectKey} SET (${toSet.join(', ')});`);
1252
+ }
1253
+ if (toReset.length > 0) {
1254
+ stmts.push(`ALTER MATERIALIZED VIEW ${objectKey} RESET (${toReset.join(', ')});`);
1255
+ }
1256
+ return stmts.join('\n');
1257
+ }
1258
+
1259
+ case 'owner':
1260
+ return `ALTER MATERIALIZED VIEW ${objectKey} OWNER TO ${ident(change.desiredValue)};`;
1261
+
1262
+ case 'schema':
1263
+ return `ALTER MATERIALIZED VIEW ${objectKey} SET SCHEMA ${ident(change.desiredValue)};`;
1264
+
1265
+ case 'tablespace':
1266
+ return `ALTER MATERIALIZED VIEW ${objectKey} SET TABLESPACE ${ident(change.desiredValue)};`;
1267
+
1268
+ case 'renameColumn':
1269
+ return `ALTER MATERIALIZED VIEW ${objectKey} RENAME COLUMN ${ident(change.currentValue)} TO ${ident(change.desiredValue)};`;
1270
+
1271
+ case 'rename':
1272
+ return `ALTER MATERIALIZED VIEW ${objectKey} RENAME TO ${ident(change.desiredValue)};`;
1273
+
1274
+ default:
1275
+ return `-- Unsupported access method property: ${property}`;
1276
+ }
1277
+ }
1278
+
1279
+ function generateAlterOperatorSql(change) {
1280
+ const objectKey = change.objectKey;
1281
+ const property = change.property;
1282
+ const leftType = change.before?.leftType || change.after?.leftType || 'NONE';
1283
+ const rightType = change.before?.rightType || change.after?.rightType || 'NONE';
1284
+
1285
+ switch (property) {
1286
+ case 'owner':
1287
+ return `ALTER OPERATOR ${objectKey} (${leftType}, ${rightType}) OWNER TO ${ident(change.desiredValue)};`;
1288
+ case 'schema':
1289
+ return `ALTER OPERATOR ${objectKey} (${leftType}, ${rightType}) SET SCHEMA ${ident(change.desiredValue)};`;
1290
+ case 'storageParameters':
1291
+ case 'storageOptions':
1292
+ case 'storageSettings': {
1293
+ const currentParams = change.currentValue || {};
1294
+ const desiredParams = change.desiredValue || {};
1295
+ const toSet = [];
1296
+ const toReset = [];
1297
+
1298
+ for (const [k, v] of Object.entries(desiredParams)) {
1299
+ if (currentParams[k] !== v) {
1300
+ toSet.push(`${k}=${v}`);
1301
+ }
1302
+ }
1303
+ for (const k of Object.keys(currentParams)) {
1304
+ if (desiredParams[k] === undefined) {
1305
+ toReset.push(k);
1306
+ }
1307
+ }
1308
+
1309
+ const stmts = [];
1310
+ if (toSet.length > 0) {
1311
+ stmts.push(`ALTER OPERATOR ${objectKey} (${leftType}, ${rightType}) SET (${toSet.join(', ')});`);
1312
+ }
1313
+ if (toReset.length > 0) {
1314
+ stmts.push(`ALTER OPERATOR ${objectKey} (${leftType}, ${rightType}) RESET (${toReset.join(', ')});`);
1315
+ }
1316
+ return stmts.join('\n');
1317
+ }
1318
+ default:
1319
+ return `-- Unsupported operator property: ${property}`;
1320
+ }
1321
+ }
1322
+
1323
+ function generateAlterOperatorClassSql(change) {
1324
+ const objectKey = change.objectKey;
1325
+ const property = change.property;
1326
+ const accessMethod = change.after?.accessMethod || change.before?.accessMethod;
1327
+
1328
+ if (!accessMethod) {
1329
+ return `-- Cannot generate operator class ALTER without access method`;
1330
+ }
1331
+
1332
+ switch (property) {
1333
+ case 'owner':
1334
+ return `ALTER OPERATOR CLASS ${objectKey} USING ${accessMethod} OWNER TO ${ident(change.desiredValue)};`;
1335
+ case 'schema':
1336
+ return `ALTER OPERATOR CLASS ${objectKey} USING ${accessMethod} SET SCHEMA ${ident(change.desiredValue)};`;
1337
+ default:
1338
+ return `-- Unsupported operator class property: ${property}`;
1339
+ }
1340
+ }
1341
+
1342
+ function generateAlterAccessMethodSql(change) {
1343
+ const objectKey = change.objectKey;
1344
+ const property = change.property;
1345
+ return `-- PostgreSQL does not support altering access method handler: ${objectKey} (${property})`;
1346
+ }
1347
+
1348
+ function generateAlterEventTriggerSql(change) {
1349
+ const name = change.objectKey;
1350
+ const property = change.property;
1351
+ const value = change.desiredValue;
1352
+
1353
+ switch (property) {
1354
+ case 'enabled':
1355
+ const isEnabled = value === true || value === 'ENABLED' || change.after?.enabled === true || change.after?.enabled === 'ENABLED';
1356
+ return `ALTER EVENT TRIGGER ${ident(name)} ${isEnabled ? 'ENABLE' : 'DISABLE'};`;
1357
+ case 'owner':
1358
+ return `ALTER EVENT TRIGGER ${ident(name)} OWNER TO ${ident(value)};`;
1359
+ default:
1360
+ return `-- Unsupported event trigger property: ${property}`;
1361
+ }
1362
+ }
1363
+
1364
+ function generateGenericCommentSql(change) {
1365
+ const typeMap = {
1366
+ table: 'TABLE',
1367
+ column: 'COLUMN',
1368
+ index: 'INDEX',
1369
+ constraint: 'CONSTRAINT',
1370
+ view: 'VIEW',
1371
+ materializedView: 'MATERIALIZED VIEW',
1372
+ function: 'FUNCTION',
1373
+ procedure: 'PROCEDURE',
1374
+ trigger: 'TRIGGER',
1375
+ eventTrigger: 'EVENT TRIGGER',
1376
+ policy: 'POLICY',
1377
+ rule: 'RULE',
1378
+ aggregate: 'AGGREGATE',
1379
+ sequence: 'SEQUENCE',
1380
+ type: 'TYPE',
1381
+ extension: 'EXTENSION',
1382
+ schema: 'SCHEMA',
1383
+ statistics: 'STATISTICS',
1384
+ collation: 'COLLATION',
1385
+ operator: 'OPERATOR',
1386
+ operatorClass: 'OPERATOR CLASS',
1387
+ operatorFamily: 'OPERATOR FAMILY',
1388
+ cast: 'CAST',
1389
+ foreignTable: 'FOREIGN TABLE',
1390
+ foreignDataWrapper: 'FOREIGN DATA WRAPPER',
1391
+ foreignServer: 'SERVER',
1392
+ publication: 'PUBLICATION',
1393
+ subscription: 'SUBSCRIPTION',
1394
+ textSearchConfig: 'TEXT SEARCH CONFIGURATION',
1395
+ textSearchDict: 'TEXT SEARCH DICTIONARY',
1396
+ textSearchParser: 'TEXT SEARCH PARSER',
1397
+ textSearchTemplate: 'TEXT SEARCH TEMPLATE',
1398
+ conversion: 'CONVERSION',
1399
+ language: 'LANGUAGE'
1400
+ };
1401
+
1402
+ const typeKeyword = typeMap[change.objectType];
1403
+ if (!typeKeyword) return `-- Unsupported comment for ${change.objectType}`;
1404
+
1405
+ const name = change.objectKey;
1406
+ const val = change.desiredValue;
1407
+
1408
+ let formattedName = name;
1409
+ if (change.objectType === 'column') {
1410
+ const parts = name.split('.');
1411
+ const col = parts.pop();
1412
+ const tab = parts.join('.');
1413
+ formattedName = `${tab}.${ident(col)}`;
1414
+ } else if (change.objectType === 'constraint') {
1415
+ const parts = name.split('.');
1416
+ const conName = parts.pop();
1417
+ const tab = parts.join('.');
1418
+ formattedName = `${ident(conName)} ON ${tab}`;
1419
+ } else if (change.objectType === 'trigger' || change.objectType === 'rule' || change.objectType === 'policy') {
1420
+ const parts = name.split('.');
1421
+ const objName = parts.pop();
1422
+ const tab = parts.join('.');
1423
+ formattedName = `${ident(objName)} ON ${tab}`;
1424
+ } else if (change.objectType === 'function' || change.objectType === 'procedure') {
1425
+ const fn = change.after || change.before || {};
1426
+ const argTypesList = fn.argumentTypes || fn.arguments || [];
1427
+ const argsStr = Array.isArray(argTypesList) ? argTypesList.join(', ') : '';
1428
+ formattedName = `${name}(${argsStr})`;
1429
+ } else if (change.objectType === 'operator') {
1430
+ const leftType = change.before?.leftType || change.after?.leftType || 'NONE';
1431
+ const rightType = change.before?.rightType || change.after?.rightType || 'NONE';
1432
+ formattedName = `${name} (${leftType}, ${rightType})`;
1433
+ } else if (change.objectType === 'operatorClass' || change.objectType === 'operatorFamily') {
1434
+ const accessMethod = change.after?.accessMethod || change.before?.accessMethod || 'btree';
1435
+ formattedName = `${name} USING ${accessMethod}`;
1436
+ }
1437
+
1438
+ if (val === null || val === undefined || val === '') {
1439
+ return `COMMENT ON ${typeKeyword} ${formattedName} IS NULL;`;
1440
+ }
1441
+ return `COMMENT ON ${typeKeyword} ${formattedName} IS '${escapeString(val)}';`;
1442
+ }
1443
+
1444
+ function generateGenericPrivilegesSql(change) {
1445
+ const typeMap = {
1446
+ table: 'TABLE',
1447
+ column: 'COLUMN',
1448
+ view: 'VIEW',
1449
+ materializedView: 'MATERIALIZED VIEW',
1450
+ function: 'FUNCTION',
1451
+ procedure: 'PROCEDURE',
1452
+ sequence: 'SEQUENCE',
1453
+ type: 'TYPE',
1454
+ schema: 'SCHEMA',
1455
+ foreignTable: 'FOREIGN TABLE',
1456
+ foreignDataWrapper: 'FOREIGN DATA WRAPPER',
1457
+ foreignServer: 'SERVER',
1458
+ publication: 'PUBLICATION',
1459
+ subscription: 'SUBSCRIPTION',
1460
+ textSearchConfig: 'TEXT SEARCH CONFIGURATION',
1461
+ textSearchDict: 'TEXT SEARCH DICTIONARY',
1462
+ conversion: 'CONVERSION',
1463
+ language: 'LANGUAGE'
1464
+ };
1465
+
1466
+ const typeKeyword = typeMap[change.objectType];
1467
+ if (!typeKeyword) return `-- Unsupported privileges for ${change.objectType}`;
1468
+
1469
+ const name = change.objectKey;
1470
+ let formattedName = name;
1471
+ if (change.objectType === 'column') {
1472
+ const parts = name.split('.');
1473
+ const col = parts.pop();
1474
+ const tab = parts.join('.');
1475
+ formattedName = `${tab}.${ident(col)}`;
1476
+ } else if (change.objectType === 'function' || change.objectType === 'procedure') {
1477
+ const fn = change.after || change.before || {};
1478
+ const argTypesList = fn.argumentTypes || fn.arguments || [];
1479
+ const argsStr = Array.isArray(argTypesList) ? argTypesList.join(', ') : '';
1480
+ formattedName = `${name}(${argsStr})`;
1481
+ }
1482
+
1483
+ const stmts = [];
1484
+ stmts.push(`REVOKE ALL ON ${typeKeyword} ${formattedName} FROM PUBLIC;`);
1485
+
1486
+ const privileges = change.desiredValue || [];
1487
+ if (Array.isArray(privileges)) {
1488
+ for (const g of privileges) {
1489
+ let grantSql = `GRANT ${g.privilege} ON ${typeKeyword} ${formattedName} TO ${ident(g.grantee)}`;
1490
+ if (g.isGrantable) grantSql += ' WITH GRANT OPTION';
1491
+ stmts.push(grantSql + ';');
1492
+ }
1493
+ }
1494
+ return stmts.join('\n');
1495
+ }
1496
+
1497
+ function generateAlterExtensionSql(change) {
1498
+ const name = change.objectKey;
1499
+ const property = change.property;
1500
+ const value = change.desiredValue;
1501
+
1502
+ switch (property) {
1503
+ case 'version':
1504
+ return `ALTER EXTENSION ${ident(name)} UPDATE TO '${escapeString(value)}';`;
1505
+ case 'schema':
1506
+ return `ALTER EXTENSION ${ident(name)} SET SCHEMA ${ident(value)};`;
1507
+ default:
1508
+ return `-- Unsupported extension property: ${property}`;
1509
+ }
1510
+ }
1511
+
1512
+