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.
- package/dist/cjs/cli/commands/push.cjs +18 -8
- package/dist/cjs/cli/utils/migration-generator.cjs +210 -22
- package/dist/cjs/cli/utils/schema-diff.cjs +73 -9
- package/dist/cjs/cli/utils/schema-hash.cjs +18 -14
- package/dist/cjs/cli/utils/schema-to-ast.cjs +35 -16
- package/dist/cjs/schema-definition/pg-schema/pg-relations/relation-builder.cjs +2 -0
- package/dist/cockroachdb-builder.d.ts +4 -0
- package/dist/dsql-builder.d.ts +4 -0
- package/dist/esm/cli/commands/push.js +18 -8
- package/dist/esm/cli/utils/migration-generator.js +210 -22
- package/dist/esm/cli/utils/schema-diff.js +73 -9
- package/dist/esm/cli/utils/schema-hash.js +18 -14
- package/dist/esm/cli/utils/schema-to-ast.js +35 -16
- package/dist/esm/schema-definition/pg-schema/pg-relations/relation-builder.js +2 -0
- package/dist/index.d.ts +4 -0
- package/dist/nile-builder.d.ts +4 -0
- package/dist/pg-builder.d.ts +4 -0
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
|
259
|
-
console.log(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(` ${
|
|
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
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
sql.push(
|
|
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
|
|
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
|
-
|
|
616
|
+
columnSQL.push(...up);
|
|
500
617
|
if (includeDown)
|
|
501
618
|
downSQL.unshift(...down);
|
|
502
619
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
const
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
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
|
|
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 => ({
|
|
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 => ({
|
|
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
|
-
|
|
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
|
-
|
|
667
|
+
if (col.after?.comment)
|
|
668
|
+
commentsAdded++;
|
|
669
|
+
}
|
|
670
|
+
else if (col.type === 'removed') {
|
|
622
671
|
colsRemoved++;
|
|
623
|
-
|
|
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
|
-
|
|
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
|
-
|
|
53
|
-
|
|
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
|
|
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')
|