relq 1.0.93 → 1.0.95

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.
@@ -116,7 +116,7 @@ function mergeTrackingIds(introspected, desired) {
116
116
  return introspected;
117
117
  }
118
118
  async function runPush(config, projectRoot, opts = {}) {
119
- const { force = false, dryRun = false, skipPrompt = false } = opts;
119
+ const { force = false, dryRun = false, full = false, skipPrompt = false } = opts;
120
120
  const connection = config.connection;
121
121
  const includeFunctions = config.includeFunctions ?? false;
122
122
  const includeTriggers = config.includeTriggers ?? false;
@@ -235,7 +235,8 @@ async function runPush(config, projectRoot, opts = {}) {
235
235
  includeComments: false,
236
236
  });
237
237
  const upStatements = migration.upSQL;
238
- spin.stop(`Generated ${upStatements.length} statement(s)`);
238
+ const sqlStatementCount = upStatements.filter(s => s.trim() && !s.trim().startsWith('--')).length;
239
+ spin.stop(`Generated ${sqlStatementCount} statement(s)`);
239
240
  const summaryLines = (0, schema_diff_1.formatCategorizedSummary)(filteredDiff);
240
241
  if (summaryLines.length > 0) {
241
242
  for (const line of summaryLines)
@@ -255,15 +256,22 @@ async function runPush(config, projectRoot, opts = {}) {
255
256
  if (dryRun) {
256
257
  console.log(`${colors_1.colors.yellow('Dry run')} - SQL that would be executed:`);
257
258
  console.log('');
258
- for (const stmt of upStatements.slice(0, 15)) {
259
- console.log(` ${stmt.substring(0, 120)}${stmt.length > 120 ? '...' : ''}`);
260
- }
261
- if (upStatements.length > 15) {
262
- console.log(` ${colors_1.colors.muted(`... and ${upStatements.length - 15} more statements`)}`);
259
+ for (const stmt of upStatements) {
260
+ console.log(stmt);
263
261
  }
264
262
  if (migration.downSQL.length > 0) {
265
263
  console.log('');
266
- console.log(`${colors_1.colors.muted(`Rollback: ${migration.downSQL.length} revert statement(s) will be saved`)}`);
264
+ if (full) {
265
+ console.log(`${colors_1.colors.yellow('Rollback')} - SQL to revert:`);
266
+ console.log('');
267
+ for (const stmt of migration.downSQL) {
268
+ console.log(stmt);
269
+ }
270
+ }
271
+ else {
272
+ console.log(`${colors_1.colors.muted(`Rollback: ${migration.downSQL.length} revert statement(s) will be saved`)}`);
273
+ console.log(`${colors_1.colors.muted('Use')} ${colors_1.colors.cyan('--full')} ${colors_1.colors.muted('to include rollback SQL.')}`);
274
+ }
267
275
  }
268
276
  console.log('');
269
277
  console.log(`${colors_1.colors.muted('Remove')} ${colors_1.colors.cyan('--dry-run')} ${colors_1.colors.muted('to execute.')}`);
@@ -415,6 +423,7 @@ exports.default = (0, citty_1.defineCommand)({
415
423
  args: {
416
424
  force: { type: 'boolean', description: 'Force push without confirmation' },
417
425
  'dry-run': { type: 'boolean', description: 'Show SQL without executing' },
426
+ full: { type: 'boolean', description: 'Show all statements in dry run (including rollback)' },
418
427
  yes: { type: 'boolean', alias: 'y', description: 'Skip confirmation' },
419
428
  },
420
429
  async run({ args }) {
@@ -423,6 +432,7 @@ exports.default = (0, citty_1.defineCommand)({
423
432
  await runPush(config, projectRoot, {
424
433
  force: args.force === true,
425
434
  dryRun: args['dry-run'] === true,
435
+ full: args.full === true,
426
436
  skipPrompt: args.yes === true,
427
437
  });
428
438
  },
@@ -55,22 +55,64 @@ function generateMigration(diff, options = {}) {
55
55
  }
56
56
  }
57
57
  }
58
+ const deferredFKsByTable = new Map();
58
59
  for (const table of diff.tables.filter(t => t.type === 'added')) {
59
60
  const columns = (table.columns || [])
60
61
  .filter(c => c.after)
61
62
  .map(c => c.after);
63
+ const allConstraints = (table.constraints || [])
64
+ .filter(c => c.after)
65
+ .map(c => c.after);
66
+ const inlineConstraints = allConstraints.filter(c => c.type !== 'FOREIGN KEY');
67
+ const fkConstraints = allConstraints.filter(c => c.type === 'FOREIGN KEY');
68
+ const indexes = (table.indexes || [])
69
+ .filter(i => i.after)
70
+ .map(i => i.after);
62
71
  if (columns.length > 0) {
63
- up.push(...generateCreateTable({ name: table.name, columns }));
72
+ up.push(...generateCreateTable({ name: table.name, columns, constraints: inlineConstraints, indexes }));
73
+ }
74
+ if (fkConstraints.length > 0) {
75
+ const group = deferredFKsByTable.get(table.name) || { up: [], down: [] };
76
+ for (const fk of fkConstraints) {
77
+ group.up.push(`ALTER TABLE "${table.name}" ADD CONSTRAINT "${fk.name}" ${fk.definition};`);
78
+ if (includeDown) {
79
+ group.down.push(`ALTER TABLE "${table.name}" DROP CONSTRAINT IF EXISTS "${fk.name}";`);
80
+ }
81
+ }
82
+ deferredFKsByTable.set(table.name, group);
64
83
  }
65
84
  if (includeDown) {
66
85
  down.unshift(`DROP TABLE IF EXISTS "${table.name}" CASCADE;`);
67
86
  }
68
87
  }
69
- for (const table of diff.tables.filter(t => t.type === 'modified')) {
88
+ const modifiedTables = diff.tables.filter(t => t.type === 'modified');
89
+ for (const table of modifiedTables) {
90
+ up.push(`-- ==================================================================`);
91
+ up.push(`-- ALTER TABLE: ${table.name}`);
92
+ up.push(`-- ==================================================================`);
93
+ up.push('');
70
94
  const { upSQL, downSQL } = generateAlterTable(table, includeDown);
71
95
  up.push(...upSQL);
96
+ up.push('');
97
+ up.push('');
72
98
  down.unshift(...downSQL);
73
99
  }
100
+ if (deferredFKsByTable.size > 0) {
101
+ up.push(`-- ==================================================================`);
102
+ up.push(`-- FOREIGN KEY CONSTRAINTS`);
103
+ up.push(`-- ==================================================================`);
104
+ for (const [tableName, group] of deferredFKsByTable) {
105
+ up.push('');
106
+ up.push(`-- Foreign keys for ${tableName}`);
107
+ up.push(...group.up);
108
+ if (includeDown) {
109
+ down.unshift(...group.down);
110
+ }
111
+ }
112
+ up.push('');
113
+ up.push('');
114
+ }
115
+ up.splice(-2);
74
116
  return { up, down };
75
117
  }
76
118
  function generateMigrationFromComparison(comparison, options = {}) {
@@ -182,6 +224,9 @@ function generateMigrationFromComparison(comparison, options = {}) {
182
224
  }
183
225
  for (const { table, column } of comparison.added.columns) {
184
226
  up.push(`ALTER TABLE "${table}" ADD COLUMN ${generateParsedColumnDef(column)};`);
227
+ if (column.comment) {
228
+ up.push(`COMMENT ON COLUMN "${table}"."${column.name}" IS '${column.comment.replace(/'/g, "''")}';`);
229
+ }
185
230
  if (includeDown) {
186
231
  down.unshift(`ALTER TABLE "${table}" DROP COLUMN IF EXISTS "${column.name}";`);
187
232
  }
@@ -376,22 +421,67 @@ function generateColumnModification(tableName, columnName, changes) {
376
421
  function generateCreateTableFromParsed(table) {
377
422
  const sql = [];
378
423
  const columnDefs = [];
424
+ const constraintDefs = [];
379
425
  for (const col of table.columns) {
380
426
  columnDefs.push(` ${generateParsedColumnDef(col)}`);
381
427
  }
382
- let createSQL = `CREATE TABLE "${table.name}" (\n${columnDefs.join(',\n')}\n)`;
428
+ for (const con of table.constraints) {
429
+ if (con.type === 'FOREIGN KEY')
430
+ continue;
431
+ if (con.type === 'PRIMARY KEY' && con.columns.length === 1 &&
432
+ table.columns.some(c => c.isPrimaryKey && c.name === con.columns[0]))
433
+ continue;
434
+ if (con.type === 'UNIQUE' && con.columns.length === 1 &&
435
+ table.columns.some(c => c.isUnique && c.name === con.columns[0]))
436
+ continue;
437
+ constraintDefs.push(` CONSTRAINT "${con.name}" ${buildParsedConstraintDef(con)}`);
438
+ }
439
+ const allDefs = [...columnDefs, ...constraintDefs];
440
+ let createSQL = `CREATE TABLE "${table.name}" (\n${allDefs.join(',\n')}\n)`;
383
441
  if (table.isPartitioned && table.partitionType && table.partitionKey?.length) {
384
442
  createSQL += ` PARTITION BY ${table.partitionType} (${table.partitionKey.join(', ')})`;
385
443
  }
386
444
  sql.push(createSQL + ';');
445
+ for (const col of table.columns) {
446
+ if (col.comment) {
447
+ sql.push(`COMMENT ON COLUMN "${table.name}"."${col.name}" IS '${col.comment.replace(/'/g, "''")}';`);
448
+ }
449
+ }
387
450
  for (const idx of table.indexes) {
388
451
  const isPKIndex = table.columns.some(c => c.isPrimaryKey && idx.columns.includes(c.name) && idx.columns.length === 1);
389
452
  if (!isPKIndex) {
390
453
  sql.push(generateCreateIndexFromParsed(table.name, idx));
391
454
  }
392
455
  }
456
+ sql.push('');
457
+ sql.push('');
393
458
  return sql;
394
459
  }
460
+ function buildParsedConstraintDef(con) {
461
+ const cols = con.columns.map(c => `"${c}"`).join(', ');
462
+ switch (con.type) {
463
+ case 'PRIMARY KEY':
464
+ return `PRIMARY KEY (${cols})`;
465
+ case 'UNIQUE':
466
+ return `UNIQUE (${cols})`;
467
+ case 'CHECK':
468
+ return `CHECK (${con.expression || ''})`;
469
+ case 'FOREIGN KEY': {
470
+ let def = `FOREIGN KEY (${cols})`;
471
+ if (con.references) {
472
+ const refCols = con.references.columns.map(c => `"${c}"`).join(', ');
473
+ def += ` REFERENCES "${con.references.table}" (${refCols})`;
474
+ if (con.references.onDelete)
475
+ def += ` ON DELETE ${con.references.onDelete}`;
476
+ if (con.references.onUpdate)
477
+ def += ` ON UPDATE ${con.references.onUpdate}`;
478
+ }
479
+ return def;
480
+ }
481
+ default:
482
+ return con.expression || '';
483
+ }
484
+ }
395
485
  function generateParsedColumnDef(col) {
396
486
  const parts = [`"${col.name}"`, col.type];
397
487
  if (col.isPrimaryKey) {
@@ -463,15 +553,29 @@ function generateMigrationFile(diff, name, options = {}) {
463
553
  }
464
554
  function generateCreateTable(table) {
465
555
  const sql = [];
556
+ sql.push(`-- ==================================================================`);
557
+ sql.push(`-- TABLE: ${table.name}`);
558
+ sql.push(`-- ==================================================================`);
559
+ sql.push('');
466
560
  const columnDefs = [];
467
561
  const constraintDefs = [];
562
+ const maxNameLen = Math.max(...table.columns.map(c => {
563
+ const quotedName = needsQuote(c.name) ? `"${c.name}"` : c.name;
564
+ return quotedName.length;
565
+ }));
566
+ const padWidth = Math.max(maxNameLen + 4, 28);
468
567
  for (const col of table.columns) {
469
- columnDefs.push(` ${generateColumnDefinition(col)}`);
568
+ columnDefs.push(` ${generateColumnDefinitionAligned(col, padWidth)}`);
470
569
  }
471
570
  const hasPKColumn = table.columns.some(c => c.isPrimaryKey);
472
571
  for (const con of table.constraints || []) {
473
572
  if (con.type === 'PRIMARY KEY' && hasPKColumn)
474
573
  continue;
574
+ if (con.type === 'UNIQUE') {
575
+ const uniqueMatch = con.definition?.match(/^UNIQUE\s*\("([^"]+)"\)$/);
576
+ if (uniqueMatch && table.columns.some(c => c.isUnique && c.name === uniqueMatch[1]))
577
+ continue;
578
+ }
475
579
  constraintDefs.push(` CONSTRAINT "${con.name}" ${con.definition}`);
476
580
  }
477
581
  const allDefs = [...columnDefs, ...constraintDefs];
@@ -480,40 +584,79 @@ function generateCreateTable(table) {
480
584
  createSQL += ` PARTITION BY ${table.partitionType} (${table.partitionKey.join(', ')})`;
481
585
  }
482
586
  sql.push(createSQL + ';');
483
- for (const idx of table.indexes || []) {
484
- if (idx.isPrimary)
485
- continue;
486
- sql.push(generateCreateIndex(table.name, idx));
587
+ const indexes = (table.indexes || []).filter(idx => !idx.isPrimary);
588
+ if (indexes.length > 0) {
589
+ sql.push('');
590
+ sql.push(`-- Indexes for ${table.name}`);
591
+ for (const idx of indexes) {
592
+ sql.push(generateCreateIndex(table.name, idx));
593
+ }
487
594
  }
595
+ const commentCols = table.columns.filter(c => c.comment);
596
+ if (commentCols.length > 0) {
597
+ sql.push('');
598
+ sql.push(`-- Comments for ${table.name}`);
599
+ for (const col of commentCols) {
600
+ sql.push(`COMMENT ON COLUMN "${table.name}"."${col.name}" IS '${col.comment.replace(/'/g, "''")}';`);
601
+ }
602
+ }
603
+ sql.push('');
604
+ sql.push('');
488
605
  return sql;
489
606
  }
490
607
  function generateDropTable(table) {
491
608
  return [`DROP TABLE IF EXISTS "${table.name}" CASCADE;`];
492
609
  }
493
610
  function generateAlterTable(table, includeDown) {
494
- const upSQL = [];
611
+ const columnSQL = [];
495
612
  const downSQL = [];
496
613
  const tableName = table.name;
497
614
  for (const col of table.columns || []) {
498
615
  const { up, down } = generateColumnChange(tableName, col);
499
- upSQL.push(...up);
616
+ columnSQL.push(...up);
500
617
  if (includeDown)
501
618
  downSQL.unshift(...down);
502
619
  }
503
- for (const idx of table.indexes || []) {
504
- const { up, down } = generateIndexChange(tableName, idx);
505
- upSQL.push(...up);
506
- if (includeDown)
507
- downSQL.unshift(...down);
508
- }
509
- for (const con of table.constraints || []) {
510
- const { up, down } = generateConstraintChange(tableName, con);
511
- upSQL.push(...up);
512
- if (includeDown)
513
- downSQL.unshift(...down);
620
+ const ddlStatements = columnSQL.filter(s => !s.startsWith('COMMENT ON'));
621
+ const commentStatements = columnSQL.filter(s => s.startsWith('COMMENT ON'));
622
+ const upSQL = [...ddlStatements];
623
+ const indexChanges = table.indexes || [];
624
+ if (indexChanges.length > 0) {
625
+ upSQL.push('');
626
+ upSQL.push(`-- Indexes for ${tableName}`);
627
+ for (const idx of indexChanges) {
628
+ const { up, down } = generateIndexChange(tableName, idx);
629
+ upSQL.push(...up);
630
+ if (includeDown)
631
+ downSQL.unshift(...down);
632
+ }
633
+ }
634
+ if (commentStatements.length > 0) {
635
+ upSQL.push('');
636
+ upSQL.push(`-- Comments for ${tableName}`);
637
+ upSQL.push(...commentStatements);
638
+ }
639
+ const constraintChanges = table.constraints || [];
640
+ if (constraintChanges.length > 0) {
641
+ upSQL.push('');
642
+ upSQL.push(`-- Constraints for ${tableName}`);
643
+ for (const con of constraintChanges) {
644
+ const { up, down } = generateConstraintChange(tableName, con);
645
+ upSQL.push(...up);
646
+ if (includeDown)
647
+ downSQL.unshift(...down);
648
+ }
514
649
  }
515
650
  return { upSQL, downSQL };
516
651
  }
652
+ const PG_RESERVED = new Set([
653
+ 'action', 'comment', 'data', 'date', 'domain', 'key', 'language', 'locked',
654
+ 'method', 'name', 'order', 'password', 'read', 'schema', 'type', 'valid',
655
+ 'value', 'version', 'interval',
656
+ ]);
657
+ function needsQuote(name) {
658
+ return PG_RESERVED.has(name.toLowerCase()) || /[A-Z\s-]/.test(name);
659
+ }
517
660
  function generateColumnDefinition(col) {
518
661
  const parts = [`"${col.name}"`, mapDataType(col)];
519
662
  if (col.isPrimaryKey) {
@@ -533,16 +676,43 @@ function generateColumnDefinition(col) {
533
676
  }
534
677
  return parts.join(' ');
535
678
  }
679
+ function generateColumnDefinitionAligned(col, padWidth) {
680
+ const quotedName = `"${col.name}"`;
681
+ const paddedName = quotedName.padEnd(padWidth);
682
+ const typeParts = [mapDataType(col)];
683
+ if (col.isPrimaryKey) {
684
+ typeParts.push('PRIMARY KEY');
685
+ }
686
+ if (!col.isNullable && !col.isPrimaryKey) {
687
+ typeParts.push('NOT NULL');
688
+ }
689
+ if (col.isUnique && !col.isPrimaryKey) {
690
+ typeParts.push('UNIQUE');
691
+ }
692
+ if (col.defaultValue) {
693
+ typeParts.push(`DEFAULT ${col.defaultValue}`);
694
+ }
695
+ if (col.references) {
696
+ typeParts.push(`REFERENCES "${col.references.table}"("${col.references.column}")`);
697
+ }
698
+ return `${paddedName} ${typeParts.join(' ')}`;
699
+ }
536
700
  function generateColumnChange(tableName, col) {
537
701
  const up = [];
538
702
  const down = [];
539
703
  if (col.type === 'added' && col.after) {
540
704
  up.push(`ALTER TABLE "${tableName}" ADD COLUMN ${generateColumnDefinition(col.after)};`);
705
+ if (col.after.comment) {
706
+ up.push(`COMMENT ON COLUMN "${tableName}"."${col.name}" IS '${col.after.comment.replace(/'/g, "''")}';`);
707
+ }
541
708
  down.push(`ALTER TABLE "${tableName}" DROP COLUMN IF EXISTS "${col.name}";`);
542
709
  }
543
710
  if (col.type === 'removed' && col.before) {
544
711
  up.push(`ALTER TABLE "${tableName}" DROP COLUMN IF EXISTS "${col.name}";`);
545
712
  down.push(`ALTER TABLE "${tableName}" ADD COLUMN ${generateColumnDefinition(col.before)};`);
713
+ if (col.before.comment) {
714
+ down.push(`COMMENT ON COLUMN "${tableName}"."${col.name}" IS '${col.before.comment.replace(/'/g, "''")}';`);
715
+ }
546
716
  }
547
717
  if (col.type === 'modified' && col.changes && col.changes.length > 0) {
548
718
  for (const change of col.changes) {
@@ -617,6 +787,23 @@ function generateColumnChange(tableName, col) {
617
787
  }
618
788
  break;
619
789
  }
790
+ case 'comment': {
791
+ const newComment = change.to != null ? String(change.to) : null;
792
+ const oldComment = change.from != null ? String(change.from) : null;
793
+ if (newComment) {
794
+ up.push(`COMMENT ON COLUMN "${tableName}"."${col.name}" IS '${newComment.replace(/'/g, "''")}';`);
795
+ }
796
+ else {
797
+ up.push(`COMMENT ON COLUMN "${tableName}"."${col.name}" IS NULL;`);
798
+ }
799
+ if (oldComment) {
800
+ down.push(`COMMENT ON COLUMN "${tableName}"."${col.name}" IS '${oldComment.replace(/'/g, "''")}';`);
801
+ }
802
+ else {
803
+ down.push(`COMMENT ON COLUMN "${tableName}"."${col.name}" IS NULL;`);
804
+ }
805
+ break;
806
+ }
620
807
  }
621
808
  }
622
809
  }
@@ -626,7 +813,8 @@ function generateCreateIndex(tableName, idx) {
626
813
  const unique = idx.isUnique ? 'UNIQUE ' : '';
627
814
  const cols = Array.isArray(idx.columns) ? idx.columns : [idx.columns];
628
815
  const colList = cols.map(c => `"${c}"`).join(', ');
629
- const using = idx.type && idx.type !== 'btree' ? ` USING ${idx.type}` : '';
816
+ const normalizedType = (idx.type || 'btree').toLowerCase();
817
+ const using = normalizedType !== 'btree' ? ` USING ${idx.type}` : '';
630
818
  return `CREATE ${unique}INDEX IF NOT EXISTS "${idx.name}" ON "${tableName}"${using} (${colList});`;
631
819
  }
632
820
  function generateIndexChange(tableName, idx) {
@@ -21,6 +21,7 @@ function normalizedToColumnInfo(col) {
21
21
  maxLength: col.length ?? null,
22
22
  precision: null,
23
23
  scale: null,
24
+ comment: col.comment,
24
25
  };
25
26
  }
26
27
  const TYPE_ALIASES = {
@@ -234,6 +235,21 @@ function diffTables(local, remote) {
234
235
  const diffs = [];
235
236
  const localMap = new Map(local.map(t => [t.name, t]));
236
237
  const remoteMap = new Map(remote.map(t => [t.name, t]));
238
+ const toConstraintInfo = (c) => ({
239
+ name: c.name,
240
+ type: (c.type || 'CHECK'),
241
+ columns: [],
242
+ definition: c.definition || '',
243
+ trackingId: c.trackingId,
244
+ });
245
+ const toIndexInfo = (i) => ({
246
+ name: i.name,
247
+ columns: i.columns,
248
+ isUnique: i.unique,
249
+ isPrimary: false,
250
+ type: i.type || 'btree',
251
+ trackingId: i.trackingId,
252
+ });
237
253
  for (const [name, remoteTable] of remoteMap) {
238
254
  if (!localMap.has(name)) {
239
255
  diffs.push({
@@ -244,7 +260,16 @@ function diffTables(local, remote) {
244
260
  type: 'added',
245
261
  after: normalizedToColumnInfo(c),
246
262
  })),
247
- indexes: remoteTable.indexes.map(i => ({ name: i.name, type: 'added' })),
263
+ indexes: remoteTable.indexes.map(i => ({
264
+ name: i.name,
265
+ type: 'added',
266
+ after: toIndexInfo(i),
267
+ })),
268
+ constraints: remoteTable.constraints.map(c => ({
269
+ name: c.name,
270
+ type: 'added',
271
+ after: toConstraintInfo(c),
272
+ })),
248
273
  });
249
274
  }
250
275
  }
@@ -258,7 +283,16 @@ function diffTables(local, remote) {
258
283
  type: 'removed',
259
284
  before: normalizedToColumnInfo(c),
260
285
  })),
261
- indexes: localTable.indexes.map(i => ({ name: i.name, type: 'removed' })),
286
+ indexes: localTable.indexes.map(i => ({
287
+ name: i.name,
288
+ type: 'removed',
289
+ before: toIndexInfo(i),
290
+ })),
291
+ constraints: localTable.constraints.map(c => ({
292
+ name: c.name,
293
+ type: 'removed',
294
+ before: toConstraintInfo(c),
295
+ })),
262
296
  });
263
297
  }
264
298
  }
@@ -325,20 +359,31 @@ function compareColumns(local, remote) {
325
359
  if (local.isUnique !== remote.isUnique) {
326
360
  changes.push({ field: 'unique', from: local.isUnique, to: remote.isUnique });
327
361
  }
362
+ if ((local.comment || undefined) !== (remote.comment || undefined)) {
363
+ changes.push({ field: 'comment', from: local.comment, to: remote.comment });
364
+ }
328
365
  return changes;
329
366
  }
330
367
  function diffIndexes(local, remote) {
331
368
  const diffs = [];
332
369
  const localMap = new Map(local.map(i => [i.name, i]));
333
370
  const remoteMap = new Map(remote.map(i => [i.name, i]));
334
- for (const [name] of remoteMap) {
371
+ const toIdxInfo = (i) => ({
372
+ name: i.name,
373
+ columns: i.columns,
374
+ isUnique: i.unique,
375
+ isPrimary: false,
376
+ type: i.type || 'btree',
377
+ trackingId: i.trackingId,
378
+ });
379
+ for (const [name, idx] of remoteMap) {
335
380
  if (!localMap.has(name)) {
336
- diffs.push({ name, type: 'added' });
381
+ diffs.push({ name, type: 'added', after: toIdxInfo(idx) });
337
382
  }
338
383
  }
339
- for (const [name] of localMap) {
384
+ for (const [name, idx] of localMap) {
340
385
  if (!remoteMap.has(name)) {
341
- diffs.push({ name, type: 'removed' });
386
+ diffs.push({ name, type: 'removed', before: toIdxInfo(idx) });
342
387
  }
343
388
  }
344
389
  return diffs;
@@ -608,6 +653,7 @@ function formatCategorizedSummary(diff) {
608
653
  let pkAdded = 0, pkRemoved = 0;
609
654
  let checkAdded = 0, checkRemoved = 0;
610
655
  let extAdded = 0, extRemoved = 0;
656
+ let commentsAdded = 0, commentsRemoved = 0, commentsModified = 0;
611
657
  for (const table of diff.tables) {
612
658
  if (table.type === 'added')
613
659
  tablesAdded++;
@@ -616,12 +662,29 @@ function formatCategorizedSummary(diff) {
616
662
  else if (table.type === 'modified')
617
663
  tablesModified++;
618
664
  for (const col of table.columns || []) {
619
- if (col.type === 'added')
665
+ if (col.type === 'added') {
620
666
  colsAdded++;
621
- else if (col.type === 'removed')
667
+ if (col.after?.comment)
668
+ commentsAdded++;
669
+ }
670
+ else if (col.type === 'removed') {
622
671
  colsRemoved++;
623
- else if (col.type === 'modified')
672
+ if (col.before?.comment)
673
+ commentsRemoved++;
674
+ }
675
+ else if (col.type === 'modified') {
624
676
  colsModified++;
677
+ for (const change of col.changes || []) {
678
+ if (change.field === 'comment') {
679
+ if (change.from && change.to)
680
+ commentsModified++;
681
+ else if (change.to)
682
+ commentsAdded++;
683
+ else if (change.from)
684
+ commentsRemoved++;
685
+ }
686
+ }
687
+ }
625
688
  }
626
689
  for (const idx of table.indexes || []) {
627
690
  if (idx.type === 'added')
@@ -706,6 +769,7 @@ function formatCategorizedSummary(diff) {
706
769
  fmt('unique constraints', uniqueAdded, uniqueRemoved);
707
770
  fmt('foreign keys', fkAdded, fkRemoved);
708
771
  fmt('check constraints', checkAdded, checkRemoved);
772
+ fmt('comments', commentsAdded, commentsRemoved, commentsModified);
709
773
  fmt('extensions', extAdded, extRemoved);
710
774
  return lines;
711
775
  }
@@ -41,22 +41,14 @@ const crypto = __importStar(require("node:crypto"));
41
41
  function normalizeSchema(schema) {
42
42
  const userTables = schema.tables.filter(t => !t.name.startsWith('_relq') &&
43
43
  !t.name.startsWith('relq_'));
44
- const tables = userTables
45
- .map(normalizeTable)
46
- .sort((a, b) => a.name.localeCompare(b.name));
47
- const extensions = [...schema.extensions].sort();
44
+ const tables = userTables.map(normalizeTable);
45
+ const extensions = [...schema.extensions];
48
46
  return { tables, extensions };
49
47
  }
50
48
  function normalizeTable(table) {
51
- const columns = table.columns
52
- .map(normalizeColumn)
53
- .sort((a, b) => a.name.localeCompare(b.name));
54
- const indexes = (table.indexes || [])
55
- .map(normalizeIndex)
56
- .sort((a, b) => a.name.localeCompare(b.name));
57
- const constraints = (table.constraints || [])
58
- .map(normalizeConstraint)
59
- .sort((a, b) => a.name.localeCompare(b.name));
49
+ const columns = table.columns.map(normalizeColumn);
50
+ const indexes = (table.indexes || []).map(normalizeIndex);
51
+ const constraints = (table.constraints || []).map(normalizeConstraint);
60
52
  return {
61
53
  name: table.name,
62
54
  columns,
@@ -105,6 +97,7 @@ function normalizeColumn(col) {
105
97
  isPrimaryKey: col.isPrimaryKey ?? false,
106
98
  isUnique: col.isUnique ?? false,
107
99
  length: explicitLength ?? extractedLength,
100
+ comment: col.comment ?? c.comment ?? undefined,
108
101
  trackingId: col.trackingId ?? c.trackingId,
109
102
  };
110
103
  }
@@ -127,7 +120,18 @@ function normalizeConstraint(con) {
127
120
  }
128
121
  function generateSchemaHash(schema) {
129
122
  const normalized = normalizeSchema(schema);
130
- const json = JSON.stringify(normalized, null, 0);
123
+ const sorted = {
124
+ tables: [...normalized.tables]
125
+ .sort((a, b) => a.name.localeCompare(b.name))
126
+ .map(t => ({
127
+ ...t,
128
+ columns: [...t.columns].sort((a, b) => a.name.localeCompare(b.name)),
129
+ indexes: [...t.indexes].sort((a, b) => a.name.localeCompare(b.name)),
130
+ constraints: [...t.constraints].sort((a, b) => a.name.localeCompare(b.name)),
131
+ })),
132
+ extensions: [...normalized.extensions].sort(),
133
+ };
134
+ const json = JSON.stringify(sorted, null, 0);
131
135
  return crypto
132
136
  .createHash('sha1')
133
137
  .update(json, 'utf8')