tempest-db-js 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/bin.cjs +342 -14
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +2 -2
- package/dist/{chunk-OP7FRDI5.js → chunk-43XL66JG.js} +352 -14
- package/dist/chunk-43XL66JG.js.map +1 -0
- package/dist/{chunk-Q32CBI2A.js → chunk-JR4MLFQN.js} +87 -9
- package/dist/chunk-JR4MLFQN.js.map +1 -0
- package/dist/index.cjs +86 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +102 -3
- package/dist/index.d.ts +102 -3
- package/dist/index.js +1 -1
- package/dist/migrations/index.cjs +403 -16
- package/dist/migrations/index.cjs.map +1 -1
- package/dist/migrations/index.d.cts +51 -12
- package/dist/migrations/index.d.ts +51 -12
- package/dist/migrations/index.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-OP7FRDI5.js.map +0 -1
- package/dist/chunk-Q32CBI2A.js.map +0 -1
package/dist/bin.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { runMigrationCli, diffSchema, replaySchema, reflectSchema, detectRenames } from './chunk-
|
|
3
|
-
import './chunk-
|
|
2
|
+
import { runMigrationCli, diffSchema, replaySchema, reflectSchema, detectRenames } from './chunk-43XL66JG.js';
|
|
3
|
+
import './chunk-JR4MLFQN.js';
|
|
4
4
|
import { existsSync } from 'fs';
|
|
5
5
|
import { resolve } from 'path';
|
|
6
6
|
import { createInterface } from 'readline/promises';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { columnsOf } from './chunk-
|
|
1
|
+
import { columnsOf } from './chunk-JR4MLFQN.js';
|
|
2
2
|
|
|
3
3
|
// src/migrations/operations.ts
|
|
4
4
|
var IrreversibleMigration = class extends Error {
|
|
@@ -31,6 +31,10 @@ function invert(op) {
|
|
|
31
31
|
return { kind: "rename_column", table: op.table, from: op.to, to: op.from };
|
|
32
32
|
case "recreate_table":
|
|
33
33
|
return { kind: "recreate_table", from: op.to, to: op.from };
|
|
34
|
+
case "add_constraint":
|
|
35
|
+
return { kind: "drop_constraint", table: op.table, constraint: op.constraint };
|
|
36
|
+
case "drop_constraint":
|
|
37
|
+
return { kind: "add_constraint", table: op.table, constraint: op.constraint };
|
|
34
38
|
case "execute":
|
|
35
39
|
if (op.down === null) {
|
|
36
40
|
throw new IrreversibleMigration("execute() operation has no down SQL");
|
|
@@ -220,10 +224,45 @@ function renderDefault(def, dialect) {
|
|
|
220
224
|
if (typeof value === "object") return quoteLiteral(JSON.stringify(value));
|
|
221
225
|
return quoteLiteral(String(value));
|
|
222
226
|
}
|
|
227
|
+
function renderFkAction(action) {
|
|
228
|
+
return action.toUpperCase();
|
|
229
|
+
}
|
|
230
|
+
function renderFkActions(fk) {
|
|
231
|
+
let sql = "";
|
|
232
|
+
if (fk.onDelete) sql += ` ON DELETE ${renderFkAction(fk.onDelete)}`;
|
|
233
|
+
if (fk.onUpdate) sql += ` ON UPDATE ${renderFkAction(fk.onUpdate)}`;
|
|
234
|
+
return sql;
|
|
235
|
+
}
|
|
236
|
+
function columnConstraintSuffix(col, dialect) {
|
|
237
|
+
let sql = "";
|
|
238
|
+
if (col.unique) sql += " UNIQUE";
|
|
239
|
+
if (col.references) {
|
|
240
|
+
const ref = col.references;
|
|
241
|
+
sql += ` REFERENCES ${quoteId(ref.table, dialect)} (${quoteId(ref.column, dialect)})`;
|
|
242
|
+
sql += renderFkActions(ref);
|
|
243
|
+
}
|
|
244
|
+
return sql;
|
|
245
|
+
}
|
|
246
|
+
function renderUniqueConstraint(uc, dialect) {
|
|
247
|
+
const cols = uc.columns.map((c) => quoteId(c, dialect)).join(", ");
|
|
248
|
+
return `CONSTRAINT ${quoteId(uc.name, dialect)} UNIQUE (${cols})`;
|
|
249
|
+
}
|
|
250
|
+
function renderForeignKeyConstraint(fk, dialect) {
|
|
251
|
+
const cols = fk.columns.map((c) => quoteId(c, dialect)).join(", ");
|
|
252
|
+
const refCols = fk.refColumns.map((c) => quoteId(c, dialect)).join(", ");
|
|
253
|
+
return `CONSTRAINT ${quoteId(fk.name, dialect)} FOREIGN KEY (${cols}) REFERENCES ${quoteId(fk.refTable, dialect)} (${refCols})${renderFkActions(fk)}`;
|
|
254
|
+
}
|
|
255
|
+
function tableConstraintClauses(table, dialect) {
|
|
256
|
+
return [
|
|
257
|
+
...table.uniqueConstraints.map((uc) => renderUniqueConstraint(uc, dialect)),
|
|
258
|
+
...table.foreignKeys.map((fk) => renderForeignKeyConstraint(fk, dialect))
|
|
259
|
+
];
|
|
260
|
+
}
|
|
223
261
|
function renderColumnDef(col, dialect) {
|
|
224
262
|
let sql = `${quoteId(col.name, dialect)} ${renderColumnType(col.type, dialect)}`;
|
|
225
263
|
if (col.notNull) sql += " NOT NULL";
|
|
226
264
|
if (col.default !== null) sql += ` DEFAULT ${renderDefault(col.default, dialect)}`;
|
|
265
|
+
sql += columnConstraintSuffix(col, dialect);
|
|
227
266
|
return sql;
|
|
228
267
|
}
|
|
229
268
|
function enumTypeName(table, column) {
|
|
@@ -247,13 +286,13 @@ function renderCreateTable(table, dialect) {
|
|
|
247
286
|
let def = `${quoteId(c.name, dialect)} ${quoteId(typeName, dialect)}`;
|
|
248
287
|
if (c.notNull) def += " NOT NULL";
|
|
249
288
|
if (c.default !== null) def += ` DEFAULT ${renderDefault(c.default, dialect)}`;
|
|
250
|
-
return def;
|
|
289
|
+
return def + columnConstraintSuffix(c, dialect);
|
|
251
290
|
}
|
|
252
291
|
if (dialect === "postgresql" && isAutoIncrementPk(table, c)) {
|
|
253
|
-
return `${quoteId(c.name, dialect)} ${postgresSerialType(c.type.kind)}`;
|
|
292
|
+
return `${quoteId(c.name, dialect)} ${postgresSerialType(c.type.kind)}${columnConstraintSuffix(c, dialect)}`;
|
|
254
293
|
}
|
|
255
294
|
if (dialect === "mysql" && isAutoIncrementPk(table, c)) {
|
|
256
|
-
return `${quoteId(c.name, dialect)} ${renderColumnType(c.type, dialect)} NOT NULL AUTO_INCREMENT`;
|
|
295
|
+
return `${quoteId(c.name, dialect)} ${renderColumnType(c.type, dialect)} NOT NULL AUTO_INCREMENT${columnConstraintSuffix(c, dialect)}`;
|
|
257
296
|
}
|
|
258
297
|
return renderColumnDef(c, dialect);
|
|
259
298
|
});
|
|
@@ -262,6 +301,7 @@ function renderCreateTable(table, dialect) {
|
|
|
262
301
|
`PRIMARY KEY (${table.primaryKey.map((c) => quoteId(c, dialect)).join(", ")})`
|
|
263
302
|
);
|
|
264
303
|
}
|
|
304
|
+
cols.push(...tableConstraintClauses(table, dialect));
|
|
265
305
|
return [
|
|
266
306
|
...typeStmts,
|
|
267
307
|
`CREATE TABLE ${quoteId(table.name, dialect)} (
|
|
@@ -295,10 +335,38 @@ function renderOperation(op, dialect) {
|
|
|
295
335
|
return renderAlterColumn(op.table, op.to, dialect);
|
|
296
336
|
case "recreate_table":
|
|
297
337
|
return dialect === "sqlite" ? renderSqliteRebuild(op.from, op.to) : renderTableDiff(op.from, op.to, dialect);
|
|
338
|
+
case "add_constraint":
|
|
339
|
+
return renderAddConstraint(op.table, op.constraint, dialect);
|
|
340
|
+
case "drop_constraint":
|
|
341
|
+
return renderDropConstraint(op.table, op.constraint, dialect);
|
|
298
342
|
case "execute":
|
|
299
343
|
return [op.up];
|
|
300
344
|
}
|
|
301
345
|
}
|
|
346
|
+
function renderAddConstraint(table, constraint, dialect) {
|
|
347
|
+
if (dialect === "sqlite") {
|
|
348
|
+
throw new Error(
|
|
349
|
+
`add_constraint on SQLite needs a table-rebuild (recreate_table); constraint ${constraint.constraint.name} on ${table}`
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
const clause = constraint.type === "unique" ? renderUniqueConstraint(constraint.constraint, dialect) : renderForeignKeyConstraint(constraint.constraint, dialect);
|
|
353
|
+
return [`ALTER TABLE ${quoteId(table, dialect)} ADD ${clause}`];
|
|
354
|
+
}
|
|
355
|
+
function renderDropConstraint(table, constraint, dialect) {
|
|
356
|
+
if (dialect === "sqlite") {
|
|
357
|
+
throw new Error(
|
|
358
|
+
`drop_constraint on SQLite needs a table-rebuild (recreate_table); constraint ${constraint.constraint.name} on ${table}`
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
const t = quoteId(table, dialect);
|
|
362
|
+
const name = quoteId(constraint.constraint.name, dialect);
|
|
363
|
+
if (dialect === "mysql") {
|
|
364
|
+
return [
|
|
365
|
+
constraint.type === "unique" ? `ALTER TABLE ${t} DROP INDEX ${name}` : `ALTER TABLE ${t} DROP FOREIGN KEY ${name}`
|
|
366
|
+
];
|
|
367
|
+
}
|
|
368
|
+
return [`ALTER TABLE ${t} DROP CONSTRAINT ${name}`];
|
|
369
|
+
}
|
|
302
370
|
function renderSqliteRebuild(from, to) {
|
|
303
371
|
const tmp = `__new_${to.name}`;
|
|
304
372
|
const common = Object.keys(to.columns).filter((c) => c in from.columns);
|
|
@@ -308,6 +376,7 @@ function renderSqliteRebuild(from, to) {
|
|
|
308
376
|
`PRIMARY KEY (${to.primaryKey.map((c) => quoteId(c, "sqlite")).join(", ")})`
|
|
309
377
|
);
|
|
310
378
|
}
|
|
379
|
+
cols.push(...tableConstraintClauses(to, "sqlite"));
|
|
311
380
|
const commonSql = common.map((c) => quoteId(c, "sqlite")).join(", ");
|
|
312
381
|
return [
|
|
313
382
|
"PRAGMA foreign_keys=off",
|
|
@@ -371,9 +440,74 @@ function columnSignature(col) {
|
|
|
371
440
|
type: col.type,
|
|
372
441
|
notNull: col.notNull,
|
|
373
442
|
primaryKey: col.primaryKey,
|
|
374
|
-
default: col.default
|
|
443
|
+
default: col.default,
|
|
444
|
+
unique: col.unique,
|
|
445
|
+
references: col.references
|
|
375
446
|
});
|
|
376
447
|
}
|
|
448
|
+
function uniqueSignature(uc) {
|
|
449
|
+
return JSON.stringify({ columns: uc.columns });
|
|
450
|
+
}
|
|
451
|
+
function foreignKeySignature(fk) {
|
|
452
|
+
return JSON.stringify({
|
|
453
|
+
columns: fk.columns,
|
|
454
|
+
refTable: fk.refTable,
|
|
455
|
+
refColumns: fk.refColumns,
|
|
456
|
+
onDelete: fk.onDelete ?? null,
|
|
457
|
+
onUpdate: fk.onUpdate ?? null
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
function diffConstraints(current, target) {
|
|
461
|
+
const ops = [];
|
|
462
|
+
const table = target.name;
|
|
463
|
+
const currentUq = new Map(current.uniqueConstraints.map((u) => [u.name, u]));
|
|
464
|
+
const targetUq = new Map(target.uniqueConstraints.map((u) => [u.name, u]));
|
|
465
|
+
for (const [name, cur] of currentUq) {
|
|
466
|
+
const tgt = targetUq.get(name);
|
|
467
|
+
if (!tgt || uniqueSignature(cur) !== uniqueSignature(tgt)) {
|
|
468
|
+
ops.push(dropUnique(table, cur));
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
for (const [name, tgt] of targetUq) {
|
|
472
|
+
const cur = currentUq.get(name);
|
|
473
|
+
if (!cur || uniqueSignature(cur) !== uniqueSignature(tgt)) {
|
|
474
|
+
ops.push(addUnique(table, tgt));
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
const currentFk = new Map(current.foreignKeys.map((f) => [f.name, f]));
|
|
478
|
+
const targetFk = new Map(target.foreignKeys.map((f) => [f.name, f]));
|
|
479
|
+
for (const [name, cur] of currentFk) {
|
|
480
|
+
const tgt = targetFk.get(name);
|
|
481
|
+
if (!tgt || foreignKeySignature(cur) !== foreignKeySignature(tgt)) {
|
|
482
|
+
ops.push(dropForeignKey(table, cur));
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
for (const [name, tgt] of targetFk) {
|
|
486
|
+
const cur = currentFk.get(name);
|
|
487
|
+
if (!cur || foreignKeySignature(cur) !== foreignKeySignature(tgt)) {
|
|
488
|
+
ops.push(addForeignKey(table, tgt));
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
return ops;
|
|
492
|
+
}
|
|
493
|
+
function uniqueNamed(uc) {
|
|
494
|
+
return { type: "unique", constraint: uc };
|
|
495
|
+
}
|
|
496
|
+
function foreignKeyNamed(fk) {
|
|
497
|
+
return { type: "foreignKey", constraint: fk };
|
|
498
|
+
}
|
|
499
|
+
function addUnique(table, uc) {
|
|
500
|
+
return { kind: "add_constraint", table, constraint: uniqueNamed(uc) };
|
|
501
|
+
}
|
|
502
|
+
function dropUnique(table, uc) {
|
|
503
|
+
return { kind: "drop_constraint", table, constraint: uniqueNamed(uc) };
|
|
504
|
+
}
|
|
505
|
+
function addForeignKey(table, fk) {
|
|
506
|
+
return { kind: "add_constraint", table, constraint: foreignKeyNamed(fk) };
|
|
507
|
+
}
|
|
508
|
+
function dropForeignKey(table, fk) {
|
|
509
|
+
return { kind: "drop_constraint", table, constraint: foreignKeyNamed(fk) };
|
|
510
|
+
}
|
|
377
511
|
function diffSchema(current, target) {
|
|
378
512
|
const ops = [];
|
|
379
513
|
const drops = [];
|
|
@@ -402,6 +536,7 @@ function diffSchema(current, target) {
|
|
|
402
536
|
ops.push({ kind: "drop_column", table: name, column: currentCol });
|
|
403
537
|
}
|
|
404
538
|
}
|
|
539
|
+
ops.push(...diffConstraints(currentTable, targetTable));
|
|
405
540
|
}
|
|
406
541
|
for (const [name, currentTable] of Object.entries(current.tables)) {
|
|
407
542
|
if (!target.tables[name]) {
|
|
@@ -466,6 +601,9 @@ function heads(migrations) {
|
|
|
466
601
|
}
|
|
467
602
|
|
|
468
603
|
// src/migrations/ir.ts
|
|
604
|
+
function constraintName(prefix, table, columns) {
|
|
605
|
+
return `${prefix}_${table}_${columns.join("_")}`;
|
|
606
|
+
}
|
|
469
607
|
function reflectTable(model) {
|
|
470
608
|
const columns = {};
|
|
471
609
|
const primaryKey = [];
|
|
@@ -476,11 +614,32 @@ function reflectTable(model) {
|
|
|
476
614
|
type: col.type,
|
|
477
615
|
notNull: col.flags.notNull || isPk,
|
|
478
616
|
primaryKey: isPk,
|
|
479
|
-
default: col.defaultValue
|
|
617
|
+
default: col.defaultValue,
|
|
618
|
+
unique: col.flags.unique,
|
|
619
|
+
references: col.reference
|
|
480
620
|
};
|
|
481
621
|
if (isPk) primaryKey.push(name);
|
|
482
622
|
}
|
|
483
|
-
|
|
623
|
+
const uniqueConstraints = [];
|
|
624
|
+
const foreignKeys = [];
|
|
625
|
+
for (const c of model.tableArgs?.() ?? []) {
|
|
626
|
+
if (c.kind === "unique") {
|
|
627
|
+
uniqueConstraints.push({
|
|
628
|
+
name: c.name ?? constraintName("uq", model.tablename, c.columns),
|
|
629
|
+
columns: c.columns
|
|
630
|
+
});
|
|
631
|
+
} else {
|
|
632
|
+
foreignKeys.push({
|
|
633
|
+
name: c.name ?? constraintName("fk", model.tablename, c.columns),
|
|
634
|
+
columns: c.columns,
|
|
635
|
+
refTable: c.refTable,
|
|
636
|
+
refColumns: c.refColumns,
|
|
637
|
+
onDelete: c.onDelete,
|
|
638
|
+
onUpdate: c.onUpdate
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
return { name: model.tablename, columns, primaryKey, uniqueConstraints, foreignKeys };
|
|
484
643
|
}
|
|
485
644
|
function reflectSchema(models) {
|
|
486
645
|
const tables = {};
|
|
@@ -517,6 +676,39 @@ function affinityToKind(affinity) {
|
|
|
517
676
|
return "text";
|
|
518
677
|
}
|
|
519
678
|
}
|
|
679
|
+
function sqliteForeignKeys(driver, table) {
|
|
680
|
+
const rows = driver.execute(`PRAGMA foreign_key_list(${JSON.stringify(table)})`, []).rows;
|
|
681
|
+
const byId = /* @__PURE__ */ new Map();
|
|
682
|
+
for (const r of rows) {
|
|
683
|
+
const list = byId.get(r.id) ?? [];
|
|
684
|
+
list.push(r);
|
|
685
|
+
byId.set(r.id, list);
|
|
686
|
+
}
|
|
687
|
+
const fks = [];
|
|
688
|
+
for (const group of byId.values()) {
|
|
689
|
+
const ordered = [...group].sort((a, b) => a.seq - b.seq);
|
|
690
|
+
const columns = ordered.map((r) => r.from);
|
|
691
|
+
fks.push({
|
|
692
|
+
name: `fk_${table}_${columns.join("_")}`,
|
|
693
|
+
columns,
|
|
694
|
+
refTable: ordered[0]?.table ?? "",
|
|
695
|
+
refColumns: ordered.map((r) => r.to)
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
return fks;
|
|
699
|
+
}
|
|
700
|
+
function sqliteUniques(driver, table) {
|
|
701
|
+
const indexes = driver.execute(`PRAGMA index_list(${JSON.stringify(table)})`, []).rows;
|
|
702
|
+
const uniques = [];
|
|
703
|
+
for (const idx of indexes) {
|
|
704
|
+
if (Number(idx.unique) !== 1 || idx.origin === "pk") continue;
|
|
705
|
+
const cols = driver.execute(`PRAGMA index_info(${JSON.stringify(idx.name)})`, []).rows.sort((a, b) => a.seqno - b.seqno).map((c) => c.name);
|
|
706
|
+
if (cols.length > 0) {
|
|
707
|
+
uniques.push({ name: `uq_${table}_${cols.join("_")}`, columns: cols });
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
return uniques;
|
|
711
|
+
}
|
|
520
712
|
function introspectSqlite(driver) {
|
|
521
713
|
const tablesRows = driver.execute(
|
|
522
714
|
"SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' AND name != 'tempest_db_js_migrations'",
|
|
@@ -536,14 +728,39 @@ function introspectSqlite(driver) {
|
|
|
536
728
|
type: { kind: affinityToKind(affinity), meta: {} },
|
|
537
729
|
notNull: Number(col.notnull) === 1 || isPk,
|
|
538
730
|
primaryKey: isPk,
|
|
539
|
-
default: null
|
|
731
|
+
default: null,
|
|
732
|
+
unique: false,
|
|
733
|
+
references: null
|
|
540
734
|
};
|
|
541
735
|
if (isPk) primaryKey.push(col.name);
|
|
542
736
|
}
|
|
543
|
-
tables[tableName] = {
|
|
737
|
+
tables[tableName] = {
|
|
738
|
+
name: tableName,
|
|
739
|
+
columns,
|
|
740
|
+
primaryKey,
|
|
741
|
+
uniqueConstraints: sqliteUniques(driver, tableName),
|
|
742
|
+
foreignKeys: sqliteForeignKeys(driver, tableName)
|
|
743
|
+
};
|
|
544
744
|
}
|
|
545
745
|
return { tables };
|
|
546
746
|
}
|
|
747
|
+
function constraintKeys(table) {
|
|
748
|
+
const fks = /* @__PURE__ */ new Set();
|
|
749
|
+
const uniques = /* @__PURE__ */ new Set();
|
|
750
|
+
for (const col of Object.values(table.columns)) {
|
|
751
|
+
if (col.unique) uniques.add(col.name);
|
|
752
|
+
if (col.references) {
|
|
753
|
+
fks.add(`${col.name}=>${col.references.table}(${col.references.column})`);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
for (const uc of table.uniqueConstraints) {
|
|
757
|
+
uniques.add([...uc.columns].sort().join(","));
|
|
758
|
+
}
|
|
759
|
+
for (const fk of table.foreignKeys) {
|
|
760
|
+
fks.add(`${fk.columns.join(",")}=>${fk.refTable}(${fk.refColumns.join(",")})`);
|
|
761
|
+
}
|
|
762
|
+
return { fks, uniques };
|
|
763
|
+
}
|
|
547
764
|
function checkDrift(driver, models) {
|
|
548
765
|
const actual = introspectSqlite(driver);
|
|
549
766
|
const expected = reflectSchema(models);
|
|
@@ -581,6 +798,34 @@ function checkDrift(driver, models) {
|
|
|
581
798
|
);
|
|
582
799
|
}
|
|
583
800
|
}
|
|
801
|
+
const expectedKeys = constraintKeys(expectedTable);
|
|
802
|
+
const actualKeys = constraintKeys(actualTable);
|
|
803
|
+
for (const fk of expectedKeys.fks) {
|
|
804
|
+
if (!actualKeys.fks.has(fk)) {
|
|
805
|
+
issues.push(`foreign key "${tableName}: ${fk}" is missing from the database`);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
for (const fk of actualKeys.fks) {
|
|
809
|
+
if (!expectedKeys.fks.has(fk)) {
|
|
810
|
+
issues.push(
|
|
811
|
+
`foreign key "${tableName}: ${fk}" exists in the database but not in the model`
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
for (const uq of expectedKeys.uniques) {
|
|
816
|
+
if (!actualKeys.uniques.has(uq)) {
|
|
817
|
+
issues.push(
|
|
818
|
+
`unique constraint "${tableName}: (${uq})" is missing from the database`
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
for (const uq of actualKeys.uniques) {
|
|
823
|
+
if (!expectedKeys.uniques.has(uq)) {
|
|
824
|
+
issues.push(
|
|
825
|
+
`unique constraint "${tableName}: (${uq})" exists in the database but not in the model`
|
|
826
|
+
);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
584
829
|
}
|
|
585
830
|
for (const tableName of Object.keys(actual.tables)) {
|
|
586
831
|
if (!expected.tables[tableName]) {
|
|
@@ -642,14 +887,59 @@ async function introspectPostgres(driver) {
|
|
|
642
887
|
},
|
|
643
888
|
notNull: col.is_nullable === "NO" || isPk,
|
|
644
889
|
primaryKey: isPk,
|
|
645
|
-
default: null
|
|
890
|
+
default: null,
|
|
891
|
+
unique: false,
|
|
892
|
+
references: null
|
|
646
893
|
};
|
|
647
894
|
if (isPk) primaryKey.push(name);
|
|
648
895
|
}
|
|
649
|
-
tables[tableName] = {
|
|
896
|
+
tables[tableName] = {
|
|
897
|
+
name: tableName,
|
|
898
|
+
columns,
|
|
899
|
+
primaryKey,
|
|
900
|
+
uniqueConstraints: await postgresUniques(driver, tableName),
|
|
901
|
+
foreignKeys: await postgresForeignKeys(driver, tableName)
|
|
902
|
+
};
|
|
650
903
|
}
|
|
651
904
|
return { tables };
|
|
652
905
|
}
|
|
906
|
+
async function postgresForeignKeys(driver, table) {
|
|
907
|
+
const result = await driver.execute(
|
|
908
|
+
`SELECT c.conname AS name,
|
|
909
|
+
(SELECT array_agg(a.attname ORDER BY k.ord)
|
|
910
|
+
FROM unnest(c.conkey) WITH ORDINALITY AS k(attnum, ord)
|
|
911
|
+
JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = k.attnum) AS cols,
|
|
912
|
+
cf.relname AS ref_table,
|
|
913
|
+
(SELECT array_agg(a.attname ORDER BY k.ord)
|
|
914
|
+
FROM unnest(c.confkey) WITH ORDINALITY AS k(attnum, ord)
|
|
915
|
+
JOIN pg_attribute a ON a.attrelid = c.confrelid AND a.attnum = k.attnum) AS ref_cols
|
|
916
|
+
FROM pg_constraint c
|
|
917
|
+
JOIN pg_class cf ON cf.oid = c.confrelid
|
|
918
|
+
WHERE c.contype = 'f' AND c.conrelid = $1::regclass`,
|
|
919
|
+
[table]
|
|
920
|
+
);
|
|
921
|
+
return result.rows.map((r) => ({
|
|
922
|
+
name: String(r.name),
|
|
923
|
+
columns: r.cols ?? [],
|
|
924
|
+
refTable: String(r.ref_table),
|
|
925
|
+
refColumns: r.ref_cols ?? []
|
|
926
|
+
}));
|
|
927
|
+
}
|
|
928
|
+
async function postgresUniques(driver, table) {
|
|
929
|
+
const result = await driver.execute(
|
|
930
|
+
`SELECT c.conname AS name,
|
|
931
|
+
(SELECT array_agg(a.attname ORDER BY k.ord)
|
|
932
|
+
FROM unnest(c.conkey) WITH ORDINALITY AS k(attnum, ord)
|
|
933
|
+
JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = k.attnum) AS cols
|
|
934
|
+
FROM pg_constraint c
|
|
935
|
+
WHERE c.contype = 'u' AND c.conrelid = $1::regclass`,
|
|
936
|
+
[table]
|
|
937
|
+
);
|
|
938
|
+
return result.rows.map((r) => ({
|
|
939
|
+
name: String(r.name),
|
|
940
|
+
columns: r.cols ?? []
|
|
941
|
+
}));
|
|
942
|
+
}
|
|
653
943
|
async function checkDriftPostgres(driver, models) {
|
|
654
944
|
const actual = await introspectPostgres(driver);
|
|
655
945
|
const expected = reflectSchema(models);
|
|
@@ -682,6 +972,20 @@ async function checkDriftPostgres(driver, models) {
|
|
|
682
972
|
);
|
|
683
973
|
}
|
|
684
974
|
}
|
|
975
|
+
const expectedKeys = constraintKeys(expectedTable);
|
|
976
|
+
const actualKeys = constraintKeys(actualTable);
|
|
977
|
+
for (const fk of expectedKeys.fks) {
|
|
978
|
+
if (!actualKeys.fks.has(fk)) {
|
|
979
|
+
issues.push(`foreign key "${tableName}: ${fk}" is missing from the database`);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
for (const uq of expectedKeys.uniques) {
|
|
983
|
+
if (!actualKeys.uniques.has(uq)) {
|
|
984
|
+
issues.push(
|
|
985
|
+
`unique constraint "${tableName}: (${uq})" is missing from the database`
|
|
986
|
+
);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
685
989
|
}
|
|
686
990
|
for (const tableName of Object.keys(actual.tables)) {
|
|
687
991
|
if (!expected.tables[tableName]) {
|
|
@@ -697,7 +1001,9 @@ function columnShape(col) {
|
|
|
697
1001
|
type: col.type,
|
|
698
1002
|
notNull: col.notNull,
|
|
699
1003
|
primaryKey: col.primaryKey,
|
|
700
|
-
default: col.default
|
|
1004
|
+
default: col.default,
|
|
1005
|
+
unique: col.unique,
|
|
1006
|
+
references: col.references
|
|
701
1007
|
});
|
|
702
1008
|
}
|
|
703
1009
|
function tableShape(columns) {
|
|
@@ -816,6 +1122,14 @@ var Op = class {
|
|
|
816
1122
|
recreateTable(from, to) {
|
|
817
1123
|
this.run({ kind: "recreate_table", from, to });
|
|
818
1124
|
}
|
|
1125
|
+
/** Add a table-level unique / foreign-key constraint. */
|
|
1126
|
+
addConstraint(table, constraint) {
|
|
1127
|
+
this.run({ kind: "add_constraint", table, constraint });
|
|
1128
|
+
}
|
|
1129
|
+
/** Drop a table-level unique / foreign-key constraint. */
|
|
1130
|
+
dropConstraint(table, constraint) {
|
|
1131
|
+
this.run({ kind: "drop_constraint", table, constraint });
|
|
1132
|
+
}
|
|
819
1133
|
/** Raw SQL escape hatch (e.g. a data migration). `down` may be `null`. */
|
|
820
1134
|
execute(up, down = null) {
|
|
821
1135
|
this.run({ kind: "execute", up, down });
|
|
@@ -1084,6 +1398,30 @@ function applyOperation(schema, op) {
|
|
|
1084
1398
|
}
|
|
1085
1399
|
break;
|
|
1086
1400
|
}
|
|
1401
|
+
case "add_constraint": {
|
|
1402
|
+
const t = tables[op.table];
|
|
1403
|
+
if (t) {
|
|
1404
|
+
tables[op.table] = op.constraint.type === "unique" ? {
|
|
1405
|
+
...t,
|
|
1406
|
+
uniqueConstraints: [...t.uniqueConstraints, op.constraint.constraint]
|
|
1407
|
+
} : { ...t, foreignKeys: [...t.foreignKeys, op.constraint.constraint] };
|
|
1408
|
+
}
|
|
1409
|
+
break;
|
|
1410
|
+
}
|
|
1411
|
+
case "drop_constraint": {
|
|
1412
|
+
const t = tables[op.table];
|
|
1413
|
+
if (t) {
|
|
1414
|
+
const dropName = op.constraint.constraint.name;
|
|
1415
|
+
tables[op.table] = op.constraint.type === "unique" ? {
|
|
1416
|
+
...t,
|
|
1417
|
+
uniqueConstraints: t.uniqueConstraints.filter((u) => u.name !== dropName)
|
|
1418
|
+
} : {
|
|
1419
|
+
...t,
|
|
1420
|
+
foreignKeys: t.foreignKeys.filter((f) => f.name !== dropName)
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
break;
|
|
1424
|
+
}
|
|
1087
1425
|
}
|
|
1088
1426
|
return { tables };
|
|
1089
1427
|
}
|
|
@@ -1221,5 +1559,5 @@ function runMigrationCli(argv, config) {
|
|
|
1221
1559
|
}
|
|
1222
1560
|
|
|
1223
1561
|
export { AsyncMigrationRunner, CyclicMigrationGraph, IrreversibleMigration, MigrationRunner, Op, UnknownRevision, applyOperation, applyRenames, checkDrift, checkDriftPostgres, defineMigrationConfig, detectRenames, diffSchema, emptySchema, generateMigration, heads, introspectPostgres, introspectSqlite, invert, invertAll, makeRevisionId, reflectSchema, reflectTable, renderColumnDef, renderColumnType, renderDefault, renderOperation, replaySchema, runMigrationCli, sqliteAffinity, topoOrder };
|
|
1224
|
-
//# sourceMappingURL=chunk-
|
|
1225
|
-
//# sourceMappingURL=chunk-
|
|
1562
|
+
//# sourceMappingURL=chunk-43XL66JG.js.map
|
|
1563
|
+
//# sourceMappingURL=chunk-43XL66JG.js.map
|