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,1283 @@
1
+ import crypto from 'crypto';
2
+
3
+ /**
4
+ * SchemaSnapshot Shape (complete)
5
+ *
6
+ * {
7
+ * version: { major, minor, patch, numeric },
8
+ * timestamp: string,
9
+ * database: { name, owner, encoding, collate, ctype },
10
+ *
11
+ * // Database-level objects
12
+ * roles: [{ name, superuser, inherit, createRole, createDB, canLogin, memberships }],
13
+ * tablespaces: [{ name, owner, location, acl }],
14
+ * casts: [{ sourceType, targetType, function, context, method }],
15
+ * accessMethods: [{ name, handler, type }],
16
+ * languages: [{ name, owner, trusted, handler }],
17
+ * defaultPrivileges: [{ role, schema, objectType, acl }],
18
+ * databases: [{ name, owner, encoding, collate, ctype, isTemplate, allowConn }],
19
+ *
20
+ * // Cross-schema objects
21
+ * publications: [{ name, owner, allTables, operations, tables, schemas }],
22
+ * subscriptions: [{ name, conninfo(REDRACTED), slotName, publications, enabled }],
23
+ *
24
+ * // Per-schema objects
25
+ * schemas: {
26
+ * [schemaName]: {
27
+ * tables: [...],
28
+ * columns: [...], // With ALL 14 properties
29
+ * indexes: [...],
30
+ * constraints: [...], // PK, UNIQUE, FK, CHECK, EXCLUSION, NOT NULL
31
+ * sequences: [...],
32
+ * views: [...],
33
+ * materializedViews: [...],
34
+ * functions: [...],
35
+ * procedures: [...],
36
+ * aggregates: [...],
37
+ * triggers: [...],
38
+ * eventTriggers: [...],
39
+ * policies: [...],
40
+ * enums: [...],
41
+ * compositeTypes: [...],
42
+ * domainTypes: [...],
43
+ * rangeTypes: [...],
44
+ * multirangeTypes: [...],
45
+ * rules: [...],
46
+ * collations: [...],
47
+ * conversions: [...],
48
+ * operators: [...],
49
+ * operatorClasses: [...],
50
+ * operatorFamilies: [...],
51
+ * textSearchConfigs: [...],
52
+ * textSearchDictionaries: [...],
53
+ * textSearchParsers: [...],
54
+ * textSearchTemplates: [...],
55
+ * foreignTables: [...],
56
+ * extensions: [...],
57
+ * statistics: [...],
58
+ * inheritance: [...],
59
+ * partitions: [...],
60
+ * comments: [...],
61
+ * grants: [...], // Table-level + column-level
62
+ * defaultPrivileges: [...],
63
+ * }
64
+ * }
65
+ * }
66
+ */
67
+
68
+ /**
69
+ * Translates raw pg_catalog query results into the canonical SchemaSnapshot.
70
+ * This is a PURE READ-ONLY operation - we never modify the user's schema.
71
+ * @param {Object} raw
72
+ * @returns {import('../types/schema.js').SchemaSnapshot}
73
+ */
74
+ export function translateSnapshot(raw) {
75
+ const {
76
+ version,
77
+ database,
78
+ schemas = [],
79
+ tables = [],
80
+ columns = [],
81
+ constraints = [],
82
+ indexes = [],
83
+ indexColumns = [],
84
+ functions = [],
85
+ triggers = [],
86
+ types = { enums: [], composites: [], domains: [], ranges: [], multiranges: [] },
87
+ views = [],
88
+ materializedViews = [],
89
+ sequences = [],
90
+ partitions = [],
91
+ policies = [],
92
+ extensions = [],
93
+ inheritance = [],
94
+ comments = {},
95
+ grants = [],
96
+ pg18Features = { notEnforced: [], virtualColumns: [] },
97
+ // New object types
98
+ publications = [],
99
+ subscriptions = [],
100
+ statistics = [],
101
+ collations = [],
102
+ conversions = [],
103
+ operators = [],
104
+ operatorClasses = [],
105
+ operatorFamilies = [],
106
+ textSearchConfigs = {},
107
+ textSearchDictionaries = [],
108
+ textSearchParsers = [],
109
+ textSearchTemplates = [],
110
+ foreignDataWrappers = [],
111
+ foreignServers = [],
112
+ userMappings = [],
113
+ foreignTables = {},
114
+ casts = [],
115
+ eventTriggers = [],
116
+ rules = [],
117
+ roles = [],
118
+ tablespaces = [],
119
+ accessMethods = [],
120
+ proceduralLanguages = [],
121
+ defaultPrivileges = [],
122
+ databases = [],
123
+ aggregates = [],
124
+ procedures = [],
125
+ toastOptions = [],
126
+ } = raw;
127
+
128
+ const schemaMap = {};
129
+ for (const s of schemas) {
130
+ schemaMap[s.name] = {
131
+ name: s.name,
132
+ owner: s.owner,
133
+ privileges: parseACL(s.privileges),
134
+ comment: s.comment || undefined,
135
+ tables: [],
136
+ views: [],
137
+ materializedViews: [],
138
+ indexes: [],
139
+ sequences: [],
140
+ functions: [],
141
+ procedures: [],
142
+ aggregates: [],
143
+ triggers: [],
144
+ eventTriggers: [],
145
+ policies: [],
146
+ types: [],
147
+ enums: [],
148
+ compositeTypes: [],
149
+ domainTypes: [],
150
+ rangeTypes: [],
151
+ multirangeTypes: [],
152
+ rules: [],
153
+ collations: [],
154
+ conversions: [],
155
+ operators: [],
156
+ operatorClasses: [],
157
+ operatorFamilies: [],
158
+ textSearchConfigs: [],
159
+ textSearchDictionaries: [],
160
+ textSearchParsers: [],
161
+ textSearchTemplates: [],
162
+ foreignTables: [],
163
+ extensions: [],
164
+ statistics: [],
165
+ comments: {},
166
+ grants: [],
167
+ defaultPrivileges: [],
168
+ };
169
+ }
170
+
171
+ const tableMap = {};
172
+ for (const t of tables) {
173
+ const key = `${t.schema}.${t.name}`;
174
+ const isForeignTable = t.kind === 'f';
175
+ tableMap[key] = {
176
+ schema: t.schema,
177
+ name: t.name,
178
+ owner: t.owner,
179
+ isTemporary: t.persistence === 't',
180
+ isUnlogged: t.persistence === 'u',
181
+ isPartitioned: t.kind === 'p',
182
+ isPartition: partitions.some(p => p.child_table === t.name && p.child_schema === t.schema),
183
+ isForeignTable,
184
+ partitionStrategy: t.partition_strategy ? ({ r: 'RANGE', l: 'LIST', h: 'HASH' }[t.partition_strategy] || t.partition_strategy) : undefined,
185
+ partitionColumns: partitions.find(p => p.child_table === t.name && p.child_schema === t.schema)?.partition_columns || undefined,
186
+ partitionParent: (() => {
187
+ const p = partitions.find(p => p.child_table === t.name && p.child_schema === t.schema);
188
+ return p ? `${p.parent_schema}.${p.parent_table}` : undefined;
189
+ })(),
190
+ partitionBound: t.partition_bound || undefined,
191
+ partitionKeyDef: t.partition_key_def || undefined,
192
+ isDefaultPartition: partitions.find(p => p.child_table === t.name && p.child_schema === t.schema)?.is_default || false,
193
+ partitionExpression: partitions.find(p => p.child_table === t.name && p.child_schema === t.schema)?.partition_expression || undefined,
194
+ inheritsFrom: inheritance.filter(i => i.child_table === t.name && i.child_schema === t.schema).map(i => `${i.parent_schema}.${i.parent_table}`),
195
+ tablespace: t.tablespace || undefined,
196
+ storageParameters: t.storage_options ? parseStorageOptions(t.storage_options) : undefined,
197
+ replicaIdentity: (() => {
198
+ if (t.replica_identity === 'd') return 'default';
199
+ if (t.replica_identity === 'f') return 'full';
200
+ if (t.replica_identity === 'n') return 'nothing';
201
+ if (t.replica_identity === 'i' && t.replica_identity_index) return `index:${t.replica_identity_index}`;
202
+ return undefined;
203
+ })(),
204
+ accessMethod: t.access_method || undefined,
205
+ hasOids: t.has_oids || false,
206
+ userCatalog: t.user_catalog_table || false,
207
+ comment: comments[key] || undefined,
208
+ privileges: [],
209
+ rowLevelSecurity: t.rls_enabled,
210
+ forceRowLevelSecurity: t.rls_forced,
211
+ rlsEnabled: t.rls_enabled,
212
+ rlsForced: t.rls_forced,
213
+ columns: [],
214
+ constraints: [],
215
+ indexes: [],
216
+ triggers: [],
217
+ policies: [],
218
+ foreignServer: undefined,
219
+ foreignOptions: undefined,
220
+ };
221
+ if (schemaMap[t.schema]) {
222
+ if (isForeignTable) {
223
+ schemaMap[t.schema].foreignTables.push(key);
224
+ } else {
225
+ schemaMap[t.schema].tables.push(key);
226
+ }
227
+ }
228
+ }
229
+
230
+ // Add foreign table details
231
+ const ftTables = foreignTables.tables || foreignTables || [];
232
+ const ftColumnOptions = foreignTables.columnOptions || [];
233
+
234
+ for (const ft of ftTables) {
235
+ const key = `${ft.schema}.${ft.name}`;
236
+ if (tableMap[key]) {
237
+ tableMap[key].foreignServer = ft.server_name;
238
+ tableMap[key].foreignOptions = ft.options || {};
239
+ tableMap[key].comment = ft.comment || tableMap[key].comment;
240
+ tableMap[key].privileges = ft.privileges || tableMap[key].privileges;
241
+ }
242
+ }
243
+
244
+ // Add foreign table column options
245
+ const ftColOptionsMap = {};
246
+ for (const col of ftColumnOptions) {
247
+ const key = `${col.schema}.${col.table_name}`;
248
+ if (!ftColOptionsMap[key]) ftColOptionsMap[key] = {};
249
+ ftColOptionsMap[key][col.column_name] = col.options;
250
+ }
251
+
252
+ for (const [key, columnOpts] of Object.entries(ftColOptionsMap)) {
253
+ if (tableMap[key] && tableMap[key].columns) {
254
+ for (const col of tableMap[key].columns) {
255
+ if (columnOpts[col.name]) {
256
+ col.foreignOptions = columnOpts[col.name];
257
+ }
258
+ }
259
+ }
260
+ }
261
+
262
+ // Add TOAST storage options to tables
263
+ for (const t of toastOptions) {
264
+ const key = `${t.schema}.${t.table_name}`;
265
+ if (tableMap[key]) {
266
+ tableMap[key].toastStorageOptions = t.toast_storage_options ? parseStorageOptions(t.toast_storage_options) : undefined;
267
+ tableMap[key].toastTableName = t.toast_table_name;
268
+ }
269
+ }
270
+
271
+ for (const col of columns) {
272
+ const key = `${col.schema}.${col.table_name}`;
273
+ if (tableMap[key]) {
274
+ const isGenerated = col.generated === 's' || col.generated === 'v';
275
+ const isIdentity = col.identity === 'a' || col.identity === 'd';
276
+ tableMap[key].columns.push({
277
+ name: col.name,
278
+ dataType: col.data_type,
279
+ ordinalPosition: col.ordinal_position,
280
+ isNullable: col.is_nullable,
281
+ defaultValue: col.default_value || undefined,
282
+ isGenerated,
283
+ generatedExpression: isGenerated ? col.generated_expression : undefined,
284
+ generatedStorage: col.generated === 'v' ? 'VIRTUAL' : col.generated === 's' ? 'STORED' : undefined,
285
+ isIdentity,
286
+ identityMode: col.identity === 'a' ? 'ALWAYS' : col.identity === 'd' ? 'BY_DEFAULT' : undefined,
287
+ identityStart: col.identity_start ?? undefined,
288
+ identityIncrement: col.identity_increment ?? undefined,
289
+ identityMin: col.identity_min ?? undefined,
290
+ identityMax: col.identity_max ?? undefined,
291
+ identityCycle: col.identity_cycle ?? undefined,
292
+ identityCache: col.identity_cache ?? undefined,
293
+ collation: col.collation || undefined,
294
+ storage: col.storage || undefined,
295
+ comment: col.comment || undefined,
296
+ statisticsTarget: col.statistics_target ?? undefined,
297
+ compression: col.compression || undefined,
298
+ inheritedCount: col.inherited_count ?? 0,
299
+ isLocal: col.is_local ?? false,
300
+ privileges: parseACL(col.privileges),
301
+ isPrimaryKey: col.is_primary_key ?? false,
302
+ isUnique: col.is_unique ?? false,
303
+ length: col.length ?? undefined,
304
+ arrayDimensions: col.array_dimensions ?? undefined,
305
+ foreignOptions: col.foreign_options || undefined,
306
+ });
307
+ }
308
+ }
309
+
310
+ // Fix: Iterate over Object.values(tableMap), not tableMap directly
311
+ for (const table of Object.values(tableMap)) {
312
+ table.columns.sort((a, b) => a.ordinalPosition - b.ordinalPosition);
313
+ }
314
+
315
+ const constraintMap = {};
316
+ for (const c of constraints) {
317
+ const key = `${c.schema}.${c.name}`;
318
+ const tableKey = c.table_ref?.includes('.') ? c.table_ref : `${c.schema}.${c.table_ref}`;
319
+ constraintMap[key] = {
320
+ schema: c.schema,
321
+ name: c.name,
322
+ table: tableKey,
323
+ type: c.type,
324
+ deferrable: c.deferrable,
325
+ initiallyDeferred: c.initially_deferred,
326
+ isValidated: c.is_validated,
327
+ enforced: c.type === 'FOREIGN_KEY' ? true : (pg18Features.notEnforced.find(ne => ne.name === c.name && ne.schema === c.schema) ? false : true),
328
+ noInherit: c.no_inherit,
329
+ isInherited: c.is_inherited,
330
+ isLocal: c.is_local,
331
+ comment: c.comment || undefined,
332
+ definition: c.definition,
333
+ columns: c.column_indices ? getColumnNamesFromIndices(tableKey, c.column_indices, tableMap) : undefined,
334
+ referencedTable: c.foreign_table || undefined,
335
+ referencedColumns: c.foreign_column_indices ? getColumnNamesFromIndices(c.foreign_table, c.foreign_column_indices, tableMap) : undefined,
336
+ matchType: c.match_type || undefined,
337
+ onDelete: c.on_delete || undefined,
338
+ onUpdate: c.on_update || undefined,
339
+ index: c.index_name || undefined,
340
+ indexTablespace: c.index_tablespace || undefined,
341
+ exclusionExpression: c.exclusion_expression || undefined,
342
+ };
343
+ if (tableMap[tableKey]) {
344
+ tableMap[tableKey].constraints.push(key);
345
+ }
346
+ }
347
+
348
+ const indexMap = {};
349
+ for (const idx of indexes) {
350
+ const key = `${idx.schema}.${idx.index_name}`;
351
+ const tableKey = `${idx.table_schema}.${idx.table_name}`;
352
+
353
+ // Get columns for this index and remove duplicates
354
+ let cols = indexColumns.filter(ic => ic.index_relname === idx.index_relname && ic.schema === idx.schema);
355
+ // Remove duplicates by position
356
+ const seenPositions = new Set();
357
+ cols = cols.filter(ic => {
358
+ if (seenPositions.has(ic.position)) return false;
359
+ seenPositions.add(ic.position);
360
+ return true;
361
+ });
362
+
363
+ // Build include columns list (columns after number_of_key_columns)
364
+ const numKeyCols = idx.number_of_key_columns || cols.length;
365
+ const includeCols = numKeyCols < cols.length
366
+ ? cols.slice(numKeyCols).map(c => c.column_name)
367
+ : undefined;
368
+
369
+ indexMap[key] = {
370
+ schema: idx.schema,
371
+ name: idx.index_name,
372
+ table: tableKey,
373
+ isUnique: idx.is_unique,
374
+ isPrimary: idx.is_primary,
375
+ isConcurrent: false,
376
+ method: idx.method,
377
+ columns: cols.slice(0, numKeyCols).map((c, i) => ({
378
+ expression: c.expression || c.column_name || undefined,
379
+ collation: c.collation || undefined,
380
+ opclass: c.opclass || undefined,
381
+ direction: c.direction || undefined,
382
+ nullsOrder: c.nulls_order || undefined,
383
+ comment: c.column_comment || undefined,
384
+ })),
385
+ includeColumns: includeCols,
386
+ whereClause: idx.where_clause || undefined,
387
+ storageParameters: idx.storage_options ? parseStorageOptions(idx.storage_options) : undefined,
388
+ tablespace: idx.tablespace || undefined,
389
+ comment: idx.comment || undefined,
390
+ owner: idx.owner,
391
+ isValid: idx.is_valid ?? true,
392
+ isReady: idx.is_ready ?? true,
393
+ isLive: idx.is_live ?? true,
394
+ isReplicaIdentity: idx.is_replica_identity ?? false,
395
+ isClustered: idx.is_clustered ?? false,
396
+ numberOfKeyColumns: numKeyCols,
397
+ nullsNotDistinct: idx.nulls_not_distinct || undefined,
398
+ brinPagesPerRange: idx.brin_pages_per_range ?? undefined,
399
+ definition: idx.definition,
400
+ };
401
+ if (tableMap[tableKey]) {
402
+ tableMap[tableKey].indexes.push(key);
403
+ }
404
+ if (schemaMap[idx.schema]) {
405
+ schemaMap[idx.schema].indexes.push(key);
406
+ }
407
+ }
408
+
409
+ const functionMap = {};
410
+ for (const f of functions) {
411
+ const key = `${f.schema}.${f.name}(${f.argument_types || ''})`;
412
+ functionMap[key] = {
413
+ schema: f.schema,
414
+ name: f.name,
415
+ argumentTypes: f.argument_types ? f.argument_types.split(',').map(s => s.trim()).filter(Boolean) : [],
416
+ argumentNames: f.argument_names || [],
417
+ argumentDefaults: f.argument_defaults || undefined,
418
+ argumentModes: f.argument_modes || [],
419
+ returnType: f.return_type,
420
+ returnSet: f.return_set || false,
421
+ language: f.language,
422
+ source: f.source,
423
+ precompiledBody: f.precompiled_body || undefined,
424
+ volatility: f.volatility,
425
+ isStrict: f.is_strict,
426
+ security: f.security,
427
+ parallel: f.parallel,
428
+ isLeakproof: f.is_leakproof,
429
+ cost: f.cost,
430
+ rows: f.rows,
431
+ kind: f.kind,
432
+ owner: f.owner,
433
+ configuration: f.configuration || undefined,
434
+ supportFunction: f.support_function || undefined,
435
+ privileges: f.privileges || undefined,
436
+ comment: f.comment || undefined,
437
+ sfunc: f.agg_sfunc || undefined,
438
+ stype: f.agg_stype || undefined,
439
+ finalfunc: f.agg_finalfunc || undefined,
440
+ combinefunc: f.agg_combinefunc || undefined,
441
+ initcond: f.agg_initcond || undefined,
442
+ sspace: f.agg_sspace || undefined,
443
+ finalfuncExtra: f.agg_finalfunc_extra || false,
444
+ finalfuncModify: f.agg_finalfunc_modify || undefined,
445
+ serialfunc: f.agg_serialfunc || undefined,
446
+ deserialfunc: f.agg_deserialfunc || undefined,
447
+ sortop: f.agg_sortop || undefined,
448
+ hypothetical: f.agg_hypothetical || false,
449
+ };
450
+ if (schemaMap[f.schema]) {
451
+ if (f.kind === 'AGGREGATE') {
452
+ schemaMap[f.schema].aggregates.push(key);
453
+ } else if (f.kind === 'PROCEDURE') {
454
+ schemaMap[f.schema].procedures.push(key);
455
+ } else {
456
+ schemaMap[f.schema].functions.push(key);
457
+ }
458
+ }
459
+ }
460
+
461
+ const triggerMap = {};
462
+ for (const t of triggers) {
463
+ const key = `${t.schema}.${t.table_name}.${t.name}`;
464
+ const tableKey = `${t.table_schema}.${t.table_name}`;
465
+ triggerMap[key] = {
466
+ schema: t.schema,
467
+ name: t.name,
468
+ table: tableKey,
469
+ timing: t.timing,
470
+ events: t.events || [],
471
+ level: t.level || 'ROW',
472
+ isForEachRow: t.is_for_each_row,
473
+ function: t.function_call || `${t.function_schema}.${t.function_name}`,
474
+ whenCondition: t.when_condition || undefined,
475
+ functionCall: t.function_call,
476
+ enabled: t.enabled,
477
+ isConstraint: t.is_constraint || false,
478
+ isDeferrable: t.is_deferrable || false,
479
+ isDeferred: t.is_deferred || false,
480
+ updateOfColumns: t.update_of_columns || [],
481
+ oldTableName: t.old_table_name || undefined,
482
+ newTableName: t.new_table_name || undefined,
483
+ comment: t.comment || undefined,
484
+ };
485
+ if (tableMap[tableKey]) {
486
+ tableMap[tableKey].triggers.push(key);
487
+ }
488
+ }
489
+
490
+ const typeMap = {};
491
+ for (const e of types.enums) {
492
+ const key = `${e.schema}.${e.name}`;
493
+ typeMap[key] = {
494
+ schema: e.schema,
495
+ name: e.name,
496
+ kind: 'ENUM',
497
+ enumValues: e.enum_values || [],
498
+ owner: e.owner,
499
+ comment: e.comment || undefined,
500
+ privileges: parseACL(e.privileges),
501
+ arrayType: e.array_type || undefined,
502
+ };
503
+ if (schemaMap[e.schema]) {
504
+ schemaMap[e.schema].enums.push(key);
505
+ }
506
+ }
507
+ for (const c of types.composites) {
508
+ const key = `${c.schema}.${c.name}`;
509
+ typeMap[key] = {
510
+ schema: c.schema,
511
+ name: c.name,
512
+ kind: 'COMPOSITE',
513
+ attributes: c.attributes || [],
514
+ owner: c.owner,
515
+ comment: c.comment || undefined,
516
+ };
517
+ if (schemaMap[c.schema]) {
518
+ schemaMap[c.schema].compositeTypes.push(key);
519
+ }
520
+ }
521
+ for (const d of types.domains) {
522
+ const key = `${d.schema}.${d.name}`;
523
+ typeMap[key] = {
524
+ schema: d.schema,
525
+ name: d.name,
526
+ kind: 'DOMAIN',
527
+ baseType: d.base_type,
528
+ baseTypeSchema: d.base_type_schema || undefined,
529
+ notNull: d.not_null,
530
+ defaultValue: d.default_value || undefined,
531
+ checkConstraint: d.check_constraint || undefined,
532
+ owner: d.owner,
533
+ comment: d.comment || undefined,
534
+ collation: d.collation || undefined,
535
+ privileges: parseACL(d.privileges),
536
+ isValidated: d.is_validated ?? true,
537
+ typmod: d.typmod ?? undefined,
538
+ length: d.length ?? undefined,
539
+ };
540
+ if (schemaMap[d.schema]) {
541
+ schemaMap[d.schema].domainTypes.push(key);
542
+ }
543
+ }
544
+ for (const r of types.ranges) {
545
+ const key = `${r.schema}.${r.name}`;
546
+ typeMap[key] = {
547
+ schema: r.schema,
548
+ name: r.name,
549
+ kind: 'RANGE',
550
+ subtype: r.subtype,
551
+ subtypeSchema: r.subtype_schema || undefined,
552
+ multirangeType: r.multirange_type || undefined,
553
+ collation: r.collation || undefined,
554
+ subtypeOpclass: r.subtype_opclass || undefined,
555
+ subtypeDiff: r.subtype_diff || undefined,
556
+ canonicalFunction: r.canonical_function || undefined,
557
+ owner: r.owner,
558
+ comment: r.comment || undefined,
559
+ privileges: parseACL(r.privileges),
560
+ };
561
+ if (schemaMap[r.schema]) {
562
+ schemaMap[r.schema].rangeTypes.push(key);
563
+ }
564
+ }
565
+ for (const m of types.multiranges || []) {
566
+ const key = `${m.schema}.${m.name}`;
567
+ typeMap[key] = {
568
+ schema: m.schema,
569
+ name: m.name,
570
+ kind: 'MULTIRANGE',
571
+ rangeType: m.range_type,
572
+ owner: m.owner,
573
+ comment: m.comment || undefined,
574
+ };
575
+ if (schemaMap[m.schema]) {
576
+ schemaMap[m.schema].multirangeTypes.push(key);
577
+ }
578
+ }
579
+
580
+ const sequenceMap = {};
581
+ for (const s of sequences) {
582
+ const key = `${s.schema}.${s.name}`;
583
+ sequenceMap[key] = {
584
+ schema: s.schema,
585
+ name: s.name,
586
+ dataType: s.data_type,
587
+ startValue: s.start_value !== null ? s.start_value : undefined,
588
+ increment: s.increment !== null ? s.increment : undefined,
589
+ minValue: s.min_value !== null ? s.min_value : undefined,
590
+ maxValue: s.max_value !== null ? s.max_value : undefined,
591
+ cache: s.cache !== null ? s.cache : undefined,
592
+ cycle: s.cycle,
593
+ ownedBy: s.owned_by || undefined,
594
+ owner: s.owner,
595
+ tablespace: s.tablespace || undefined,
596
+ comment: s.comment || undefined,
597
+ currentValue: s.current_value !== null ? s.current_value : undefined,
598
+ };
599
+ if (schemaMap[s.schema]) {
600
+ schemaMap[s.schema].sequences.push(key);
601
+ }
602
+ }
603
+
604
+ const extensionMap = {};
605
+ for (const e of extensions) {
606
+ extensionMap[e.name] = {
607
+ name: e.name,
608
+ schema: e.schema,
609
+ version: e.version,
610
+ owner: e.owner,
611
+ isRelocatable: e.is_relocatable || false,
612
+ comment: e.comment || undefined,
613
+ isAvailable: e.is_available !== false,
614
+ };
615
+ if (schemaMap[e.schema]) {
616
+ schemaMap[e.schema].extensions.push(e.name);
617
+ }
618
+ }
619
+
620
+ const policyMap = {};
621
+ for (const p of policies) {
622
+ const key = `${p.schema}.${p.table_name}.${p.name}`;
623
+ const tableKey = `${p.schema}.${p.table_name}`;
624
+ policyMap[key] = {
625
+ schema: p.schema,
626
+ name: p.name,
627
+ table: tableKey,
628
+ command: p.command,
629
+ isPermissive: p.is_permissive,
630
+ roles: p.roles || [],
631
+ using: p.using_expression || undefined,
632
+ withCheck: p.with_check_expression || undefined,
633
+ comment: p.comment || undefined,
634
+ };
635
+ if (tableMap[tableKey]) {
636
+ tableMap[tableKey].policies.push(key);
637
+ }
638
+ }
639
+
640
+ const viewMap = {};
641
+ for (const v of views) {
642
+ const key = `${v.schema}.${v.name}`;
643
+ const relOptions = parseStorageOptions(v.rel_options) || {};
644
+ viewMap[key] = {
645
+ schema: v.schema,
646
+ name: v.name,
647
+ definition: v.definition,
648
+ owner: v.owner,
649
+ checkOption: v.check_option || 'NONE',
650
+ securityBarrier: relOptions.security_barrier === 'true' || relOptions.security_barrier === true || false,
651
+ securityInvoker: relOptions.security_invoker === 'true' || relOptions.security_invoker === true || false,
652
+ isRecursive: v.is_recursive || false,
653
+ columns: v.columns || [],
654
+ privileges: v.privileges || undefined,
655
+ comment: v.comment || undefined,
656
+ };
657
+ if (schemaMap[v.schema]) {
658
+ schemaMap[v.schema].views.push(key);
659
+ }
660
+ }
661
+
662
+ const matViewMap = {};
663
+ for (const v of materializedViews) {
664
+ const key = `${v.schema}.${v.name}`;
665
+ matViewMap[key] = {
666
+ schema: v.schema,
667
+ name: v.name,
668
+ definition: v.definition,
669
+ owner: v.owner,
670
+ tablespace: v.tablespace || undefined,
671
+ storageParameters: v.storage_options ? parseStorageOptions(v.storage_options) : undefined,
672
+ isPopulated: v.is_populated !== false,
673
+ withData: v.is_populated !== false,
674
+ columns: v.columns || [],
675
+ privileges: v.privileges || undefined,
676
+ comment: v.comment || undefined,
677
+ };
678
+ if (schemaMap[v.schema]) {
679
+ schemaMap[v.schema].materializedViews.push(key);
680
+ }
681
+ }
682
+
683
+ // Statistics objects
684
+ const statisticsMap = {};
685
+ for (const s of statistics) {
686
+ const key = `${s.schema}.${s.name}`;
687
+ statisticsMap[key] = {
688
+ schema: s.schema,
689
+ name: s.name,
690
+ table: s.table_name ? `${s.table_schema}.${s.table_name}` : undefined,
691
+ kinds: s.kinds || [],
692
+ columns: s.columns || [],
693
+ definition: s.definition || undefined,
694
+ owner: s.owner,
695
+ statisticsTarget: s.statisticsTarget ?? undefined,
696
+ comment: s.comment || undefined,
697
+ size: s.size ?? undefined,
698
+ };
699
+ if (schemaMap[s.schema]) {
700
+ schemaMap[s.schema].statistics.push(key);
701
+ }
702
+ }
703
+
704
+ // Collations
705
+ const collationMap = {};
706
+ for (const c of collations) {
707
+ const key = `${c.schema}.${c.name}`;
708
+ collationMap[key] = {
709
+ schema: c.schema,
710
+ name: c.name,
711
+ provider: c.provider,
712
+ locale: c.locale || undefined,
713
+ lcCollate: c.lcCollate || undefined,
714
+ lcCtype: c.lcCtype || undefined,
715
+ encoding: c.encoding || undefined,
716
+ isDeterministic: c.isDeterministic !== false,
717
+ version: c.version || undefined,
718
+ comment: c.comment || undefined,
719
+ owner: c.owner,
720
+ };
721
+ if (schemaMap[c.schema]) {
722
+ schemaMap[c.schema].collations.push(key);
723
+ }
724
+ }
725
+
726
+ // Conversions
727
+ const conversionMap = {};
728
+ for (const c of conversions) {
729
+ const key = `${c.schema}.${c.name}`;
730
+ conversionMap[key] = {
731
+ schema: c.schema,
732
+ name: c.name,
733
+ sourceEncoding: c.source_encoding,
734
+ targetEncoding: c.target_encoding,
735
+ proc: c.proc,
736
+ isDefault: c.is_default || false,
737
+ owner: c.owner,
738
+ comment: c.comment || undefined,
739
+ };
740
+ if (schemaMap[c.schema]) {
741
+ schemaMap[c.schema].conversions.push(key);
742
+ }
743
+ }
744
+
745
+ // Operators
746
+ const operatorMap = {};
747
+ for (const o of operators) {
748
+ const key = `${o.schema}.${o.name}(${o.left_type || 'NONE'},${o.right_type || 'NONE'})`;
749
+ operatorMap[key] = {
750
+ schema: o.schema,
751
+ name: o.name,
752
+ leftType: o.left_type || undefined,
753
+ rightType: o.right_type || undefined,
754
+ resultType: o.result_type || undefined,
755
+ proc: o.proc,
756
+ canHash: o.can_hash || false,
757
+ canMerge: o.can_merge || false,
758
+ commutator: o.commutator,
759
+ negator: o.negator,
760
+ restrictFunction: o.restrict_function || undefined,
761
+ joinFunction: o.join_function || undefined,
762
+ owner: o.owner,
763
+ comment: o.comment || undefined,
764
+ };
765
+ if (schemaMap[o.schema]) {
766
+ schemaMap[o.schema].operators.push(key);
767
+ }
768
+ }
769
+
770
+ // Operator Classes
771
+ const operatorClassMap = {};
772
+ for (const oc of operatorClasses) {
773
+ const key = `${oc.schema}.${oc.name}`;
774
+ operatorClassMap[key] = {
775
+ schema: oc.schema,
776
+ name: oc.name,
777
+ family: oc.family,
778
+ inputType: oc.input_type,
779
+ isDefault: oc.is_default || false,
780
+ accessMethod: oc.access_method,
781
+ storageType: oc.storage_type || undefined,
782
+ operators: oc.operators || [],
783
+ functions: oc.functions || [],
784
+ owner: oc.owner,
785
+ comment: oc.comment || undefined,
786
+ };
787
+ if (schemaMap[oc.schema]) {
788
+ schemaMap[oc.schema].operatorClasses.push(key);
789
+ }
790
+ }
791
+
792
+ // Operator Families
793
+ const operatorFamilyMap = {};
794
+ for (const ofam of operatorFamilies) {
795
+ const key = `${ofam.schema}.${ofam.name}`;
796
+ operatorFamilyMap[key] = {
797
+ schema: ofam.schema,
798
+ name: ofam.name,
799
+ accessMethod: ofam.access_method,
800
+ owner: ofam.owner,
801
+ };
802
+ if (schemaMap[ofam.schema]) {
803
+ schemaMap[ofam.schema].operatorFamilies.push(key);
804
+ }
805
+ }
806
+
807
+ // Text Search Configs
808
+ const tscConfigs = textSearchConfigs.configs || textSearchConfigs || [];
809
+ const tscMappings = textSearchConfigs.tokenMappings || [];
810
+
811
+ const tokenMappingsMap = {};
812
+ for (const m of tscMappings) {
813
+ const key = `${m.schema}.${m.config_name}`;
814
+ if (!tokenMappingsMap[key]) tokenMappingsMap[key] = [];
815
+ tokenMappingsMap[key].push({
816
+ tokenType: m.token_type,
817
+ seq: m.seq,
818
+ dictionary: `${m.dict_schema}.${m.dictionary}`,
819
+ });
820
+ }
821
+
822
+ const textSearchConfigMap = {};
823
+ for (const tsc of tscConfigs) {
824
+ const key = `${tsc.schema}.${tsc.name}`;
825
+ textSearchConfigMap[key] = {
826
+ schema: tsc.schema,
827
+ name: tsc.name,
828
+ parser: tsc.parser,
829
+ owner: tsc.owner,
830
+ tokenMappings: tokenMappingsMap[key] || [],
831
+ comment: tsc.comment || undefined,
832
+ };
833
+ if (schemaMap[tsc.schema]) {
834
+ schemaMap[tsc.schema].textSearchConfigs.push(key);
835
+ }
836
+ }
837
+
838
+ // Text Search Dictionaries
839
+ const textSearchDictMap = {};
840
+ for (const tsd of textSearchDictionaries) {
841
+ const key = `${tsd.schema}.${tsd.name}`;
842
+ textSearchDictMap[key] = {
843
+ schema: tsd.schema,
844
+ name: tsd.name,
845
+ template: tsd.template,
846
+ options: tsd.options || undefined,
847
+ owner: tsd.owner,
848
+ comment: tsd.comment || undefined,
849
+ };
850
+ if (schemaMap[tsd.schema]) {
851
+ schemaMap[tsd.schema].textSearchDictionaries.push(key);
852
+ }
853
+ }
854
+
855
+ // Text Search Parsers
856
+ const textSearchParserMap = {};
857
+ for (const tsp of textSearchParsers) {
858
+ const key = `${tsp.schema}.${tsp.name}`;
859
+ textSearchParserMap[key] = {
860
+ schema: tsp.schema,
861
+ name: tsp.name,
862
+ start: tsp.start,
863
+ getToken: tsp.get_token,
864
+ end: tsp.end,
865
+ headline: tsp.headline || undefined,
866
+ lextypes: tsp.lextypes || undefined,
867
+ owner: tsp.owner,
868
+ comment: tsp.comment || undefined,
869
+ };
870
+ if (schemaMap[tsp.schema]) {
871
+ schemaMap[tsp.schema].textSearchParsers.push(key);
872
+ }
873
+ }
874
+
875
+ // Text Search Templates
876
+ const textSearchTemplateMap = {};
877
+ for (const tst of textSearchTemplates) {
878
+ const key = `${tst.schema}.${tst.name}`;
879
+ textSearchTemplateMap[key] = {
880
+ schema: tst.schema,
881
+ name: tst.name,
882
+ lexize: tst.lexize,
883
+ init: tst.init || undefined,
884
+ owner: tst.owner,
885
+ comment: tst.comment || undefined,
886
+ };
887
+ if (schemaMap[tst.schema]) {
888
+ schemaMap[tst.schema].textSearchTemplates.push(key);
889
+ }
890
+ }
891
+
892
+ // Foreign Data Wrappers
893
+ const fdwMap = {};
894
+ for (const fdw of foreignDataWrappers) {
895
+ fdwMap[fdw.name] = {
896
+ name: fdw.name,
897
+ handler: fdw.handler || undefined,
898
+ validator: fdw.validator || undefined,
899
+ options: redactOptions(fdw.options),
900
+ owner: fdw.owner,
901
+ privileges: fdw.privileges || undefined,
902
+ comment: fdw.comment || undefined,
903
+ };
904
+ }
905
+
906
+ // Foreign Servers
907
+ const foreignServerMap = {};
908
+ for (const fs of foreignServers) {
909
+ foreignServerMap[fs.name] = {
910
+ name: fs.name,
911
+ fdw: fs.fdw,
912
+ type: fs.type || undefined,
913
+ version: fs.version || undefined,
914
+ options: redactOptions(fs.options),
915
+ owner: fs.owner,
916
+ privileges: fs.privileges || undefined,
917
+ comment: fs.comment || undefined,
918
+ };
919
+ }
920
+
921
+ // User Mappings
922
+ const userMappingMap = {};
923
+ for (const um of userMappings) {
924
+ const key = `${um.user}@${um.server}`;
925
+ userMappingMap[key] = {
926
+ user: um.user,
927
+ server: um.server,
928
+ options: redactOptions(um.options),
929
+ };
930
+ }
931
+
932
+ // Casts
933
+ const castMap = {};
934
+ for (const c of casts) {
935
+ const key = `${c.source_type}->${c.target_type}`;
936
+ castMap[key] = {
937
+ sourceType: c.source_type,
938
+ targetType: c.target_type,
939
+ function: c.function || undefined,
940
+ context: c.context,
941
+ method: c.method,
942
+ comment: c.comment || undefined,
943
+ };
944
+ }
945
+
946
+ // Event Triggers
947
+ const eventTriggerMap = {};
948
+ for (const et of eventTriggers) {
949
+ eventTriggerMap[et.name] = {
950
+ name: et.name,
951
+ event: et.event,
952
+ function: et.function,
953
+ enabled: et.enabled,
954
+ tags: et.tags || [],
955
+ owner: et.owner,
956
+ comment: et.comment || undefined,
957
+ };
958
+ }
959
+
960
+ // Rules
961
+ const ruleMap = {};
962
+ for (const r of rules) {
963
+ const key = `${r.schema}.${r.table}.${r.name}`;
964
+ ruleMap[key] = {
965
+ schema: r.schema,
966
+ name: r.name,
967
+ table: `${r.schema}.${r.table}`,
968
+ event: r.event,
969
+ isInstead: r.is_instead || false,
970
+ isEnabled: r.is_enabled !== false,
971
+ definition: r.definition || undefined,
972
+ condition: r.qual || undefined,
973
+ comment: r.comment || undefined,
974
+ };
975
+ if (schemaMap[r.schema]) {
976
+ schemaMap[r.schema].rules.push(key);
977
+ }
978
+ }
979
+
980
+ // Roles
981
+ const roleMap = {};
982
+ for (const r of roles) {
983
+ roleMap[r.name] = {
984
+ name: r.name,
985
+ isSuperuser: r.is_superuser || false,
986
+ canCreateRole: r.can_create_role || false,
987
+ canCreateDB: r.can_create_db || false,
988
+ canLogin: r.can_login || false,
989
+ inherit: r.inherit !== false,
990
+ memberships: r.memberships || [],
991
+ comment: r.comment || undefined,
992
+ };
993
+ }
994
+
995
+ // Tablespaces
996
+ const tablespaceMap = {};
997
+ for (const ts of tablespaces) {
998
+ tablespaceMap[ts.name] = {
999
+ name: ts.name,
1000
+ owner: ts.owner,
1001
+ location: ts.location || undefined,
1002
+ acl: ts.acl || undefined,
1003
+ options: ts.options || undefined,
1004
+ };
1005
+ }
1006
+
1007
+ // Access Methods
1008
+ const accessMethodMap = {};
1009
+ for (const am of accessMethods) {
1010
+ accessMethodMap[am.name] = {
1011
+ name: am.name,
1012
+ type: am.type,
1013
+ handler: am.handler || undefined,
1014
+ };
1015
+ }
1016
+
1017
+ // Procedural Languages
1018
+ const languageMap = {};
1019
+ for (const lang of proceduralLanguages) {
1020
+ languageMap[lang.name] = {
1021
+ name: lang.name,
1022
+ isTrusted: lang.is_trusted || false,
1023
+ handler: lang.handler || undefined,
1024
+ inline: lang.inline || undefined,
1025
+ validator: lang.validator || undefined,
1026
+ owner: lang.owner,
1027
+ };
1028
+ }
1029
+
1030
+ // Default Privileges
1031
+ const defaultPrivMap = {};
1032
+ let dpIdx = 0;
1033
+ for (const dp of defaultPrivileges) {
1034
+ const key = `default_priv_${dpIdx++}`;
1035
+ defaultPrivMap[key] = {
1036
+ role: dp.role,
1037
+ schema: dp.schema || undefined,
1038
+ objectType: dp.object_type,
1039
+ acl: dp.acl,
1040
+ };
1041
+ }
1042
+
1043
+ // Databases
1044
+ const databaseMap = {};
1045
+ for (const db of databases) {
1046
+ databaseMap[db.name] = {
1047
+ name: db.name,
1048
+ owner: db.owner,
1049
+ encoding: db.encoding,
1050
+ collate: db.collate,
1051
+ ctype: db.ctype,
1052
+ isTemplate: db.is_template || false,
1053
+ allowConn: db.allow_conn !== false,
1054
+ size: db.size || undefined,
1055
+ };
1056
+ }
1057
+
1058
+ // Publications
1059
+ const publicationMap = {};
1060
+ for (const pub of publications) {
1061
+ publicationMap[pub.name] = {
1062
+ name: pub.name,
1063
+ owner: pub.owner,
1064
+ allTables: pub.all_tables || false,
1065
+ insert: pub.insert !== false,
1066
+ update: pub.update !== false,
1067
+ delete: pub.delete !== false,
1068
+ truncate: pub.truncate !== false,
1069
+ viaRoot: pub.via_root || false,
1070
+ tables: pub.tables || [],
1071
+ schemas: pub.schemas || [],
1072
+ comment: pub.comment || undefined,
1073
+ };
1074
+ }
1075
+
1076
+ // Subscriptions (with PII redaction)
1077
+ const subscriptionMap = {};
1078
+ for (const sub of subscriptions) {
1079
+ subscriptionMap[sub.name] = {
1080
+ name: sub.name,
1081
+ conninfo: redactConnectionString(sub.conninfo),
1082
+ slotName: sub.slot_name || undefined,
1083
+ publications: sub.publications || [],
1084
+ enabled: sub.enabled !== false,
1085
+ syncCommit: sub.sync_commit || undefined,
1086
+ binaryTransfer: sub.binary_transfer || false,
1087
+ streaming: sub.streaming === 'p' ? 'parallel' : (sub.streaming === 't' ? 'on' : 'off'),
1088
+ twoPhase: sub.two_phase || false,
1089
+ disableOnError: sub.disable_on_error || false,
1090
+ origin: sub.origin || 'any',
1091
+ owner: sub.owner,
1092
+ comment: sub.comment || undefined,
1093
+ };
1094
+ }
1095
+
1096
+ // Distribute comments to schemas
1097
+ const commentsMap = {};
1098
+ for (const [key, comment] of Object.entries(comments)) {
1099
+ commentsMap[key] = comment;
1100
+ const parts = key.split('.');
1101
+ if (parts.length >= 1) {
1102
+ const schemaName = parts[0];
1103
+ if (schemaMap[schemaName]) {
1104
+ schemaMap[schemaName].comments[key] = comment;
1105
+ }
1106
+ }
1107
+ }
1108
+
1109
+ // Distribute grants to schemas
1110
+ const grantList = [];
1111
+ for (const g of grants) {
1112
+ grantList.push({
1113
+ schema: g.schema,
1114
+ object: g.object,
1115
+ objectType: g.object_type,
1116
+ privilege: g.privilege,
1117
+ grantee: g.grantee,
1118
+ grantor: g.grantor,
1119
+ isGrantable: g.is_grantable || false,
1120
+ column: g.column || undefined,
1121
+ });
1122
+ if (schemaMap[g.schema]) {
1123
+ schemaMap[g.schema].grants.push(g.object);
1124
+ }
1125
+ if (g.objectType === 'TABLE' || g.object_type === 'TABLE' || g.object_type === 'PARTITIONED TABLE') {
1126
+ const tableKey = `${g.schema}.${g.object}`;
1127
+ if (tableMap[tableKey]) {
1128
+ tableMap[tableKey].privileges.push({
1129
+ privilege: g.privilege,
1130
+ grantee: g.grantee,
1131
+ grantor: g.grantor,
1132
+ isGrantable: g.is_grantable || false,
1133
+ });
1134
+ }
1135
+ }
1136
+ }
1137
+
1138
+ // Checksum calculation
1139
+ const canonicalObj = {
1140
+ schemas: schemaMap,
1141
+ tables: tableMap,
1142
+ views: viewMap,
1143
+ materializedViews: matViewMap,
1144
+ indexes: indexMap,
1145
+ functions: functionMap,
1146
+ triggers: triggerMap,
1147
+ types: typeMap,
1148
+ sequences: sequenceMap,
1149
+ constraints: constraintMap,
1150
+ policies: policyMap,
1151
+ extensions: extensionMap,
1152
+ statistics: statisticsMap,
1153
+ collations: collationMap,
1154
+ conversions: conversionMap,
1155
+ operators: operatorMap,
1156
+ operatorClasses: operatorClassMap,
1157
+ operatorFamilies: operatorFamilyMap,
1158
+ textSearchConfigs: textSearchConfigMap,
1159
+ textSearchDictionaries: textSearchDictMap,
1160
+ textSearchParsers: textSearchParserMap,
1161
+ textSearchTemplates: textSearchTemplateMap,
1162
+ rules: ruleMap,
1163
+ publications: publicationMap,
1164
+ subscriptions: subscriptionMap,
1165
+ };
1166
+ const sortedObj = {};
1167
+ for (const key of Object.keys(canonicalObj).sort()) {
1168
+ sortedObj[key] = canonicalObj[key];
1169
+ }
1170
+ const canonical = JSON.stringify(sortedObj);
1171
+ const checksum = crypto.createHash('sha256').update(canonical).digest('hex');
1172
+
1173
+ return {
1174
+ version: typeof version === 'object' ? version : { numeric: version, string: version.toString() },
1175
+ timestamp: new Date().toISOString(),
1176
+ checksum,
1177
+ database: database || undefined,
1178
+ schemas: schemaMap,
1179
+ tables: tableMap,
1180
+ views: viewMap,
1181
+ materializedViews: matViewMap,
1182
+ indexes: indexMap,
1183
+ functions: functionMap,
1184
+ procedures: Object.fromEntries(Object.entries(functionMap).filter(([k, v]) => v.kind === 'PROCEDURE')),
1185
+ aggregates: Object.fromEntries(Object.entries(functionMap).filter(([k, v]) => v.kind === 'AGGREGATE')),
1186
+ triggers: triggerMap,
1187
+ eventTriggers: eventTriggerMap,
1188
+ policies: policyMap,
1189
+ types: typeMap,
1190
+ sequences: sequenceMap,
1191
+ extensions: extensionMap,
1192
+ constraints: constraintMap,
1193
+ statistics: statisticsMap,
1194
+ collations: collationMap,
1195
+ conversions: conversionMap,
1196
+ operators: operatorMap,
1197
+ operatorClasses: operatorClassMap,
1198
+ operatorFamilies: operatorFamilyMap,
1199
+ textSearchConfigs: textSearchConfigMap,
1200
+ textSearchDictionaries: textSearchDictMap,
1201
+ textSearchParsers: textSearchParserMap,
1202
+ textSearchTemplates: textSearchTemplateMap,
1203
+ foreignDataWrappers: fdwMap,
1204
+ foreignServers: foreignServerMap,
1205
+ userMappings: userMappingMap,
1206
+ casts: castMap,
1207
+ rules: ruleMap,
1208
+ roles: roleMap,
1209
+ tablespaces: tablespaceMap,
1210
+ accessMethods: accessMethodMap,
1211
+ languages: languageMap,
1212
+ defaultPrivileges: defaultPrivMap,
1213
+ databases: databaseMap,
1214
+ publications: publicationMap,
1215
+ subscriptions: subscriptionMap,
1216
+ comments: commentsMap,
1217
+ grants: grantList,
1218
+ };
1219
+ }
1220
+
1221
+ function parseStorageOptions(options) {
1222
+ if (!options) return undefined;
1223
+ const result = {};
1224
+ for (const opt of options) {
1225
+ const match = opt.match(/^([^=]+)=(.*)$/);
1226
+ if (match) {
1227
+ result[match[1]] = isNaN(match[2]) ? match[2] : Number(match[2]);
1228
+ }
1229
+ }
1230
+ return result;
1231
+ }
1232
+
1233
+ function getColumnNamesFromIndices(tableRef, indices, tableMap) {
1234
+ if (!tableRef || !indices) return undefined;
1235
+ const table = Object.values(tableMap).find(t => `${t.schema}.${t.name}` === tableRef);
1236
+ if (!table) return undefined;
1237
+ return indices.map(idx => table.columns.find(c => c.ordinalPosition === idx)?.name).filter(Boolean);
1238
+ }
1239
+
1240
+ function redactConnectionString(connStr) {
1241
+ if (!connStr) return connStr;
1242
+ return connStr.replace(/password=[^'"\s]+/gi, 'password=[REDACTED]');
1243
+ }
1244
+
1245
+ function redactOptions(options) {
1246
+ if (!options) return options;
1247
+ if (typeof options === 'string') {
1248
+ return options.replace(/password=[^'"\s]+/gi, 'password=[REDACTED]');
1249
+ }
1250
+ if (Array.isArray(options)) {
1251
+ return options.map(opt => {
1252
+ if (typeof opt === 'string' && opt.toLowerCase().includes('password')) {
1253
+ return opt.replace(/password=\S+/gi, 'password=[REDACTED]');
1254
+ }
1255
+ return opt;
1256
+ });
1257
+ }
1258
+ if (typeof options === 'object') {
1259
+ const redacted = { ...options };
1260
+ for (const key of Object.keys(redacted)) {
1261
+ if (key.toLowerCase().includes('password') || key.toLowerCase().includes('secret')) {
1262
+ redacted[key] = '[REDACTED]';
1263
+ }
1264
+ }
1265
+ return redacted;
1266
+ }
1267
+ return options;
1268
+ }
1269
+
1270
+ function parseACL(acl) {
1271
+ if (!acl) return [];
1272
+ if (typeof acl === 'string') {
1273
+ return acl.replace(/[{}"]/g, '').split(',').filter(Boolean);
1274
+ }
1275
+ if (Array.isArray(acl)) {
1276
+ return acl.map(a => typeof a === 'string' ? a.replace(/[{}"]/g, '') : a).filter(Boolean);
1277
+ }
1278
+ return [];
1279
+ }
1280
+
1281
+ export function normalizeSchema(raw) {
1282
+ return translateSnapshot(raw);
1283
+ }