relq 1.0.4 → 1.0.6

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 (84) hide show
  1. package/dist/cjs/cli/commands/add.cjs +252 -12
  2. package/dist/cjs/cli/commands/commit.cjs +12 -1
  3. package/dist/cjs/cli/commands/export.cjs +25 -19
  4. package/dist/cjs/cli/commands/import.cjs +219 -100
  5. package/dist/cjs/cli/commands/init.cjs +86 -14
  6. package/dist/cjs/cli/commands/pull.cjs +104 -23
  7. package/dist/cjs/cli/commands/push.cjs +38 -3
  8. package/dist/cjs/cli/index.cjs +9 -1
  9. package/dist/cjs/cli/utils/ast/codegen/builder.cjs +297 -0
  10. package/dist/cjs/cli/utils/ast/codegen/constraints.cjs +185 -0
  11. package/dist/cjs/cli/utils/ast/codegen/defaults.cjs +311 -0
  12. package/dist/cjs/cli/utils/ast/codegen/index.cjs +24 -0
  13. package/dist/cjs/cli/utils/ast/codegen/type-map.cjs +116 -0
  14. package/dist/cjs/cli/utils/ast/codegen/utils.cjs +69 -0
  15. package/dist/cjs/cli/utils/ast/index.cjs +19 -0
  16. package/dist/cjs/cli/utils/ast/transformer/helpers.cjs +154 -0
  17. package/dist/cjs/cli/utils/ast/transformer/index.cjs +25 -0
  18. package/dist/cjs/cli/utils/ast/types.cjs +2 -0
  19. package/dist/cjs/cli/utils/ast-codegen.cjs +949 -0
  20. package/dist/cjs/cli/utils/ast-transformer.cjs +916 -0
  21. package/dist/cjs/cli/utils/change-tracker.cjs +50 -1
  22. package/dist/cjs/cli/utils/cli-utils.cjs +151 -0
  23. package/dist/cjs/cli/utils/fast-introspect.cjs +149 -23
  24. package/dist/cjs/cli/utils/pg-parser.cjs +1 -0
  25. package/dist/cjs/cli/utils/repo-manager.cjs +121 -4
  26. package/dist/cjs/cli/utils/schema-comparator.cjs +98 -14
  27. package/dist/cjs/cli/utils/schema-introspect.cjs +56 -19
  28. package/dist/cjs/cli/utils/snapshot-manager.cjs +0 -1
  29. package/dist/cjs/cli/utils/sql-generator.cjs +353 -64
  30. package/dist/cjs/cli/utils/type-generator.cjs +114 -15
  31. package/dist/cjs/core/relq-client.cjs +22 -6
  32. package/dist/cjs/schema-definition/column-types.cjs +150 -13
  33. package/dist/cjs/schema-definition/defaults.cjs +72 -0
  34. package/dist/cjs/schema-definition/index.cjs +15 -1
  35. package/dist/cjs/schema-definition/introspection.cjs +7 -3
  36. package/dist/cjs/schema-definition/pg-relations.cjs +169 -0
  37. package/dist/cjs/schema-definition/pg-view.cjs +30 -0
  38. package/dist/cjs/schema-definition/table-definition.cjs +110 -4
  39. package/dist/cjs/types/config-types.cjs +13 -4
  40. package/dist/cjs/utils/aws-dsql.cjs +177 -0
  41. package/dist/config.d.ts +146 -1
  42. package/dist/esm/cli/commands/add.js +250 -13
  43. package/dist/esm/cli/commands/commit.js +12 -1
  44. package/dist/esm/cli/commands/export.js +25 -19
  45. package/dist/esm/cli/commands/import.js +221 -102
  46. package/dist/esm/cli/commands/init.js +86 -14
  47. package/dist/esm/cli/commands/pull.js +106 -25
  48. package/dist/esm/cli/commands/push.js +39 -4
  49. package/dist/esm/cli/index.js +9 -1
  50. package/dist/esm/cli/utils/ast/codegen/builder.js +291 -0
  51. package/dist/esm/cli/utils/ast/codegen/constraints.js +176 -0
  52. package/dist/esm/cli/utils/ast/codegen/defaults.js +305 -0
  53. package/dist/esm/cli/utils/ast/codegen/index.js +6 -0
  54. package/dist/esm/cli/utils/ast/codegen/type-map.js +111 -0
  55. package/dist/esm/cli/utils/ast/codegen/utils.js +60 -0
  56. package/dist/esm/cli/utils/ast/index.js +3 -0
  57. package/dist/esm/cli/utils/ast/transformer/helpers.js +141 -0
  58. package/dist/esm/cli/utils/ast/transformer/index.js +2 -0
  59. package/dist/esm/cli/utils/ast/types.js +1 -0
  60. package/dist/esm/cli/utils/ast-codegen.js +945 -0
  61. package/dist/esm/cli/utils/ast-transformer.js +907 -0
  62. package/dist/esm/cli/utils/change-tracker.js +50 -1
  63. package/dist/esm/cli/utils/cli-utils.js +147 -0
  64. package/dist/esm/cli/utils/fast-introspect.js +149 -23
  65. package/dist/esm/cli/utils/pg-parser.js +1 -0
  66. package/dist/esm/cli/utils/repo-manager.js +114 -4
  67. package/dist/esm/cli/utils/schema-comparator.js +98 -14
  68. package/dist/esm/cli/utils/schema-introspect.js +56 -19
  69. package/dist/esm/cli/utils/snapshot-manager.js +0 -1
  70. package/dist/esm/cli/utils/sql-generator.js +353 -64
  71. package/dist/esm/cli/utils/type-generator.js +114 -15
  72. package/dist/esm/core/relq-client.js +23 -7
  73. package/dist/esm/schema-definition/column-types.js +147 -12
  74. package/dist/esm/schema-definition/defaults.js +69 -0
  75. package/dist/esm/schema-definition/index.js +3 -0
  76. package/dist/esm/schema-definition/introspection.js +7 -3
  77. package/dist/esm/schema-definition/pg-relations.js +161 -0
  78. package/dist/esm/schema-definition/pg-view.js +24 -0
  79. package/dist/esm/schema-definition/table-definition.js +110 -4
  80. package/dist/esm/types/config-types.js +12 -4
  81. package/dist/esm/utils/aws-dsql.js +139 -0
  82. package/dist/index.d.ts +159 -1
  83. package/dist/schema-builder.d.ts +1314 -32
  84. package/package.json +1 -1
