relq 1.0.90 → 1.0.91
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/generate.cjs +4 -14
- package/dist/cjs/cli/commands/migrate.cjs +9 -3
- package/dist/cjs/cli/commands/push.cjs +16 -5
- package/dist/cjs/cli/utils/migration-generator.cjs +68 -0
- package/dist/cjs/cli/utils/postgres/introspect.cjs +7 -2
- package/dist/cjs/cli/utils/schema-diff.cjs +139 -1
- package/dist/cjs/cli/utils/schema-to-ast.cjs +14 -0
- package/dist/esm/cli/commands/generate.js +5 -15
- package/dist/esm/cli/commands/migrate.js +10 -4
- package/dist/esm/cli/commands/push.js +17 -6
- package/dist/esm/cli/utils/migration-generator.js +68 -0
- package/dist/esm/cli/utils/postgres/introspect.js +7 -2
- package/dist/esm/cli/utils/schema-diff.js +138 -1
- package/dist/esm/cli/utils/schema-to-ast.js +14 -0
- package/package.json +1 -1
|
@@ -156,21 +156,11 @@ exports.default = (0, citty_1.defineCommand)({
|
|
|
156
156
|
console.log('');
|
|
157
157
|
return;
|
|
158
158
|
}
|
|
159
|
-
const s = filteredDiff.summary;
|
|
160
159
|
console.log('');
|
|
161
160
|
console.log(`${colors_1.colors.bold('Changes to include:')}`);
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
console.log(` ${colors_1.colors.red('-')} ${s.tablesRemoved} table(s) to drop`);
|
|
166
|
-
if (s.tablesModified > 0)
|
|
167
|
-
console.log(` ${colors_1.colors.yellow('~')} ${s.tablesModified} table(s) modified`);
|
|
168
|
-
if (s.columnsAdded > 0)
|
|
169
|
-
console.log(` ${colors_1.colors.green('+')} ${s.columnsAdded} column(s) to add`);
|
|
170
|
-
if (s.columnsRemoved > 0)
|
|
171
|
-
console.log(` ${colors_1.colors.red('-')} ${s.columnsRemoved} column(s) to drop`);
|
|
172
|
-
if (s.columnsModified > 0)
|
|
173
|
-
console.log(` ${colors_1.colors.yellow('~')} ${s.columnsModified} column(s) modified`);
|
|
161
|
+
const changeSummaryLines = (0, schema_diff_1.formatCategorizedSummary)(filteredDiff);
|
|
162
|
+
for (const line of changeSummaryLines)
|
|
163
|
+
console.log(line);
|
|
174
164
|
console.log('');
|
|
175
165
|
if ((0, schema_diff_1.hasDestructiveChanges)(filteredDiff)) {
|
|
176
166
|
const tables = (0, schema_diff_1.getDestructiveTables)(filteredDiff);
|
|
@@ -205,7 +195,7 @@ exports.default = (0, citty_1.defineCommand)({
|
|
|
205
195
|
includeDown: !noDown,
|
|
206
196
|
includeComments: true,
|
|
207
197
|
});
|
|
208
|
-
spin.stop(
|
|
198
|
+
spin.stop(`Migration generated — ${migration.upSQL.length} statement(s)`);
|
|
209
199
|
let fileName;
|
|
210
200
|
if (format === 'timestamp') {
|
|
211
201
|
fileName = (0, migration_generator_1.generateTimestampedName)(migrationName) + '.sql';
|
|
@@ -153,9 +153,15 @@ exports.default = (0, citty_1.defineCommand)({
|
|
|
153
153
|
const executeQuery = tx
|
|
154
154
|
? (sql, params) => tx.query(sql, params)
|
|
155
155
|
: (sql, params) => dbClient.query(sql, params);
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
|
|
156
|
+
const statements = (0, migration_helpers_1.splitStatements)(up);
|
|
157
|
+
for (const stmt of statements) {
|
|
158
|
+
await executeQuery(stmt);
|
|
159
|
+
}
|
|
160
|
+
const p1 = await (0, migration_helpers_1.getParamPlaceholder)(dialect, 1);
|
|
161
|
+
const p2 = await (0, migration_helpers_1.getParamPlaceholder)(dialect, 2);
|
|
162
|
+
const p3 = await (0, migration_helpers_1.getParamPlaceholder)(dialect, 3);
|
|
163
|
+
const p4 = await (0, migration_helpers_1.getParamPlaceholder)(dialect, 4);
|
|
164
|
+
await executeQuery(`INSERT INTO ${quote}${tableName}${quote} (name, filename, hash, batch) VALUES (${p1}, ${p2}, ${p3}, ${p4})`, [file.replace(/\.sql$/, ''), file, file, applied + 1]);
|
|
159
165
|
if (tx)
|
|
160
166
|
await tx.commit();
|
|
161
167
|
spin.stop(`Applied ${file}`);
|
|
@@ -236,6 +236,12 @@ async function runPush(config, projectRoot, opts = {}) {
|
|
|
236
236
|
});
|
|
237
237
|
const upStatements = migration.upSQL;
|
|
238
238
|
spin.stop(`Generated ${upStatements.length} statement(s)`);
|
|
239
|
+
const summaryLines = (0, schema_diff_1.formatCategorizedSummary)(filteredDiff);
|
|
240
|
+
if (summaryLines.length > 0) {
|
|
241
|
+
for (const line of summaryLines)
|
|
242
|
+
console.log(line);
|
|
243
|
+
console.log('');
|
|
244
|
+
}
|
|
239
245
|
const dialect = (0, dialect_router_1.detectDialect)(config);
|
|
240
246
|
if (dialect !== 'postgres' && upStatements.length > 0) {
|
|
241
247
|
const upSQL = upStatements.join('\n');
|
|
@@ -277,6 +283,7 @@ async function runPush(config, projectRoot, opts = {}) {
|
|
|
277
283
|
spin.start('Applying schema changes...');
|
|
278
284
|
const execClient = await (0, database_client_1.createDatabaseClient)(config);
|
|
279
285
|
let tx = null;
|
|
286
|
+
let statementsRun = 0;
|
|
280
287
|
try {
|
|
281
288
|
try {
|
|
282
289
|
tx = await execClient.beginTransaction();
|
|
@@ -288,7 +295,6 @@ async function runPush(config, projectRoot, opts = {}) {
|
|
|
288
295
|
const executeQuery = tx
|
|
289
296
|
? (sql) => tx.query(sql)
|
|
290
297
|
: (sql) => execClient.query(sql);
|
|
291
|
-
let statementsRun = 0;
|
|
292
298
|
for (const stmt of upStatements) {
|
|
293
299
|
try {
|
|
294
300
|
await executeQuery(stmt);
|
|
@@ -303,7 +309,6 @@ async function runPush(config, projectRoot, opts = {}) {
|
|
|
303
309
|
}
|
|
304
310
|
if (tx)
|
|
305
311
|
await tx.commit();
|
|
306
|
-
spin.stop(`Applied ${statementsRun} statement(s)`);
|
|
307
312
|
try {
|
|
308
313
|
const tableName = config.migrations?.tableName || '_relq_migrations';
|
|
309
314
|
const tableDDL = await (0, migration_helpers_1.getMigrationTableDDL)(config, tableName);
|
|
@@ -319,10 +324,11 @@ async function runPush(config, projectRoot, opts = {}) {
|
|
|
319
324
|
const hash = (0, migration_generator_1.generateTimestampedName)(migrationName);
|
|
320
325
|
await execClient.query(`INSERT INTO "${tableName}" (name, filename, hash, batch, sql_up, sql_down, source) ` +
|
|
321
326
|
`VALUES ($1, $2, $3, 0, $4, $5, 'push')`, [migrationName, '', hash, upSQL, downSQL]);
|
|
322
|
-
spin.stop(`Applied ${statementsRun} statement(s) —
|
|
327
|
+
spin.stop(`Applied ${statementsRun} statement(s) — restore point saved`);
|
|
323
328
|
}
|
|
324
329
|
catch (recordError) {
|
|
325
|
-
|
|
330
|
+
spin.stop(`Applied ${statementsRun} statement(s)`);
|
|
331
|
+
(0, ui_1.warning)(`Failed to save restore point: ${recordError?.message || String(recordError)}`);
|
|
326
332
|
}
|
|
327
333
|
}
|
|
328
334
|
catch (error) {
|
|
@@ -340,7 +346,12 @@ async function runPush(config, projectRoot, opts = {}) {
|
|
|
340
346
|
if (error.statementIndex) {
|
|
341
347
|
errorMsg += `${colors_1.colors.yellow('Statement #:')} ${error.statementIndex}\n`;
|
|
342
348
|
}
|
|
343
|
-
|
|
349
|
+
if (tx) {
|
|
350
|
+
errorMsg += `\n${colors_1.colors.muted('All changes rolled back.')}\n`;
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
errorMsg += `\n${colors_1.colors.yellow('Warning:')} ${statementsRun} statement(s) were already applied (no transaction support).\n`;
|
|
354
|
+
}
|
|
344
355
|
spin.error('SQL execution failed');
|
|
345
356
|
throw new Error(errorMsg);
|
|
346
357
|
}
|
|
@@ -336,6 +336,39 @@ function generateColumnModification(tableName, columnName, changes) {
|
|
|
336
336
|
downSQL.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" DROP DEFAULT;`);
|
|
337
337
|
}
|
|
338
338
|
break;
|
|
339
|
+
case 'length': {
|
|
340
|
+
const newLen = change.to;
|
|
341
|
+
const oldLen = change.from;
|
|
342
|
+
const newFullType = newLen ? `VARCHAR(${newLen})` : 'VARCHAR';
|
|
343
|
+
const oldFullType = oldLen ? `VARCHAR(${oldLen})` : 'VARCHAR';
|
|
344
|
+
upSQL.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" TYPE ${newFullType};`);
|
|
345
|
+
downSQL.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" TYPE ${oldFullType};`);
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
case 'unique': {
|
|
349
|
+
const constraintName = `${tableName}_${columnName}_key`;
|
|
350
|
+
if (change.to === true) {
|
|
351
|
+
upSQL.push(`ALTER TABLE "${tableName}" ADD CONSTRAINT "${constraintName}" UNIQUE ("${columnName}");`);
|
|
352
|
+
downSQL.push(`ALTER TABLE "${tableName}" DROP CONSTRAINT IF EXISTS "${constraintName}";`);
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
upSQL.push(`ALTER TABLE "${tableName}" DROP CONSTRAINT IF EXISTS "${constraintName}";`);
|
|
356
|
+
downSQL.push(`ALTER TABLE "${tableName}" ADD CONSTRAINT "${constraintName}" UNIQUE ("${columnName}");`);
|
|
357
|
+
}
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
case 'primaryKey': {
|
|
361
|
+
const pkName = `${tableName}_pkey`;
|
|
362
|
+
if (change.to === true) {
|
|
363
|
+
upSQL.push(`ALTER TABLE "${tableName}" ADD CONSTRAINT "${pkName}" PRIMARY KEY ("${columnName}");`);
|
|
364
|
+
downSQL.push(`ALTER TABLE "${tableName}" DROP CONSTRAINT IF EXISTS "${pkName}";`);
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
upSQL.push(`ALTER TABLE "${tableName}" DROP CONSTRAINT IF EXISTS "${pkName}";`);
|
|
368
|
+
downSQL.push(`ALTER TABLE "${tableName}" ADD CONSTRAINT "${pkName}" PRIMARY KEY ("${columnName}");`);
|
|
369
|
+
}
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
339
372
|
}
|
|
340
373
|
}
|
|
341
374
|
return { upSQL, downSQL };
|
|
@@ -549,6 +582,41 @@ function generateColumnChange(tableName, col) {
|
|
|
549
582
|
}
|
|
550
583
|
break;
|
|
551
584
|
}
|
|
585
|
+
case 'length': {
|
|
586
|
+
const colType = col.after?.dataType ?? col.before?.dataType ?? 'VARCHAR';
|
|
587
|
+
const baseType = colType.toUpperCase().replace(/\(.*\)/, '');
|
|
588
|
+
const newLen = change.to;
|
|
589
|
+
const oldLen = change.from;
|
|
590
|
+
const newFullType = newLen ? `${baseType}(${newLen})` : baseType;
|
|
591
|
+
const oldFullType = oldLen ? `${baseType}(${oldLen})` : baseType;
|
|
592
|
+
up.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${col.name}" TYPE ${newFullType};`);
|
|
593
|
+
down.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${col.name}" TYPE ${oldFullType};`);
|
|
594
|
+
break;
|
|
595
|
+
}
|
|
596
|
+
case 'unique': {
|
|
597
|
+
const constraintName = `${tableName}_${col.name}_key`;
|
|
598
|
+
if (change.to === true) {
|
|
599
|
+
up.push(`ALTER TABLE "${tableName}" ADD CONSTRAINT "${constraintName}" UNIQUE ("${col.name}");`);
|
|
600
|
+
down.push(`ALTER TABLE "${tableName}" DROP CONSTRAINT IF EXISTS "${constraintName}";`);
|
|
601
|
+
}
|
|
602
|
+
else {
|
|
603
|
+
up.push(`ALTER TABLE "${tableName}" DROP CONSTRAINT IF EXISTS "${constraintName}";`);
|
|
604
|
+
down.push(`ALTER TABLE "${tableName}" ADD CONSTRAINT "${constraintName}" UNIQUE ("${col.name}");`);
|
|
605
|
+
}
|
|
606
|
+
break;
|
|
607
|
+
}
|
|
608
|
+
case 'primaryKey': {
|
|
609
|
+
const pkName = `${tableName}_pkey`;
|
|
610
|
+
if (change.to === true) {
|
|
611
|
+
up.push(`ALTER TABLE "${tableName}" ADD CONSTRAINT "${pkName}" PRIMARY KEY ("${col.name}");`);
|
|
612
|
+
down.push(`ALTER TABLE "${tableName}" DROP CONSTRAINT IF EXISTS "${pkName}";`);
|
|
613
|
+
}
|
|
614
|
+
else {
|
|
615
|
+
up.push(`ALTER TABLE "${tableName}" DROP CONSTRAINT IF EXISTS "${pkName}";`);
|
|
616
|
+
down.push(`ALTER TABLE "${tableName}" ADD CONSTRAINT "${pkName}" PRIMARY KEY ("${col.name}");`);
|
|
617
|
+
}
|
|
618
|
+
break;
|
|
619
|
+
}
|
|
552
620
|
}
|
|
553
621
|
}
|
|
554
622
|
}
|
|
@@ -283,8 +283,13 @@ async function introspectPostgres(connection, options) {
|
|
|
283
283
|
continue;
|
|
284
284
|
const normalizedColName = rawColName.replace(/^"|"$/g, '').toLowerCase();
|
|
285
285
|
const col = columns.find(c => c.name.toLowerCase() === normalizedColName);
|
|
286
|
-
if (col
|
|
287
|
-
|
|
286
|
+
if (col) {
|
|
287
|
+
if (con.constraint_type === 'PRIMARY KEY') {
|
|
288
|
+
col.isPrimaryKey = true;
|
|
289
|
+
}
|
|
290
|
+
if (con.constraint_type === 'UNIQUE' && constraintColumns.length === 1) {
|
|
291
|
+
col.isUnique = true;
|
|
292
|
+
}
|
|
288
293
|
}
|
|
289
294
|
}
|
|
290
295
|
}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.diffSchemas = diffSchemas;
|
|
4
4
|
exports.formatDiff = formatDiff;
|
|
5
5
|
exports.formatSummary = formatSummary;
|
|
6
|
+
exports.formatCategorizedSummary = formatCategorizedSummary;
|
|
6
7
|
exports.filterDiff = filterDiff;
|
|
7
8
|
exports.hasDestructiveChanges = hasDestructiveChanges;
|
|
8
9
|
exports.stripDestructiveChanges = stripDestructiveChanges;
|
|
@@ -266,12 +267,14 @@ function diffTables(local, remote) {
|
|
|
266
267
|
if (remoteTable) {
|
|
267
268
|
const columnDiffs = diffColumns(localTable.columns, remoteTable.columns);
|
|
268
269
|
const indexDiffs = diffIndexes(localTable.indexes, remoteTable.indexes);
|
|
269
|
-
|
|
270
|
+
const constraintDiffs = diffConstraints(localTable.constraints, remoteTable.constraints);
|
|
271
|
+
if (columnDiffs.length > 0 || indexDiffs.length > 0 || constraintDiffs.length > 0) {
|
|
270
272
|
diffs.push({
|
|
271
273
|
name,
|
|
272
274
|
type: 'modified',
|
|
273
275
|
columns: columnDiffs,
|
|
274
276
|
indexes: indexDiffs,
|
|
277
|
+
constraints: constraintDiffs,
|
|
275
278
|
});
|
|
276
279
|
}
|
|
277
280
|
}
|
|
@@ -340,6 +343,37 @@ function diffIndexes(local, remote) {
|
|
|
340
343
|
}
|
|
341
344
|
return diffs;
|
|
342
345
|
}
|
|
346
|
+
function diffConstraints(local, remote) {
|
|
347
|
+
const diffs = [];
|
|
348
|
+
const sig = (c) => {
|
|
349
|
+
const def = (c.definition || '').replace(/"/g, '').replace(/\s+/g, ' ').trim().toLowerCase();
|
|
350
|
+
return `${c.type}:${def}`;
|
|
351
|
+
};
|
|
352
|
+
const localSigs = new Map();
|
|
353
|
+
const remoteSigs = new Map();
|
|
354
|
+
for (const c of local)
|
|
355
|
+
localSigs.set(sig(c), c);
|
|
356
|
+
for (const c of remote)
|
|
357
|
+
remoteSigs.set(sig(c), c);
|
|
358
|
+
const toConstraintInfo = (c) => ({
|
|
359
|
+
name: c.name,
|
|
360
|
+
type: (c.type || 'CHECK'),
|
|
361
|
+
columns: [],
|
|
362
|
+
definition: c.definition || '',
|
|
363
|
+
trackingId: c.trackingId,
|
|
364
|
+
});
|
|
365
|
+
for (const [s, c] of remoteSigs) {
|
|
366
|
+
if (!localSigs.has(s)) {
|
|
367
|
+
diffs.push({ name: c.name || c.definition || 'unnamed', type: 'added', after: toConstraintInfo(c) });
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
for (const [s, c] of localSigs) {
|
|
371
|
+
if (!remoteSigs.has(s)) {
|
|
372
|
+
diffs.push({ name: c.name || c.definition || 'unnamed', type: 'removed', before: toConstraintInfo(c) });
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return diffs;
|
|
376
|
+
}
|
|
343
377
|
function diffExtensions(local, remote) {
|
|
344
378
|
const diffs = [];
|
|
345
379
|
const localSet = new Set(local);
|
|
@@ -426,6 +460,11 @@ function formatDiff(diff) {
|
|
|
426
460
|
colors_1.colors.red(' -');
|
|
427
461
|
lines.push(`${idxIcon} index: ${idx.name}`);
|
|
428
462
|
}
|
|
463
|
+
for (const con of table.constraints || []) {
|
|
464
|
+
const conIcon = con.type === 'added' ? colors_1.colors.green(' +') :
|
|
465
|
+
colors_1.colors.red(' -');
|
|
466
|
+
lines.push(`${conIcon} constraint: ${con.name}`);
|
|
467
|
+
}
|
|
429
468
|
}
|
|
430
469
|
for (const ext of diff.extensions) {
|
|
431
470
|
const icon = ext.type === 'added' ? colors_1.colors.green('+') : colors_1.colors.red('-');
|
|
@@ -472,6 +511,105 @@ function formatSummary(diff) {
|
|
|
472
511
|
}
|
|
473
512
|
return parts.join(', ');
|
|
474
513
|
}
|
|
514
|
+
function formatCategorizedSummary(diff) {
|
|
515
|
+
let tablesAdded = 0, tablesRemoved = 0, tablesModified = 0;
|
|
516
|
+
let colsAdded = 0, colsRemoved = 0, colsModified = 0;
|
|
517
|
+
let idxAdded = 0, idxRemoved = 0;
|
|
518
|
+
let fkAdded = 0, fkRemoved = 0;
|
|
519
|
+
let uniqueAdded = 0, uniqueRemoved = 0;
|
|
520
|
+
let pkAdded = 0, pkRemoved = 0;
|
|
521
|
+
let checkAdded = 0, checkRemoved = 0;
|
|
522
|
+
let extAdded = 0, extRemoved = 0;
|
|
523
|
+
for (const table of diff.tables) {
|
|
524
|
+
if (table.type === 'added')
|
|
525
|
+
tablesAdded++;
|
|
526
|
+
else if (table.type === 'removed')
|
|
527
|
+
tablesRemoved++;
|
|
528
|
+
else if (table.type === 'modified')
|
|
529
|
+
tablesModified++;
|
|
530
|
+
for (const col of table.columns || []) {
|
|
531
|
+
if (col.type === 'added')
|
|
532
|
+
colsAdded++;
|
|
533
|
+
else if (col.type === 'removed')
|
|
534
|
+
colsRemoved++;
|
|
535
|
+
else if (col.type === 'modified')
|
|
536
|
+
colsModified++;
|
|
537
|
+
}
|
|
538
|
+
for (const idx of table.indexes || []) {
|
|
539
|
+
if (idx.type === 'added')
|
|
540
|
+
idxAdded++;
|
|
541
|
+
else if (idx.type === 'removed')
|
|
542
|
+
idxRemoved++;
|
|
543
|
+
}
|
|
544
|
+
for (const con of table.constraints || []) {
|
|
545
|
+
const name = (con.name || '').toLowerCase();
|
|
546
|
+
const isFk = name.includes('fkey') || name.includes('foreign');
|
|
547
|
+
const isUq = name.includes('unique') || name.includes('_key');
|
|
548
|
+
const isPk = name.includes('pkey') || name.includes('primary');
|
|
549
|
+
const before = con.before;
|
|
550
|
+
const after = con.after;
|
|
551
|
+
const conType = (before?.type || after?.type || '').toUpperCase();
|
|
552
|
+
if (conType === 'FOREIGN KEY' || isFk) {
|
|
553
|
+
if (con.type === 'added')
|
|
554
|
+
fkAdded++;
|
|
555
|
+
else if (con.type === 'removed')
|
|
556
|
+
fkRemoved++;
|
|
557
|
+
}
|
|
558
|
+
else if (conType === 'UNIQUE' || isUq) {
|
|
559
|
+
if (con.type === 'added')
|
|
560
|
+
uniqueAdded++;
|
|
561
|
+
else if (con.type === 'removed')
|
|
562
|
+
uniqueRemoved++;
|
|
563
|
+
}
|
|
564
|
+
else if (conType === 'PRIMARY KEY' || isPk) {
|
|
565
|
+
if (con.type === 'added')
|
|
566
|
+
pkAdded++;
|
|
567
|
+
else if (con.type === 'removed')
|
|
568
|
+
pkRemoved++;
|
|
569
|
+
}
|
|
570
|
+
else if (conType === 'CHECK') {
|
|
571
|
+
if (con.type === 'added')
|
|
572
|
+
checkAdded++;
|
|
573
|
+
else if (con.type === 'removed')
|
|
574
|
+
checkRemoved++;
|
|
575
|
+
}
|
|
576
|
+
else {
|
|
577
|
+
if (con.type === 'added')
|
|
578
|
+
checkAdded++;
|
|
579
|
+
else if (con.type === 'removed')
|
|
580
|
+
checkRemoved++;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
for (const ext of diff.extensions) {
|
|
585
|
+
if (ext.type === 'added')
|
|
586
|
+
extAdded++;
|
|
587
|
+
else if (ext.type === 'removed')
|
|
588
|
+
extRemoved++;
|
|
589
|
+
}
|
|
590
|
+
const lines = [];
|
|
591
|
+
const fmt = (label, added, removed, modified = 0) => {
|
|
592
|
+
const parts = [];
|
|
593
|
+
if (added)
|
|
594
|
+
parts.push(colors_1.colors.green(`${added} added`));
|
|
595
|
+
if (modified)
|
|
596
|
+
parts.push(colors_1.colors.yellow(`${modified} modified`));
|
|
597
|
+
if (removed)
|
|
598
|
+
parts.push(colors_1.colors.red(`${removed} removed`));
|
|
599
|
+
if (parts.length > 0) {
|
|
600
|
+
lines.push(` ${colors_1.colors.muted(label + ':')} ${parts.join(', ')}`);
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
fmt('tables', tablesAdded, tablesRemoved, tablesModified);
|
|
604
|
+
fmt('columns', colsAdded, colsRemoved, colsModified);
|
|
605
|
+
fmt('indexes', idxAdded, idxRemoved);
|
|
606
|
+
fmt('primary keys', pkAdded, pkRemoved);
|
|
607
|
+
fmt('unique constraints', uniqueAdded, uniqueRemoved);
|
|
608
|
+
fmt('foreign keys', fkAdded, fkRemoved);
|
|
609
|
+
fmt('check constraints', checkAdded, checkRemoved);
|
|
610
|
+
fmt('extensions', extAdded, extRemoved);
|
|
611
|
+
return lines;
|
|
612
|
+
}
|
|
475
613
|
function filterDiff(diff, ignorePatterns) {
|
|
476
614
|
const patterns = ignorePatterns.map(p => {
|
|
477
615
|
const regexStr = p.replace(/\*/g, '.*').replace(/\?/g, '.');
|
|
@@ -405,6 +405,20 @@ function tableToAST(table) {
|
|
|
405
405
|
return { name: p.$name, partitionBound };
|
|
406
406
|
});
|
|
407
407
|
}
|
|
408
|
+
for (const con of constraints) {
|
|
409
|
+
if (con.type === 'PRIMARY KEY' && con.columns.length > 0) {
|
|
410
|
+
for (const colName of con.columns) {
|
|
411
|
+
const col = columns.find(c => c.name === colName || c.tsName === colName);
|
|
412
|
+
if (col)
|
|
413
|
+
col.isPrimaryKey = true;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
if (con.type === 'UNIQUE' && con.columns.length === 1) {
|
|
417
|
+
const col = columns.find(c => c.name === con.columns[0] || c.tsName === con.columns[0]);
|
|
418
|
+
if (col)
|
|
419
|
+
col.isUnique = true;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
408
422
|
return {
|
|
409
423
|
name: table.$name,
|
|
410
424
|
schema: table.$schema || 'public',
|
|
@@ -11,7 +11,7 @@ import { getConnectionDescription } from "../utils/env-loader.js";
|
|
|
11
11
|
import { isInitialized } from "../utils/repo-manager.js";
|
|
12
12
|
import { validateSchemaFile, formatValidationErrors } from "../utils/schema-validator.js";
|
|
13
13
|
import { loadSchemaFile } from "../utils/schema-loader.js";
|
|
14
|
-
import { diffSchemas, filterDiff, hasDestructiveChanges, getDestructiveTables, stripDestructiveChanges } from "../utils/schema-diff.js";
|
|
14
|
+
import { diffSchemas, filterDiff, hasDestructiveChanges, getDestructiveTables, stripDestructiveChanges, formatCategorizedSummary } from "../utils/schema-diff.js";
|
|
15
15
|
import { normalizeSchema } from "../utils/schema-hash.js";
|
|
16
16
|
import { generateMigrationFile, generateMigrationName, getNextMigrationNumber, generateTimestampedName } from "../utils/migration-generator.js";
|
|
17
17
|
import { loadRelqignore } from "../utils/relqignore.js";
|
|
@@ -121,21 +121,11 @@ export default defineCommand({
|
|
|
121
121
|
console.log('');
|
|
122
122
|
return;
|
|
123
123
|
}
|
|
124
|
-
const s = filteredDiff.summary;
|
|
125
124
|
console.log('');
|
|
126
125
|
console.log(`${colors.bold('Changes to include:')}`);
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
console.log(` ${colors.red('-')} ${s.tablesRemoved} table(s) to drop`);
|
|
131
|
-
if (s.tablesModified > 0)
|
|
132
|
-
console.log(` ${colors.yellow('~')} ${s.tablesModified} table(s) modified`);
|
|
133
|
-
if (s.columnsAdded > 0)
|
|
134
|
-
console.log(` ${colors.green('+')} ${s.columnsAdded} column(s) to add`);
|
|
135
|
-
if (s.columnsRemoved > 0)
|
|
136
|
-
console.log(` ${colors.red('-')} ${s.columnsRemoved} column(s) to drop`);
|
|
137
|
-
if (s.columnsModified > 0)
|
|
138
|
-
console.log(` ${colors.yellow('~')} ${s.columnsModified} column(s) modified`);
|
|
126
|
+
const changeSummaryLines = formatCategorizedSummary(filteredDiff);
|
|
127
|
+
for (const line of changeSummaryLines)
|
|
128
|
+
console.log(line);
|
|
139
129
|
console.log('');
|
|
140
130
|
if (hasDestructiveChanges(filteredDiff)) {
|
|
141
131
|
const tables = getDestructiveTables(filteredDiff);
|
|
@@ -170,7 +160,7 @@ export default defineCommand({
|
|
|
170
160
|
includeDown: !noDown,
|
|
171
161
|
includeComments: true,
|
|
172
162
|
});
|
|
173
|
-
spin.stop(
|
|
163
|
+
spin.stop(`Migration generated — ${migration.upSQL.length} statement(s)`);
|
|
174
164
|
let fileName;
|
|
175
165
|
if (format === 'timestamp') {
|
|
176
166
|
fileName = generateTimestampedName(migrationName) + '.sql';
|
|
@@ -10,7 +10,7 @@ import { requireValidConfig } from "../utils/config-loader.js";
|
|
|
10
10
|
import { getConnectionDescription } from "../utils/env-loader.js";
|
|
11
11
|
import { createDatabaseClient } from "../utils/database-client.js";
|
|
12
12
|
import { detectDialect } from "../utils/dialect-router.js";
|
|
13
|
-
import { getQuoteChar, getParamPlaceholder, getMigrationTableDDL, parseMigration, } from "../utils/migration-helpers.js";
|
|
13
|
+
import { getQuoteChar, getParamPlaceholder, getMigrationTableDDL, parseMigration, splitStatements, } from "../utils/migration-helpers.js";
|
|
14
14
|
export default defineCommand({
|
|
15
15
|
meta: { name: 'migrate', description: 'Apply pending migrations' },
|
|
16
16
|
args: {
|
|
@@ -118,9 +118,15 @@ export default defineCommand({
|
|
|
118
118
|
const executeQuery = tx
|
|
119
119
|
? (sql, params) => tx.query(sql, params)
|
|
120
120
|
: (sql, params) => dbClient.query(sql, params);
|
|
121
|
-
|
|
122
|
-
const
|
|
123
|
-
|
|
121
|
+
const statements = splitStatements(up);
|
|
122
|
+
for (const stmt of statements) {
|
|
123
|
+
await executeQuery(stmt);
|
|
124
|
+
}
|
|
125
|
+
const p1 = await getParamPlaceholder(dialect, 1);
|
|
126
|
+
const p2 = await getParamPlaceholder(dialect, 2);
|
|
127
|
+
const p3 = await getParamPlaceholder(dialect, 3);
|
|
128
|
+
const p4 = await getParamPlaceholder(dialect, 4);
|
|
129
|
+
await executeQuery(`INSERT INTO ${quote}${tableName}${quote} (name, filename, hash, batch) VALUES (${p1}, ${p2}, ${p3}, ${p4})`, [file.replace(/\.sql$/, ''), file, file, applied + 1]);
|
|
124
130
|
if (tx)
|
|
125
131
|
await tx.commit();
|
|
126
132
|
spin.stop(`Applied ${file}`);
|
|
@@ -17,7 +17,7 @@ import { loadRelqignore } from "../utils/relqignore.js";
|
|
|
17
17
|
import { isInitialized } from "../utils/repo-manager.js";
|
|
18
18
|
import { saveSnapshot } from "../utils/snapshot-manager.js";
|
|
19
19
|
import { loadSchemaFile } from "../utils/schema-loader.js";
|
|
20
|
-
import { diffSchemas, filterDiff, hasDestructiveChanges, getDestructiveTables, stripDestructiveChanges } from "../utils/schema-diff.js";
|
|
20
|
+
import { diffSchemas, filterDiff, hasDestructiveChanges, getDestructiveTables, stripDestructiveChanges, formatCategorizedSummary } from "../utils/schema-diff.js";
|
|
21
21
|
import { normalizeSchema } from "../utils/schema-hash.js";
|
|
22
22
|
import { generateMigrationFile, generateMigrationName, generateTimestampedName } from "../utils/migration-generator.js";
|
|
23
23
|
import { getMigrationTableDDL } from "../utils/migration-helpers.js";
|
|
@@ -200,6 +200,12 @@ export async function runPush(config, projectRoot, opts = {}) {
|
|
|
200
200
|
});
|
|
201
201
|
const upStatements = migration.upSQL;
|
|
202
202
|
spin.stop(`Generated ${upStatements.length} statement(s)`);
|
|
203
|
+
const summaryLines = formatCategorizedSummary(filteredDiff);
|
|
204
|
+
if (summaryLines.length > 0) {
|
|
205
|
+
for (const line of summaryLines)
|
|
206
|
+
console.log(line);
|
|
207
|
+
console.log('');
|
|
208
|
+
}
|
|
203
209
|
const dialect = detectDialect(config);
|
|
204
210
|
if (dialect !== 'postgres' && upStatements.length > 0) {
|
|
205
211
|
const upSQL = upStatements.join('\n');
|
|
@@ -241,6 +247,7 @@ export async function runPush(config, projectRoot, opts = {}) {
|
|
|
241
247
|
spin.start('Applying schema changes...');
|
|
242
248
|
const execClient = await createDatabaseClient(config);
|
|
243
249
|
let tx = null;
|
|
250
|
+
let statementsRun = 0;
|
|
244
251
|
try {
|
|
245
252
|
try {
|
|
246
253
|
tx = await execClient.beginTransaction();
|
|
@@ -252,7 +259,6 @@ export async function runPush(config, projectRoot, opts = {}) {
|
|
|
252
259
|
const executeQuery = tx
|
|
253
260
|
? (sql) => tx.query(sql)
|
|
254
261
|
: (sql) => execClient.query(sql);
|
|
255
|
-
let statementsRun = 0;
|
|
256
262
|
for (const stmt of upStatements) {
|
|
257
263
|
try {
|
|
258
264
|
await executeQuery(stmt);
|
|
@@ -267,7 +273,6 @@ export async function runPush(config, projectRoot, opts = {}) {
|
|
|
267
273
|
}
|
|
268
274
|
if (tx)
|
|
269
275
|
await tx.commit();
|
|
270
|
-
spin.stop(`Applied ${statementsRun} statement(s)`);
|
|
271
276
|
try {
|
|
272
277
|
const tableName = config.migrations?.tableName || '_relq_migrations';
|
|
273
278
|
const tableDDL = await getMigrationTableDDL(config, tableName);
|
|
@@ -283,10 +288,11 @@ export async function runPush(config, projectRoot, opts = {}) {
|
|
|
283
288
|
const hash = generateTimestampedName(migrationName);
|
|
284
289
|
await execClient.query(`INSERT INTO "${tableName}" (name, filename, hash, batch, sql_up, sql_down, source) ` +
|
|
285
290
|
`VALUES ($1, $2, $3, 0, $4, $5, 'push')`, [migrationName, '', hash, upSQL, downSQL]);
|
|
286
|
-
spin.stop(`Applied ${statementsRun} statement(s) —
|
|
291
|
+
spin.stop(`Applied ${statementsRun} statement(s) — restore point saved`);
|
|
287
292
|
}
|
|
288
293
|
catch (recordError) {
|
|
289
|
-
|
|
294
|
+
spin.stop(`Applied ${statementsRun} statement(s)`);
|
|
295
|
+
warning(`Failed to save restore point: ${recordError?.message || String(recordError)}`);
|
|
290
296
|
}
|
|
291
297
|
}
|
|
292
298
|
catch (error) {
|
|
@@ -304,7 +310,12 @@ export async function runPush(config, projectRoot, opts = {}) {
|
|
|
304
310
|
if (error.statementIndex) {
|
|
305
311
|
errorMsg += `${colors.yellow('Statement #:')} ${error.statementIndex}\n`;
|
|
306
312
|
}
|
|
307
|
-
|
|
313
|
+
if (tx) {
|
|
314
|
+
errorMsg += `\n${colors.muted('All changes rolled back.')}\n`;
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
errorMsg += `\n${colors.yellow('Warning:')} ${statementsRun} statement(s) were already applied (no transaction support).\n`;
|
|
318
|
+
}
|
|
308
319
|
spin.error('SQL execution failed');
|
|
309
320
|
throw new Error(errorMsg);
|
|
310
321
|
}
|
|
@@ -295,6 +295,39 @@ function generateColumnModification(tableName, columnName, changes) {
|
|
|
295
295
|
downSQL.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" DROP DEFAULT;`);
|
|
296
296
|
}
|
|
297
297
|
break;
|
|
298
|
+
case 'length': {
|
|
299
|
+
const newLen = change.to;
|
|
300
|
+
const oldLen = change.from;
|
|
301
|
+
const newFullType = newLen ? `VARCHAR(${newLen})` : 'VARCHAR';
|
|
302
|
+
const oldFullType = oldLen ? `VARCHAR(${oldLen})` : 'VARCHAR';
|
|
303
|
+
upSQL.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" TYPE ${newFullType};`);
|
|
304
|
+
downSQL.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" TYPE ${oldFullType};`);
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
case 'unique': {
|
|
308
|
+
const constraintName = `${tableName}_${columnName}_key`;
|
|
309
|
+
if (change.to === true) {
|
|
310
|
+
upSQL.push(`ALTER TABLE "${tableName}" ADD CONSTRAINT "${constraintName}" UNIQUE ("${columnName}");`);
|
|
311
|
+
downSQL.push(`ALTER TABLE "${tableName}" DROP CONSTRAINT IF EXISTS "${constraintName}";`);
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
upSQL.push(`ALTER TABLE "${tableName}" DROP CONSTRAINT IF EXISTS "${constraintName}";`);
|
|
315
|
+
downSQL.push(`ALTER TABLE "${tableName}" ADD CONSTRAINT "${constraintName}" UNIQUE ("${columnName}");`);
|
|
316
|
+
}
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
case 'primaryKey': {
|
|
320
|
+
const pkName = `${tableName}_pkey`;
|
|
321
|
+
if (change.to === true) {
|
|
322
|
+
upSQL.push(`ALTER TABLE "${tableName}" ADD CONSTRAINT "${pkName}" PRIMARY KEY ("${columnName}");`);
|
|
323
|
+
downSQL.push(`ALTER TABLE "${tableName}" DROP CONSTRAINT IF EXISTS "${pkName}";`);
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
upSQL.push(`ALTER TABLE "${tableName}" DROP CONSTRAINT IF EXISTS "${pkName}";`);
|
|
327
|
+
downSQL.push(`ALTER TABLE "${tableName}" ADD CONSTRAINT "${pkName}" PRIMARY KEY ("${columnName}");`);
|
|
328
|
+
}
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
298
331
|
}
|
|
299
332
|
}
|
|
300
333
|
return { upSQL, downSQL };
|
|
@@ -508,6 +541,41 @@ function generateColumnChange(tableName, col) {
|
|
|
508
541
|
}
|
|
509
542
|
break;
|
|
510
543
|
}
|
|
544
|
+
case 'length': {
|
|
545
|
+
const colType = col.after?.dataType ?? col.before?.dataType ?? 'VARCHAR';
|
|
546
|
+
const baseType = colType.toUpperCase().replace(/\(.*\)/, '');
|
|
547
|
+
const newLen = change.to;
|
|
548
|
+
const oldLen = change.from;
|
|
549
|
+
const newFullType = newLen ? `${baseType}(${newLen})` : baseType;
|
|
550
|
+
const oldFullType = oldLen ? `${baseType}(${oldLen})` : baseType;
|
|
551
|
+
up.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${col.name}" TYPE ${newFullType};`);
|
|
552
|
+
down.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${col.name}" TYPE ${oldFullType};`);
|
|
553
|
+
break;
|
|
554
|
+
}
|
|
555
|
+
case 'unique': {
|
|
556
|
+
const constraintName = `${tableName}_${col.name}_key`;
|
|
557
|
+
if (change.to === true) {
|
|
558
|
+
up.push(`ALTER TABLE "${tableName}" ADD CONSTRAINT "${constraintName}" UNIQUE ("${col.name}");`);
|
|
559
|
+
down.push(`ALTER TABLE "${tableName}" DROP CONSTRAINT IF EXISTS "${constraintName}";`);
|
|
560
|
+
}
|
|
561
|
+
else {
|
|
562
|
+
up.push(`ALTER TABLE "${tableName}" DROP CONSTRAINT IF EXISTS "${constraintName}";`);
|
|
563
|
+
down.push(`ALTER TABLE "${tableName}" ADD CONSTRAINT "${constraintName}" UNIQUE ("${col.name}");`);
|
|
564
|
+
}
|
|
565
|
+
break;
|
|
566
|
+
}
|
|
567
|
+
case 'primaryKey': {
|
|
568
|
+
const pkName = `${tableName}_pkey`;
|
|
569
|
+
if (change.to === true) {
|
|
570
|
+
up.push(`ALTER TABLE "${tableName}" ADD CONSTRAINT "${pkName}" PRIMARY KEY ("${col.name}");`);
|
|
571
|
+
down.push(`ALTER TABLE "${tableName}" DROP CONSTRAINT IF EXISTS "${pkName}";`);
|
|
572
|
+
}
|
|
573
|
+
else {
|
|
574
|
+
up.push(`ALTER TABLE "${tableName}" DROP CONSTRAINT IF EXISTS "${pkName}";`);
|
|
575
|
+
down.push(`ALTER TABLE "${tableName}" ADD CONSTRAINT "${pkName}" PRIMARY KEY ("${col.name}");`);
|
|
576
|
+
}
|
|
577
|
+
break;
|
|
578
|
+
}
|
|
511
579
|
}
|
|
512
580
|
}
|
|
513
581
|
}
|
|
@@ -247,8 +247,13 @@ export async function introspectPostgres(connection, options) {
|
|
|
247
247
|
continue;
|
|
248
248
|
const normalizedColName = rawColName.replace(/^"|"$/g, '').toLowerCase();
|
|
249
249
|
const col = columns.find(c => c.name.toLowerCase() === normalizedColName);
|
|
250
|
-
if (col
|
|
251
|
-
|
|
250
|
+
if (col) {
|
|
251
|
+
if (con.constraint_type === 'PRIMARY KEY') {
|
|
252
|
+
col.isPrimaryKey = true;
|
|
253
|
+
}
|
|
254
|
+
if (con.constraint_type === 'UNIQUE' && constraintColumns.length === 1) {
|
|
255
|
+
col.isUnique = true;
|
|
256
|
+
}
|
|
252
257
|
}
|
|
253
258
|
}
|
|
254
259
|
}
|
|
@@ -256,12 +256,14 @@ function diffTables(local, remote) {
|
|
|
256
256
|
if (remoteTable) {
|
|
257
257
|
const columnDiffs = diffColumns(localTable.columns, remoteTable.columns);
|
|
258
258
|
const indexDiffs = diffIndexes(localTable.indexes, remoteTable.indexes);
|
|
259
|
-
|
|
259
|
+
const constraintDiffs = diffConstraints(localTable.constraints, remoteTable.constraints);
|
|
260
|
+
if (columnDiffs.length > 0 || indexDiffs.length > 0 || constraintDiffs.length > 0) {
|
|
260
261
|
diffs.push({
|
|
261
262
|
name,
|
|
262
263
|
type: 'modified',
|
|
263
264
|
columns: columnDiffs,
|
|
264
265
|
indexes: indexDiffs,
|
|
266
|
+
constraints: constraintDiffs,
|
|
265
267
|
});
|
|
266
268
|
}
|
|
267
269
|
}
|
|
@@ -330,6 +332,37 @@ function diffIndexes(local, remote) {
|
|
|
330
332
|
}
|
|
331
333
|
return diffs;
|
|
332
334
|
}
|
|
335
|
+
function diffConstraints(local, remote) {
|
|
336
|
+
const diffs = [];
|
|
337
|
+
const sig = (c) => {
|
|
338
|
+
const def = (c.definition || '').replace(/"/g, '').replace(/\s+/g, ' ').trim().toLowerCase();
|
|
339
|
+
return `${c.type}:${def}`;
|
|
340
|
+
};
|
|
341
|
+
const localSigs = new Map();
|
|
342
|
+
const remoteSigs = new Map();
|
|
343
|
+
for (const c of local)
|
|
344
|
+
localSigs.set(sig(c), c);
|
|
345
|
+
for (const c of remote)
|
|
346
|
+
remoteSigs.set(sig(c), c);
|
|
347
|
+
const toConstraintInfo = (c) => ({
|
|
348
|
+
name: c.name,
|
|
349
|
+
type: (c.type || 'CHECK'),
|
|
350
|
+
columns: [],
|
|
351
|
+
definition: c.definition || '',
|
|
352
|
+
trackingId: c.trackingId,
|
|
353
|
+
});
|
|
354
|
+
for (const [s, c] of remoteSigs) {
|
|
355
|
+
if (!localSigs.has(s)) {
|
|
356
|
+
diffs.push({ name: c.name || c.definition || 'unnamed', type: 'added', after: toConstraintInfo(c) });
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
for (const [s, c] of localSigs) {
|
|
360
|
+
if (!remoteSigs.has(s)) {
|
|
361
|
+
diffs.push({ name: c.name || c.definition || 'unnamed', type: 'removed', before: toConstraintInfo(c) });
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return diffs;
|
|
365
|
+
}
|
|
333
366
|
function diffExtensions(local, remote) {
|
|
334
367
|
const diffs = [];
|
|
335
368
|
const localSet = new Set(local);
|
|
@@ -416,6 +449,11 @@ export function formatDiff(diff) {
|
|
|
416
449
|
colors.red(' -');
|
|
417
450
|
lines.push(`${idxIcon} index: ${idx.name}`);
|
|
418
451
|
}
|
|
452
|
+
for (const con of table.constraints || []) {
|
|
453
|
+
const conIcon = con.type === 'added' ? colors.green(' +') :
|
|
454
|
+
colors.red(' -');
|
|
455
|
+
lines.push(`${conIcon} constraint: ${con.name}`);
|
|
456
|
+
}
|
|
419
457
|
}
|
|
420
458
|
for (const ext of diff.extensions) {
|
|
421
459
|
const icon = ext.type === 'added' ? colors.green('+') : colors.red('-');
|
|
@@ -462,6 +500,105 @@ export function formatSummary(diff) {
|
|
|
462
500
|
}
|
|
463
501
|
return parts.join(', ');
|
|
464
502
|
}
|
|
503
|
+
export function formatCategorizedSummary(diff) {
|
|
504
|
+
let tablesAdded = 0, tablesRemoved = 0, tablesModified = 0;
|
|
505
|
+
let colsAdded = 0, colsRemoved = 0, colsModified = 0;
|
|
506
|
+
let idxAdded = 0, idxRemoved = 0;
|
|
507
|
+
let fkAdded = 0, fkRemoved = 0;
|
|
508
|
+
let uniqueAdded = 0, uniqueRemoved = 0;
|
|
509
|
+
let pkAdded = 0, pkRemoved = 0;
|
|
510
|
+
let checkAdded = 0, checkRemoved = 0;
|
|
511
|
+
let extAdded = 0, extRemoved = 0;
|
|
512
|
+
for (const table of diff.tables) {
|
|
513
|
+
if (table.type === 'added')
|
|
514
|
+
tablesAdded++;
|
|
515
|
+
else if (table.type === 'removed')
|
|
516
|
+
tablesRemoved++;
|
|
517
|
+
else if (table.type === 'modified')
|
|
518
|
+
tablesModified++;
|
|
519
|
+
for (const col of table.columns || []) {
|
|
520
|
+
if (col.type === 'added')
|
|
521
|
+
colsAdded++;
|
|
522
|
+
else if (col.type === 'removed')
|
|
523
|
+
colsRemoved++;
|
|
524
|
+
else if (col.type === 'modified')
|
|
525
|
+
colsModified++;
|
|
526
|
+
}
|
|
527
|
+
for (const idx of table.indexes || []) {
|
|
528
|
+
if (idx.type === 'added')
|
|
529
|
+
idxAdded++;
|
|
530
|
+
else if (idx.type === 'removed')
|
|
531
|
+
idxRemoved++;
|
|
532
|
+
}
|
|
533
|
+
for (const con of table.constraints || []) {
|
|
534
|
+
const name = (con.name || '').toLowerCase();
|
|
535
|
+
const isFk = name.includes('fkey') || name.includes('foreign');
|
|
536
|
+
const isUq = name.includes('unique') || name.includes('_key');
|
|
537
|
+
const isPk = name.includes('pkey') || name.includes('primary');
|
|
538
|
+
const before = con.before;
|
|
539
|
+
const after = con.after;
|
|
540
|
+
const conType = (before?.type || after?.type || '').toUpperCase();
|
|
541
|
+
if (conType === 'FOREIGN KEY' || isFk) {
|
|
542
|
+
if (con.type === 'added')
|
|
543
|
+
fkAdded++;
|
|
544
|
+
else if (con.type === 'removed')
|
|
545
|
+
fkRemoved++;
|
|
546
|
+
}
|
|
547
|
+
else if (conType === 'UNIQUE' || isUq) {
|
|
548
|
+
if (con.type === 'added')
|
|
549
|
+
uniqueAdded++;
|
|
550
|
+
else if (con.type === 'removed')
|
|
551
|
+
uniqueRemoved++;
|
|
552
|
+
}
|
|
553
|
+
else if (conType === 'PRIMARY KEY' || isPk) {
|
|
554
|
+
if (con.type === 'added')
|
|
555
|
+
pkAdded++;
|
|
556
|
+
else if (con.type === 'removed')
|
|
557
|
+
pkRemoved++;
|
|
558
|
+
}
|
|
559
|
+
else if (conType === 'CHECK') {
|
|
560
|
+
if (con.type === 'added')
|
|
561
|
+
checkAdded++;
|
|
562
|
+
else if (con.type === 'removed')
|
|
563
|
+
checkRemoved++;
|
|
564
|
+
}
|
|
565
|
+
else {
|
|
566
|
+
if (con.type === 'added')
|
|
567
|
+
checkAdded++;
|
|
568
|
+
else if (con.type === 'removed')
|
|
569
|
+
checkRemoved++;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
for (const ext of diff.extensions) {
|
|
574
|
+
if (ext.type === 'added')
|
|
575
|
+
extAdded++;
|
|
576
|
+
else if (ext.type === 'removed')
|
|
577
|
+
extRemoved++;
|
|
578
|
+
}
|
|
579
|
+
const lines = [];
|
|
580
|
+
const fmt = (label, added, removed, modified = 0) => {
|
|
581
|
+
const parts = [];
|
|
582
|
+
if (added)
|
|
583
|
+
parts.push(colors.green(`${added} added`));
|
|
584
|
+
if (modified)
|
|
585
|
+
parts.push(colors.yellow(`${modified} modified`));
|
|
586
|
+
if (removed)
|
|
587
|
+
parts.push(colors.red(`${removed} removed`));
|
|
588
|
+
if (parts.length > 0) {
|
|
589
|
+
lines.push(` ${colors.muted(label + ':')} ${parts.join(', ')}`);
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
fmt('tables', tablesAdded, tablesRemoved, tablesModified);
|
|
593
|
+
fmt('columns', colsAdded, colsRemoved, colsModified);
|
|
594
|
+
fmt('indexes', idxAdded, idxRemoved);
|
|
595
|
+
fmt('primary keys', pkAdded, pkRemoved);
|
|
596
|
+
fmt('unique constraints', uniqueAdded, uniqueRemoved);
|
|
597
|
+
fmt('foreign keys', fkAdded, fkRemoved);
|
|
598
|
+
fmt('check constraints', checkAdded, checkRemoved);
|
|
599
|
+
fmt('extensions', extAdded, extRemoved);
|
|
600
|
+
return lines;
|
|
601
|
+
}
|
|
465
602
|
export function filterDiff(diff, ignorePatterns) {
|
|
466
603
|
const patterns = ignorePatterns.map(p => {
|
|
467
604
|
const regexStr = p.replace(/\*/g, '.*').replace(/\?/g, '.');
|
|
@@ -371,6 +371,20 @@ export function tableToAST(table) {
|
|
|
371
371
|
return { name: p.$name, partitionBound };
|
|
372
372
|
});
|
|
373
373
|
}
|
|
374
|
+
for (const con of constraints) {
|
|
375
|
+
if (con.type === 'PRIMARY KEY' && con.columns.length > 0) {
|
|
376
|
+
for (const colName of con.columns) {
|
|
377
|
+
const col = columns.find(c => c.name === colName || c.tsName === colName);
|
|
378
|
+
if (col)
|
|
379
|
+
col.isPrimaryKey = true;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
if (con.type === 'UNIQUE' && con.columns.length === 1) {
|
|
383
|
+
const col = columns.find(c => c.name === con.columns[0] || c.tsName === con.columns[0]);
|
|
384
|
+
if (col)
|
|
385
|
+
col.isUnique = true;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
374
388
|
return {
|
|
375
389
|
name: table.$name,
|
|
376
390
|
schema: table.$schema || 'public',
|