@@ -41,6 +41,12 @@ exports.getFetchHead = getFetchHead;
41
41
  exports.setFetchHead = setFetchHead;
42
42
  exports.generateHash = generateHash;
43
43
  exports.shortHash = shortHash;
44
+ exports.getConnectionId = getConnectionId;
45
+ exports.getConnectionLabel = getConnectionLabel;
46
+ exports.getRemoteRef = getRemoteRef;
47
+ exports.isCommitSyncedWith = isCommitSyncedWith;
48
+ exports.markCommitAsPushed = markCommitAsPushed;
49
+ exports.markCommitAsPulled = markCommitAsPulled;
44
50
  exports.saveCommit = saveCommit;
45
51
  exports.loadCommit = loadCommit;
46
52
  exports.getAllCommits = getAllCommits;
@@ -65,6 +71,7 @@ exports.stageChanges = stageChanges;
65
71
  exports.unstageChanges = unstageChanges;
66
72
  exports.clearWorkingState = clearWorkingState;
67
73
  exports.getStagedChanges = getStagedChanges;
74
+ exports.cleanupStagedChanges = cleanupStagedChanges;
68
75
  exports.getUnstagedChanges = getUnstagedChanges;
69
76
  exports.hasUncommittedChanges = hasUncommittedChanges;
70
77
  exports.ensureRemoteTable = ensureRemoteTable;
@@ -147,6 +154,81 @@ function generateHash(schema) {
147
154
  function shortHash(hash) {
148
155
  return hash.substring(0, 7);
149
156
  }
157
+ function getConnectionId(connection) {
158
+ const parts = [
159
+ connection.host || 'localhost',
160
+ String(connection.port || 5432),
161
+ connection.database || 'postgres',
162
+ connection.user || 'postgres',
163
+ ];
164
+ return crypto.createHash('sha256').update(parts.join('/')).digest('hex').substring(0, 12);
165
+ }
166
+ function getConnectionLabel(connection) {
167
+ const user = connection.user || 'postgres';
168
+ const host = connection.host || 'localhost';
169
+ const db = connection.database || 'postgres';
170
+ return `${user}@${host}/${db}`;
171
+ }
172
+ function getRemoteRef(connection) {
173
+ return {
174
+ id: getConnectionId(connection),
175
+ label: getConnectionLabel(connection),
176
+ };
177
+ }
178
+ function isCommitSyncedWith(commit, connection) {
179
+ const connectionId = getConnectionId(connection);
180
+ if (commit.remotes?.pushed?.some((r) => r.id === connectionId)) {
181
+ return { synced: true, type: 'pushed' };
182
+ }
183
+ if (commit.remotes?.pulled?.some((r) => r.id === connectionId)) {
184
+ return { synced: true, type: 'pulled' };
185
+ }
186
+ if (commit.message?.startsWith('pull: sync from ')) {
187
+ const pullTarget = commit.message.replace('pull: sync from ', '');
188
+ const currentLabel = getConnectionLabel(connection);
189
+ const legacyLabel = `${connection.user}@${connection.host}:${connection.port || 5432}/${connection.database}`;
190
+ if (pullTarget === currentLabel || pullTarget === legacyLabel) {
191
+ return { synced: true, type: 'pulled' };
192
+ }
193
+ }
194
+ return { synced: false, type: null };
195
+ }
196
+ function markCommitAsPushed(commitHash, connection, projectRoot = process.cwd()) {
197
+ const commitPath = path.join(projectRoot, RELQ_DIR, COMMITS_DIR, `${commitHash}.json`);
198
+ if (!fs.existsSync(commitPath)) {
199
+ return;
200
+ }
201
+ const commit = JSON.parse(fs.readFileSync(commitPath, 'utf-8'));
202
+ const remoteRef = getRemoteRef(connection);
203
+ if (!commit.remotes) {
204
+ commit.remotes = {};
205
+ }
206
+ if (!commit.remotes.pushed) {
207
+ commit.remotes.pushed = [];
208
+ }
209
+ if (!commit.remotes.pushed.some((r) => r.id === remoteRef.id)) {
210
+ commit.remotes.pushed.push(remoteRef);
211
+ }
212
+ fs.writeFileSync(commitPath, JSON.stringify(commit, null, 2), 'utf-8');
213
+ }
214
+ function markCommitAsPulled(commitHash, connection, projectRoot = process.cwd()) {
215
+ const commitPath = path.join(projectRoot, RELQ_DIR, COMMITS_DIR, `${commitHash}.json`);
216
+ if (!fs.existsSync(commitPath)) {
217
+ return;
218
+ }
219
+ const commit = JSON.parse(fs.readFileSync(commitPath, 'utf-8'));
220
+ const remoteRef = getRemoteRef(connection);
221
+ if (!commit.remotes) {
222
+ commit.remotes = {};
223
+ }
224
+ if (!commit.remotes.pulled) {
225
+ commit.remotes.pulled = [];
226
+ }
227
+ if (!commit.remotes.pulled.some((r) => r.id === remoteRef.id)) {
228
+ commit.remotes.pulled.push(remoteRef);
229
+ }
230
+ fs.writeFileSync(commitPath, JSON.stringify(commit, null, 2), 'utf-8');
231
+ }
150
232
  function saveCommit(commit, projectRoot = process.cwd()) {
151
233
  const commitsPath = path.join(projectRoot, RELQ_DIR, COMMITS_DIR);
152
234
  const commitPath = path.join(commitsPath, `${commit.hash}.json`);
@@ -349,7 +431,9 @@ function stageChanges(patterns, projectRoot = process.cwd()) {
349
431
  remaining.push(change);
350
432
  }
351
433
  }
352
- state.staged.push(...staged);
434
+ const newKeys = new Set(staged.map(c => `${c.objectType}:${c.objectName}:${c.parentName || ''}`));
435
+ const keptStaged = state.staged.filter(c => !newKeys.has(`${c.objectType}:${c.objectName}:${c.parentName || ''}`));
436
+ state.staged = [...keptStaged, ...staged];
353
437
  state.unstaged = remaining;
354
438
  state.timestamp = new Date().toISOString();
355
439
  saveWorkingState(state, projectRoot);
@@ -390,6 +474,18 @@ function getStagedChanges(projectRoot = process.cwd()) {
390
474
  const state = loadWorkingState(projectRoot);
391
475
  return state?.staged || [];
392
476
  }
477
+ function cleanupStagedChanges(validChanges, projectRoot = process.cwd()) {
478
+ const state = getOrCreateWorkingState(projectRoot);
479
+ const validKeys = new Set(validChanges.map(c => `${c.objectType}:${c.objectName}:${c.parentName || ''}`));
480
+ const originalCount = state.staged.length;
481
+ state.staged = state.staged.filter(c => validKeys.has(`${c.objectType}:${c.objectName}:${c.parentName || ''}`));
482
+ const removedCount = originalCount - state.staged.length;
483
+ if (removedCount > 0) {
484
+ state.timestamp = new Date().toISOString();
485
+ saveWorkingState(state, projectRoot);
486
+ }
487
+ return removedCount;
488
+ }
393
489
  function getUnstagedChanges(projectRoot = process.cwd()) {
394
490
  const state = loadWorkingState(projectRoot);
395
491
  return state?.unstaged || [];
@@ -468,7 +564,7 @@ async function getRemoteHead(connection) {
468
564
  const commits = await fetchRemoteCommits(connection, 1);
469
565
  return commits.length > 0 ? commits[0].hash : null;
470
566
  }
471
- async function pushCommit(connection, commit) {
567
+ async function pushCommit(connection, commit, projectRoot = process.cwd()) {
472
568
  const { Pool } = await Promise.resolve().then(() => __importStar(require("../../addon/pg/index.cjs")));
473
569
  const pool = new Pool({
474
570
  host: connection.host,
@@ -480,6 +576,27 @@ async function pushCommit(connection, commit) {
480
576
  ssl: connection.ssl,
481
577
  });
482
578
  try {
579
+ let schemaSnapshot = null;
580
+ if (commit.schema) {
581
+ schemaSnapshot = commit.schema;
582
+ }
583
+ else {
584
+ schemaSnapshot = loadSnapshot(projectRoot);
585
+ }
586
+ if (!schemaSnapshot) {
587
+ throw new Error(`Cannot push commit ${commit.hash}: No schema snapshot available. Run 'relq pull' first.`);
588
+ }
589
+ const stats = commit.stats || {
590
+ tables: 0,
591
+ columns: 0,
592
+ indexes: 0,
593
+ enums: 0,
594
+ domains: 0,
595
+ compositeTypes: 0,
596
+ sequences: 0,
597
+ functions: 0,
598
+ triggers: 0,
599
+ };
483
600
  await pool.query(`
484
601
  INSERT INTO _relq_commits (hash, parent_hash, author, message, schema_snapshot, stats)
485
602
  VALUES ($1, $2, $3, $4, $5, $6)
@@ -489,8 +606,8 @@ async function pushCommit(connection, commit) {
489
606
  commit.parentHash,
490
607
  commit.author,
491
608
  commit.message,
492
- JSON.stringify(commit.schema),
493
- JSON.stringify(commit.stats),
609
+ JSON.stringify(schemaSnapshot),
610
+ JSON.stringify(stats),
494
611
  ]);
495
612
  }
496
613
  finally {
@@ -198,6 +198,7 @@ function compareTables(before, after) {
198
198
  changes.push(...compareConstraints(beforeTable.constraints || [], table.constraints || [], name));
199
199
  changes.push(...compareTableComments(beforeTable, table, name));
200
200
  changes.push(...compareColumnComments(beforeTable.columns, table.columns, name));
201
+ changes.push(...compareIndexComments(beforeTable.indexes || [], table.indexes || [], name));
201
202
  changes.push(...comparePartitions(beforeTable, table, name));
202
203
  }
203
204
  }
@@ -231,21 +232,46 @@ function tableToChangeData(table) {
231
232
  }
232
233
  function compareColumns(before, after, tableName) {
233
234
  const changes = [];
234
- const beforeMap = new Map(before.map(c => [c.name, c]));
235
- const afterMap = new Map(after.map(c => [c.name, c]));
236
- for (const [name, col] of afterMap) {
237
- if (!beforeMap.has(name)) {
235
+ const beforeByName = new Map(before.map(c => [c.name, c]));
236
+ const afterByName = new Map(after.map(c => [c.name, c]));
237
+ const beforeByTrackingId = new Map(before.filter(c => c.trackingId).map(c => [c.trackingId, c]));
238
+ const afterByTrackingId = new Map(after.filter(c => c.trackingId).map(c => [c.trackingId, c]));
239
+ const processedBefore = new Set();
240
+ const processedAfter = new Set();
241
+ for (const [trackingId, afterCol] of afterByTrackingId) {
242
+ const beforeCol = beforeByTrackingId.get(trackingId);
243
+ if (beforeCol && beforeCol.name !== afterCol.name) {
244
+ changes.push((0, change_tracker_1.createChange)('RENAME', 'COLUMN', afterCol.name, columnToChangeData(beforeCol), columnToChangeData(afterCol), tableName));
245
+ processedBefore.add(beforeCol.name);
246
+ processedAfter.add(afterCol.name);
247
+ }
248
+ else if (beforeCol && beforeCol.name === afterCol.name) {
249
+ if (hasColumnChanged(beforeCol, afterCol)) {
250
+ changes.push((0, change_tracker_1.createChange)('ALTER', 'COLUMN', afterCol.name, columnToChangeData(beforeCol), columnToChangeData(afterCol), tableName));
251
+ }
252
+ processedBefore.add(beforeCol.name);
253
+ processedAfter.add(afterCol.name);
254
+ }
255
+ }
256
+ for (const [name, col] of afterByName) {
257
+ if (processedAfter.has(name))
258
+ continue;
259
+ if (!beforeByName.has(name)) {
238
260
  changes.push((0, change_tracker_1.createChange)('CREATE', 'COLUMN', name, null, columnToChangeData(col), tableName));
239
261
  }
240
262
  else {
241
- const beforeCol = beforeMap.get(name);
242
- if (hasColumnChanged(beforeCol, col)) {
263
+ const beforeCol = beforeByName.get(name);
264
+ if (!processedBefore.has(name) && hasColumnChanged(beforeCol, col)) {
243
265
  changes.push((0, change_tracker_1.createChange)('ALTER', 'COLUMN', name, columnToChangeData(beforeCol), columnToChangeData(col), tableName));
244
266
  }
267
+ processedBefore.add(name);
245
268
  }
269
+ processedAfter.add(name);
246
270
  }
247
- for (const [name, col] of beforeMap) {
248
- if (!afterMap.has(name)) {
271
+ for (const [name, col] of beforeByName) {
272
+ if (processedBefore.has(name))
273
+ continue;
274
+ if (!afterByName.has(name)) {
249
275
  changes.push((0, change_tracker_1.createChange)('DROP', 'COLUMN', name, columnToChangeData(col), null, tableName));
250
276
  }
251
277
  }
@@ -262,6 +288,7 @@ function columnToChangeData(col) {
262
288
  maxLength: col.maxLength,
263
289
  precision: col.precision,
264
290
  scale: col.scale,
291
+ trackingId: col.trackingId,
265
292
  };
266
293
  }
267
294
  function normalizeDataType(type) {
@@ -328,10 +355,28 @@ function hasColumnChanged(before, after) {
328
355
  }
329
356
  function compareIndexes(before, after, tableName) {
330
357
  const changes = [];
331
- const beforeMap = new Map(before.map(i => [i.name, i]));
332
- const afterMap = new Map(after.map(i => [i.name, i]));
333
- for (const [name, idx] of afterMap) {
334
- if (!beforeMap.has(name) && !idx.isPrimary) {
358
+ const beforeByName = new Map(before.map(i => [i.name, i]));
359
+ const afterByName = new Map(after.map(i => [i.name, i]));
360
+ const beforeByTrackingId = new Map(before.filter(i => i.trackingId).map(i => [i.trackingId, i]));
361
+ const afterByTrackingId = new Map(after.filter(i => i.trackingId).map(i => [i.trackingId, i]));
362
+ const processedBefore = new Set();
363
+ const processedAfter = new Set();
364
+ for (const [trackingId, afterIdx] of afterByTrackingId) {
365
+ const beforeIdx = beforeByTrackingId.get(trackingId);
366
+ if (beforeIdx && beforeIdx.name !== afterIdx.name && !beforeIdx.isPrimary && !afterIdx.isPrimary) {
367
+ changes.push((0, change_tracker_1.createChange)('RENAME', 'INDEX', afterIdx.name, { name: beforeIdx.name, tableName, columns: beforeIdx.columns }, { name: afterIdx.name, tableName, columns: afterIdx.columns, isUnique: afterIdx.isUnique, type: afterIdx.type }));
368
+ processedBefore.add(beforeIdx.name);
369
+ processedAfter.add(afterIdx.name);
370
+ }
371
+ else if (beforeIdx && beforeIdx.name === afterIdx.name) {
372
+ processedBefore.add(beforeIdx.name);
373
+ processedAfter.add(afterIdx.name);
374
+ }
375
+ }
376
+ for (const [name, idx] of afterByName) {
377
+ if (processedAfter.has(name) || idx.isPrimary)
378
+ continue;
379
+ if (!beforeByName.has(name)) {
335
380
  changes.push((0, change_tracker_1.createChange)('CREATE', 'INDEX', name, null, {
336
381
  name: idx.name,
337
382
  tableName,
@@ -340,9 +385,12 @@ function compareIndexes(before, after, tableName) {
340
385
  type: idx.type,
341
386
  }));
342
387
  }
388
+ processedAfter.add(name);
343
389
  }
344
- for (const [name, idx] of beforeMap) {
345
- if (!afterMap.has(name) && !idx.isPrimary) {
390
+ for (const [name, idx] of beforeByName) {
391
+ if (processedBefore.has(name) || idx.isPrimary)
392
+ continue;
393
+ if (!afterByName.has(name)) {
346
394
  changes.push((0, change_tracker_1.createChange)('DROP', 'INDEX', name, {
347
395
  name: idx.name,
348
396
  tableName,
@@ -508,6 +556,42 @@ function compareColumnComments(before, after, tableName) {
508
556
  }
509
557
  return changes;
510
558
  }
559
+ function compareIndexComments(before, after, tableName) {
560
+ const changes = [];
561
+ const beforeMap = new Map(before.map(i => [i.name, i]));
562
+ const afterMap = new Map(after.map(i => [i.name, i]));
563
+ for (const [name, afterIdx] of afterMap) {
564
+ const beforeIdx = beforeMap.get(name);
565
+ const afterComment = afterIdx.comment || null;
566
+ const beforeComment = beforeIdx ? beforeIdx.comment || null : null;
567
+ if (afterComment && !beforeComment) {
568
+ changes.push((0, change_tracker_1.createChange)('CREATE', 'INDEX_COMMENT', name, null, {
569
+ tableName,
570
+ indexName: name,
571
+ comment: afterComment,
572
+ }, tableName));
573
+ }
574
+ else if (!afterComment && beforeComment) {
575
+ changes.push((0, change_tracker_1.createChange)('DROP', 'INDEX_COMMENT', name, {
576
+ tableName,
577
+ indexName: name,
578
+ comment: beforeComment,
579
+ }, null, tableName));
580
+ }
581
+ else if (afterComment && beforeComment && afterComment !== beforeComment) {
582
+ changes.push((0, change_tracker_1.createChange)('ALTER', 'INDEX_COMMENT', name, {
583
+ tableName,
584
+ indexName: name,
585
+ comment: beforeComment,
586
+ }, {
587
+ tableName,
588
+ indexName: name,
589
+ comment: afterComment,
590
+ }, tableName));
591
+ }
592
+ }
593
+ return changes;
594
+ }
511
595
  function comparePartitions(before, after, tableName) {
512
596
  const changes = [];
513
597
  const beforePartitioned = before.isPartitioned;
@@ -35,6 +35,22 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.introspectDatabase = introspectDatabase;
37
37
  exports.tableHasData = tableHasData;
38
+ function mapInternalToFriendlyType(internalType) {
39
+ const typeMap = {
40
+ 'int2': 'smallint',
41
+ 'int4': 'integer',
42
+ 'int8': 'bigint',
43
+ 'float4': 'real',
44
+ 'float8': 'double precision',
45
+ 'bool': 'boolean',
46
+ 'timestamptz': 'timestamp with time zone',
47
+ 'timetz': 'time with time zone',
48
+ 'varchar': 'character varying',
49
+ 'bpchar': 'character',
50
+ 'varbit': 'bit varying',
51
+ };
52
+ return typeMap[internalType] || internalType;
53
+ }
38
54
  async function introspectDatabase(connection, onProgress, options) {
39
55
  const { includeFunctions = false, includeTriggers = false } = options || {};
40
56
  const { Pool } = await Promise.resolve().then(() => __importStar(require("../../addon/pg/index.cjs")));
@@ -54,7 +70,8 @@ async function introspectDatabase(connection, onProgress, options) {
54
70
  SELECT
55
71
  t.table_name,
56
72
  t.table_schema,
57
- (SELECT reltuples::bigint FROM pg_class WHERE relname = t.table_name) as row_count
73
+ (SELECT reltuples::bigint FROM pg_class WHERE relname = t.table_name) as row_count,
74
+ obj_description((quote_ident(t.table_schema) || '.' || quote_ident(t.table_name))::regclass, 'pg_class') as table_comment
58
75
  FROM information_schema.tables t
59
76
  WHERE t.table_schema = 'public'
60
77
  AND t.table_type = 'BASE TABLE'
@@ -111,11 +128,17 @@ async function introspectDatabase(connection, onProgress, options) {
111
128
  WHERE tc.table_name = $1 AND tc.constraint_type = 'PRIMARY KEY'
112
129
  ) pk ON pk.column_name = c.column_name
113
130
  LEFT JOIN (
131
+ -- Only mark as unique if column is the ONLY column in the unique constraint
132
+ -- (not part of a composite unique constraint)
114
133
  SELECT kcu.column_name, true as is_unique
115
134
  FROM information_schema.table_constraints tc
116
135
  JOIN information_schema.key_column_usage kcu
117
136
  ON tc.constraint_name = kcu.constraint_name
118
137
  WHERE tc.table_name = $1 AND tc.constraint_type = 'UNIQUE'
138
+ AND (
139
+ SELECT COUNT(*) FROM information_schema.key_column_usage kcu2
140
+ WHERE kcu2.constraint_name = tc.constraint_name
141
+ ) = 1
119
142
  ) uq ON uq.column_name = c.column_name
120
143
  LEFT JOIN (
121
144
  SELECT
@@ -132,29 +155,41 @@ async function introspectDatabase(connection, onProgress, options) {
132
155
  WHERE c.table_name = $1 AND c.table_schema = $2
133
156
  ORDER BY c.ordinal_position;
134
157
  `, [tableName, tableSchema]);
135
- const columns = columnsResult.rows.map(col => ({
136
- name: col.column_name,
137
- dataType: col.udt_name || col.data_type,
138
- isNullable: col.is_nullable === 'YES',
139
- defaultValue: col.column_default,
140
- isPrimaryKey: col.is_primary_key,
141
- isUnique: col.is_unique,
142
- maxLength: col.character_maximum_length,
143
- precision: col.numeric_precision,
144
- scale: col.numeric_scale,
145
- references: col.foreign_table ? {
146
- table: col.foreign_table,
147
- column: col.foreign_column,
148
- } : null,
149
- comment: col.column_comment || undefined,
150
- }));
158
+ const columns = columnsResult.rows.map(col => {
159
+ let dataType = col.data_type;
160
+ if (dataType === 'ARRAY' && col.udt_name) {
161
+ const baseType = col.udt_name.startsWith('_') ? col.udt_name.slice(1) : col.udt_name;
162
+ const friendlyBase = mapInternalToFriendlyType(baseType);
163
+ dataType = `${friendlyBase}[]`;
164
+ }
165
+ else if (dataType === 'USER-DEFINED' && col.udt_name) {
166
+ dataType = col.udt_name;
167
+ }
168
+ return {
169
+ name: col.column_name,
170
+ dataType,
171
+ isNullable: col.is_nullable === 'YES',
172
+ defaultValue: col.column_default,
173
+ isPrimaryKey: col.is_primary_key,
174
+ isUnique: col.is_unique,
175
+ maxLength: col.character_maximum_length,
176
+ precision: col.numeric_precision,
177
+ scale: col.numeric_scale,
178
+ references: col.foreign_table ? {
179
+ table: col.foreign_table,
180
+ column: col.foreign_column,
181
+ } : null,
182
+ comment: col.column_comment || undefined,
183
+ };
184
+ });
151
185
  const indexesResult = await pool.query(`
152
186
  SELECT
153
187
  i.relname as index_name,
154
188
  array_agg(a.attname ORDER BY k.n) as columns,
155
189
  ix.indisunique as is_unique,
156
190
  ix.indisprimary as is_primary,
157
- am.amname as index_type
191
+ am.amname as index_type,
192
+ obj_description(i.oid, 'pg_class') as index_comment
158
193
  FROM pg_index ix
159
194
  JOIN pg_class t ON t.oid = ix.indrelid
160
195
  JOIN pg_class i ON i.oid = ix.indexrelid
@@ -162,7 +197,7 @@ async function introspectDatabase(connection, onProgress, options) {
162
197
  JOIN LATERAL unnest(ix.indkey) WITH ORDINALITY AS k(attnum, n) ON true
163
198
  JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum
164
199
  WHERE t.relname = $1
165
- GROUP BY i.relname, ix.indisunique, ix.indisprimary, am.amname
200
+ GROUP BY i.relname, i.oid, ix.indisunique, ix.indisprimary, am.amname
166
201
  ORDER BY i.relname;
167
202
  `, [tableName]);
168
203
  const indexes = indexesResult.rows.map(idx => ({
@@ -171,6 +206,7 @@ async function introspectDatabase(connection, onProgress, options) {
171
206
  isUnique: idx.is_unique,
172
207
  isPrimary: idx.is_primary,
173
208
  type: idx.index_type,
209
+ comment: idx.index_comment || null,
174
210
  }));
175
211
  const constraintsResult = await pool.query(`
176
212
  SELECT
@@ -238,6 +274,7 @@ async function introspectDatabase(connection, onProgress, options) {
238
274
  isPartitioned: partitionInfo.is_partitioned || false,
239
275
  partitionType: partitionInfo.partition_type,
240
276
  partitionKey,
277
+ comment: row.table_comment || null,
241
278
  });
242
279
  }
243
280
  onProgress?.('fetching_extensions');
@@ -102,7 +102,6 @@ function tableToSnapshot(table) {
102
102
  maxLength: col.maxLength,
103
103
  precision: col.precision,
104
104
  scale: col.scale,
105
- references: col.references,
106
105
  };
107
106
  }
108
107
  for (const idx of table.indexes || []) {