tempest-db-js 0.3.0 → 0.5.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 +9 -2
- package/dist/bin.cjs +475 -44
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +2 -2
- package/dist/{chunk-Q32CBI2A.js → chunk-5QQMVTS5.js} +988 -286
- package/dist/chunk-5QQMVTS5.js.map +1 -0
- package/dist/{chunk-OP7FRDI5.js → chunk-EPMLFNFK.js} +448 -37
- package/dist/chunk-EPMLFNFK.js.map +1 -0
- package/dist/index.cjs +993 -283
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +632 -30
- package/dist/index.d.ts +632 -30
- package/dist/index.js +1 -1
- package/dist/migrations/index.cjs +591 -52
- package/dist/migrations/index.cjs.map +1 -1
- package/dist/migrations/index.d.cts +63 -14
- package/dist/migrations/index.d.ts +63 -14
- 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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { columnsOf } from './chunk-
|
|
1
|
+
import { renderPortableToken, columnNamesOf, columnsOf } from './chunk-5QQMVTS5.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");
|
|
@@ -112,6 +116,8 @@ function renderColumnType(type, dialect) {
|
|
|
112
116
|
return "NUMERIC";
|
|
113
117
|
case "blob":
|
|
114
118
|
return "BLOB";
|
|
119
|
+
case "array":
|
|
120
|
+
throw new Error(unsupportedArray("sqlite"));
|
|
115
121
|
default:
|
|
116
122
|
return "TEXT";
|
|
117
123
|
}
|
|
@@ -153,6 +159,8 @@ function renderColumnType(type, dialect) {
|
|
|
153
159
|
return "CHAR(36)";
|
|
154
160
|
case "enum":
|
|
155
161
|
return `ENUM(${(meta.values ?? []).map(quoteLiteral).join(", ")})`;
|
|
162
|
+
case "array":
|
|
163
|
+
throw new Error(unsupportedArray("mysql"));
|
|
156
164
|
}
|
|
157
165
|
}
|
|
158
166
|
switch (kind) {
|
|
@@ -191,27 +199,39 @@ function renderColumnType(type, dialect) {
|
|
|
191
199
|
return "UUID";
|
|
192
200
|
case "enum":
|
|
193
201
|
return "TEXT";
|
|
202
|
+
case "array":
|
|
203
|
+
return `${renderColumnType(arrayElement(meta.element), dialect)}[]`;
|
|
194
204
|
}
|
|
195
205
|
}
|
|
196
|
-
function
|
|
206
|
+
function unsupportedArray(dialect) {
|
|
207
|
+
return `column.array() is PostgreSQL-only \u2014 ${dialect} has no native array type. Model the column as JSON there, accepting that array operators will not work.`;
|
|
208
|
+
}
|
|
209
|
+
function arrayElement(element) {
|
|
210
|
+
if (!element) {
|
|
211
|
+
throw new Error(
|
|
212
|
+
"An array column has no element type \u2014 build it with column.array()."
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
return element;
|
|
216
|
+
}
|
|
217
|
+
function renderDefault(def, dialect, type) {
|
|
197
218
|
if (def.kind === "expression") {
|
|
198
219
|
const expr = def.expression;
|
|
199
|
-
if (typeof expr === "object")
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
return "CURRENT_DATE";
|
|
205
|
-
case "current_time":
|
|
206
|
-
return "CURRENT_TIME";
|
|
207
|
-
case "uuidv4":
|
|
208
|
-
if (dialect === "postgresql") return "gen_random_uuid()";
|
|
209
|
-
if (dialect === "mysql") return "(UUID())";
|
|
210
|
-
return "(lower(hex(randomblob(16))))";
|
|
220
|
+
if (typeof expr === "object") {
|
|
221
|
+
if ("raw" in expr) return expr.raw;
|
|
222
|
+
throw new Error(
|
|
223
|
+
"sql.expr`...` binds parameters and cannot be rendered as a DEFAULT \u2014 use sql.raw()."
|
|
224
|
+
);
|
|
211
225
|
}
|
|
226
|
+
return renderPortableToken(expr, dialect);
|
|
212
227
|
}
|
|
213
228
|
const value = def.value;
|
|
214
229
|
if (value === null) return "NULL";
|
|
230
|
+
if (Array.isArray(value) && type?.kind === "array") {
|
|
231
|
+
const elementType = renderColumnType(arrayElement(type.meta.element), dialect);
|
|
232
|
+
const items = value.map((v) => renderDefault({ kind: "literal", value: v }, dialect));
|
|
233
|
+
return `ARRAY[${items.join(", ")}]::${elementType}[]`;
|
|
234
|
+
}
|
|
215
235
|
if (typeof value === "boolean") {
|
|
216
236
|
return dialect === "postgresql" ? value ? "TRUE" : "FALSE" : value ? "1" : "0";
|
|
217
237
|
}
|
|
@@ -220,10 +240,47 @@ function renderDefault(def, dialect) {
|
|
|
220
240
|
if (typeof value === "object") return quoteLiteral(JSON.stringify(value));
|
|
221
241
|
return quoteLiteral(String(value));
|
|
222
242
|
}
|
|
243
|
+
function renderFkAction(action) {
|
|
244
|
+
return action.toUpperCase();
|
|
245
|
+
}
|
|
246
|
+
function renderFkActions(fk) {
|
|
247
|
+
let sql = "";
|
|
248
|
+
if (fk.onDelete) sql += ` ON DELETE ${renderFkAction(fk.onDelete)}`;
|
|
249
|
+
if (fk.onUpdate) sql += ` ON UPDATE ${renderFkAction(fk.onUpdate)}`;
|
|
250
|
+
return sql;
|
|
251
|
+
}
|
|
252
|
+
function columnConstraintSuffix(col, dialect) {
|
|
253
|
+
let sql = "";
|
|
254
|
+
if (col.unique) sql += " UNIQUE";
|
|
255
|
+
if (col.references) {
|
|
256
|
+
const ref = col.references;
|
|
257
|
+
sql += ` REFERENCES ${quoteId(ref.table, dialect)} (${quoteId(ref.column, dialect)})`;
|
|
258
|
+
sql += renderFkActions(ref);
|
|
259
|
+
}
|
|
260
|
+
return sql;
|
|
261
|
+
}
|
|
262
|
+
function renderUniqueConstraint(uc, dialect) {
|
|
263
|
+
const cols = uc.columns.map((c) => quoteId(c, dialect)).join(", ");
|
|
264
|
+
return `CONSTRAINT ${quoteId(uc.name, dialect)} UNIQUE (${cols})`;
|
|
265
|
+
}
|
|
266
|
+
function renderForeignKeyConstraint(fk, dialect) {
|
|
267
|
+
const cols = fk.columns.map((c) => quoteId(c, dialect)).join(", ");
|
|
268
|
+
const refCols = fk.refColumns.map((c) => quoteId(c, dialect)).join(", ");
|
|
269
|
+
return `CONSTRAINT ${quoteId(fk.name, dialect)} FOREIGN KEY (${cols}) REFERENCES ${quoteId(fk.refTable, dialect)} (${refCols})${renderFkActions(fk)}`;
|
|
270
|
+
}
|
|
271
|
+
function tableConstraintClauses(table, dialect) {
|
|
272
|
+
return [
|
|
273
|
+
...table.uniqueConstraints.map((uc) => renderUniqueConstraint(uc, dialect)),
|
|
274
|
+
...table.foreignKeys.map((fk) => renderForeignKeyConstraint(fk, dialect))
|
|
275
|
+
];
|
|
276
|
+
}
|
|
223
277
|
function renderColumnDef(col, dialect) {
|
|
224
278
|
let sql = `${quoteId(col.name, dialect)} ${renderColumnType(col.type, dialect)}`;
|
|
225
279
|
if (col.notNull) sql += " NOT NULL";
|
|
226
|
-
if (col.default !== null)
|
|
280
|
+
if (col.default !== null) {
|
|
281
|
+
sql += ` DEFAULT ${renderDefault(col.default, dialect, col.type)}`;
|
|
282
|
+
}
|
|
283
|
+
sql += columnConstraintSuffix(col, dialect);
|
|
227
284
|
return sql;
|
|
228
285
|
}
|
|
229
286
|
function enumTypeName(table, column) {
|
|
@@ -246,14 +303,16 @@ function renderCreateTable(table, dialect) {
|
|
|
246
303
|
typeStmts.push(`CREATE TYPE ${quoteId(typeName, dialect)} AS ENUM (${values})`);
|
|
247
304
|
let def = `${quoteId(c.name, dialect)} ${quoteId(typeName, dialect)}`;
|
|
248
305
|
if (c.notNull) def += " NOT NULL";
|
|
249
|
-
if (c.default !== null)
|
|
250
|
-
|
|
306
|
+
if (c.default !== null) {
|
|
307
|
+
def += ` DEFAULT ${renderDefault(c.default, dialect, c.type)}`;
|
|
308
|
+
}
|
|
309
|
+
return def + columnConstraintSuffix(c, dialect);
|
|
251
310
|
}
|
|
252
311
|
if (dialect === "postgresql" && isAutoIncrementPk(table, c)) {
|
|
253
|
-
return `${quoteId(c.name, dialect)} ${postgresSerialType(c.type.kind)}`;
|
|
312
|
+
return `${quoteId(c.name, dialect)} ${postgresSerialType(c.type.kind)}${columnConstraintSuffix(c, dialect)}`;
|
|
254
313
|
}
|
|
255
314
|
if (dialect === "mysql" && isAutoIncrementPk(table, c)) {
|
|
256
|
-
return `${quoteId(c.name, dialect)} ${renderColumnType(c.type, dialect)} NOT NULL AUTO_INCREMENT`;
|
|
315
|
+
return `${quoteId(c.name, dialect)} ${renderColumnType(c.type, dialect)} NOT NULL AUTO_INCREMENT${columnConstraintSuffix(c, dialect)}`;
|
|
257
316
|
}
|
|
258
317
|
return renderColumnDef(c, dialect);
|
|
259
318
|
});
|
|
@@ -262,6 +321,7 @@ function renderCreateTable(table, dialect) {
|
|
|
262
321
|
`PRIMARY KEY (${table.primaryKey.map((c) => quoteId(c, dialect)).join(", ")})`
|
|
263
322
|
);
|
|
264
323
|
}
|
|
324
|
+
cols.push(...tableConstraintClauses(table, dialect));
|
|
265
325
|
return [
|
|
266
326
|
...typeStmts,
|
|
267
327
|
`CREATE TABLE ${quoteId(table.name, dialect)} (
|
|
@@ -295,10 +355,38 @@ function renderOperation(op, dialect) {
|
|
|
295
355
|
return renderAlterColumn(op.table, op.to, dialect);
|
|
296
356
|
case "recreate_table":
|
|
297
357
|
return dialect === "sqlite" ? renderSqliteRebuild(op.from, op.to) : renderTableDiff(op.from, op.to, dialect);
|
|
358
|
+
case "add_constraint":
|
|
359
|
+
return renderAddConstraint(op.table, op.constraint, dialect);
|
|
360
|
+
case "drop_constraint":
|
|
361
|
+
return renderDropConstraint(op.table, op.constraint, dialect);
|
|
298
362
|
case "execute":
|
|
299
363
|
return [op.up];
|
|
300
364
|
}
|
|
301
365
|
}
|
|
366
|
+
function renderAddConstraint(table, constraint, dialect) {
|
|
367
|
+
if (dialect === "sqlite") {
|
|
368
|
+
throw new Error(
|
|
369
|
+
`add_constraint on SQLite needs a table-rebuild (recreate_table); constraint ${constraint.constraint.name} on ${table}`
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
const clause = constraint.type === "unique" ? renderUniqueConstraint(constraint.constraint, dialect) : renderForeignKeyConstraint(constraint.constraint, dialect);
|
|
373
|
+
return [`ALTER TABLE ${quoteId(table, dialect)} ADD ${clause}`];
|
|
374
|
+
}
|
|
375
|
+
function renderDropConstraint(table, constraint, dialect) {
|
|
376
|
+
if (dialect === "sqlite") {
|
|
377
|
+
throw new Error(
|
|
378
|
+
`drop_constraint on SQLite needs a table-rebuild (recreate_table); constraint ${constraint.constraint.name} on ${table}`
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
const t = quoteId(table, dialect);
|
|
382
|
+
const name = quoteId(constraint.constraint.name, dialect);
|
|
383
|
+
if (dialect === "mysql") {
|
|
384
|
+
return [
|
|
385
|
+
constraint.type === "unique" ? `ALTER TABLE ${t} DROP INDEX ${name}` : `ALTER TABLE ${t} DROP FOREIGN KEY ${name}`
|
|
386
|
+
];
|
|
387
|
+
}
|
|
388
|
+
return [`ALTER TABLE ${t} DROP CONSTRAINT ${name}`];
|
|
389
|
+
}
|
|
302
390
|
function renderSqliteRebuild(from, to) {
|
|
303
391
|
const tmp = `__new_${to.name}`;
|
|
304
392
|
const common = Object.keys(to.columns).filter((c) => c in from.columns);
|
|
@@ -308,6 +396,7 @@ function renderSqliteRebuild(from, to) {
|
|
|
308
396
|
`PRIMARY KEY (${to.primaryKey.map((c) => quoteId(c, "sqlite")).join(", ")})`
|
|
309
397
|
);
|
|
310
398
|
}
|
|
399
|
+
cols.push(...tableConstraintClauses(to, "sqlite"));
|
|
311
400
|
const commonSql = common.map((c) => quoteId(c, "sqlite")).join(", ");
|
|
312
401
|
return [
|
|
313
402
|
"PRAGMA foreign_keys=off",
|
|
@@ -360,7 +449,7 @@ function renderAlterColumn(table, to, dialect) {
|
|
|
360
449
|
to.notNull ? `ALTER TABLE ${t} ALTER COLUMN ${c} SET NOT NULL` : `ALTER TABLE ${t} ALTER COLUMN ${c} DROP NOT NULL`
|
|
361
450
|
);
|
|
362
451
|
stmts.push(
|
|
363
|
-
to.default !== null ? `ALTER TABLE ${t} ALTER COLUMN ${c} SET DEFAULT ${renderDefault(to.default, dialect)}` : `ALTER TABLE ${t} ALTER COLUMN ${c} DROP DEFAULT`
|
|
452
|
+
to.default !== null ? `ALTER TABLE ${t} ALTER COLUMN ${c} SET DEFAULT ${renderDefault(to.default, dialect, to.type)}` : `ALTER TABLE ${t} ALTER COLUMN ${c} DROP DEFAULT`
|
|
364
453
|
);
|
|
365
454
|
return stmts;
|
|
366
455
|
}
|
|
@@ -371,9 +460,74 @@ function columnSignature(col) {
|
|
|
371
460
|
type: col.type,
|
|
372
461
|
notNull: col.notNull,
|
|
373
462
|
primaryKey: col.primaryKey,
|
|
374
|
-
default: col.default
|
|
463
|
+
default: col.default,
|
|
464
|
+
unique: col.unique,
|
|
465
|
+
references: col.references
|
|
375
466
|
});
|
|
376
467
|
}
|
|
468
|
+
function uniqueSignature(uc) {
|
|
469
|
+
return JSON.stringify({ columns: uc.columns });
|
|
470
|
+
}
|
|
471
|
+
function foreignKeySignature(fk) {
|
|
472
|
+
return JSON.stringify({
|
|
473
|
+
columns: fk.columns,
|
|
474
|
+
refTable: fk.refTable,
|
|
475
|
+
refColumns: fk.refColumns,
|
|
476
|
+
onDelete: fk.onDelete ?? null,
|
|
477
|
+
onUpdate: fk.onUpdate ?? null
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
function diffConstraints(current, target) {
|
|
481
|
+
const ops = [];
|
|
482
|
+
const table = target.name;
|
|
483
|
+
const currentUq = new Map(current.uniqueConstraints.map((u) => [u.name, u]));
|
|
484
|
+
const targetUq = new Map(target.uniqueConstraints.map((u) => [u.name, u]));
|
|
485
|
+
for (const [name, cur] of currentUq) {
|
|
486
|
+
const tgt = targetUq.get(name);
|
|
487
|
+
if (!tgt || uniqueSignature(cur) !== uniqueSignature(tgt)) {
|
|
488
|
+
ops.push(dropUnique(table, cur));
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
for (const [name, tgt] of targetUq) {
|
|
492
|
+
const cur = currentUq.get(name);
|
|
493
|
+
if (!cur || uniqueSignature(cur) !== uniqueSignature(tgt)) {
|
|
494
|
+
ops.push(addUnique(table, tgt));
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
const currentFk = new Map(current.foreignKeys.map((f) => [f.name, f]));
|
|
498
|
+
const targetFk = new Map(target.foreignKeys.map((f) => [f.name, f]));
|
|
499
|
+
for (const [name, cur] of currentFk) {
|
|
500
|
+
const tgt = targetFk.get(name);
|
|
501
|
+
if (!tgt || foreignKeySignature(cur) !== foreignKeySignature(tgt)) {
|
|
502
|
+
ops.push(dropForeignKey(table, cur));
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
for (const [name, tgt] of targetFk) {
|
|
506
|
+
const cur = currentFk.get(name);
|
|
507
|
+
if (!cur || foreignKeySignature(cur) !== foreignKeySignature(tgt)) {
|
|
508
|
+
ops.push(addForeignKey(table, tgt));
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return ops;
|
|
512
|
+
}
|
|
513
|
+
function uniqueNamed(uc) {
|
|
514
|
+
return { type: "unique", constraint: uc };
|
|
515
|
+
}
|
|
516
|
+
function foreignKeyNamed(fk) {
|
|
517
|
+
return { type: "foreignKey", constraint: fk };
|
|
518
|
+
}
|
|
519
|
+
function addUnique(table, uc) {
|
|
520
|
+
return { kind: "add_constraint", table, constraint: uniqueNamed(uc) };
|
|
521
|
+
}
|
|
522
|
+
function dropUnique(table, uc) {
|
|
523
|
+
return { kind: "drop_constraint", table, constraint: uniqueNamed(uc) };
|
|
524
|
+
}
|
|
525
|
+
function addForeignKey(table, fk) {
|
|
526
|
+
return { kind: "add_constraint", table, constraint: foreignKeyNamed(fk) };
|
|
527
|
+
}
|
|
528
|
+
function dropForeignKey(table, fk) {
|
|
529
|
+
return { kind: "drop_constraint", table, constraint: foreignKeyNamed(fk) };
|
|
530
|
+
}
|
|
377
531
|
function diffSchema(current, target) {
|
|
378
532
|
const ops = [];
|
|
379
533
|
const drops = [];
|
|
@@ -402,6 +556,7 @@ function diffSchema(current, target) {
|
|
|
402
556
|
ops.push({ kind: "drop_column", table: name, column: currentCol });
|
|
403
557
|
}
|
|
404
558
|
}
|
|
559
|
+
ops.push(...diffConstraints(currentTable, targetTable));
|
|
405
560
|
}
|
|
406
561
|
for (const [name, currentTable] of Object.entries(current.tables)) {
|
|
407
562
|
if (!target.tables[name]) {
|
|
@@ -466,21 +621,49 @@ function heads(migrations) {
|
|
|
466
621
|
}
|
|
467
622
|
|
|
468
623
|
// src/migrations/ir.ts
|
|
624
|
+
function constraintName(prefix, table, columns) {
|
|
625
|
+
return `${prefix}_${table}_${columns.join("_")}`;
|
|
626
|
+
}
|
|
469
627
|
function reflectTable(model) {
|
|
628
|
+
const names = columnNamesOf(model);
|
|
629
|
+
const toColumn = (prop) => names?.[prop] ?? prop;
|
|
470
630
|
const columns = {};
|
|
471
631
|
const primaryKey = [];
|
|
472
|
-
for (const [
|
|
632
|
+
for (const [prop, col] of Object.entries(columnsOf(model))) {
|
|
473
633
|
const isPk = col.flags.primaryKey;
|
|
634
|
+
const name = toColumn(prop);
|
|
474
635
|
columns[name] = {
|
|
475
636
|
name,
|
|
476
637
|
type: col.type,
|
|
477
638
|
notNull: col.flags.notNull || isPk,
|
|
478
639
|
primaryKey: isPk,
|
|
479
|
-
default: col.defaultValue
|
|
640
|
+
default: col.defaultValue,
|
|
641
|
+
unique: col.flags.unique,
|
|
642
|
+
references: col.reference
|
|
480
643
|
};
|
|
481
644
|
if (isPk) primaryKey.push(name);
|
|
482
645
|
}
|
|
483
|
-
|
|
646
|
+
const uniqueConstraints = [];
|
|
647
|
+
const foreignKeys = [];
|
|
648
|
+
for (const c of model.tableArgs?.() ?? []) {
|
|
649
|
+
const cols = c.columns.map(toColumn);
|
|
650
|
+
if (c.kind === "unique") {
|
|
651
|
+
uniqueConstraints.push({
|
|
652
|
+
name: c.name ?? constraintName("uq", model.tablename, cols),
|
|
653
|
+
columns: cols
|
|
654
|
+
});
|
|
655
|
+
} else {
|
|
656
|
+
foreignKeys.push({
|
|
657
|
+
name: c.name ?? constraintName("fk", model.tablename, cols),
|
|
658
|
+
columns: cols,
|
|
659
|
+
refTable: c.refTable,
|
|
660
|
+
refColumns: c.refColumns,
|
|
661
|
+
onDelete: c.onDelete,
|
|
662
|
+
onUpdate: c.onUpdate
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
return { name: model.tablename, columns, primaryKey, uniqueConstraints, foreignKeys };
|
|
484
667
|
}
|
|
485
668
|
function reflectSchema(models) {
|
|
486
669
|
const tables = {};
|
|
@@ -517,6 +700,39 @@ function affinityToKind(affinity) {
|
|
|
517
700
|
return "text";
|
|
518
701
|
}
|
|
519
702
|
}
|
|
703
|
+
function sqliteForeignKeys(driver, table) {
|
|
704
|
+
const rows = driver.execute(`PRAGMA foreign_key_list(${JSON.stringify(table)})`, []).rows;
|
|
705
|
+
const byId = /* @__PURE__ */ new Map();
|
|
706
|
+
for (const r of rows) {
|
|
707
|
+
const list = byId.get(r.id) ?? [];
|
|
708
|
+
list.push(r);
|
|
709
|
+
byId.set(r.id, list);
|
|
710
|
+
}
|
|
711
|
+
const fks = [];
|
|
712
|
+
for (const group of byId.values()) {
|
|
713
|
+
const ordered = [...group].sort((a, b) => a.seq - b.seq);
|
|
714
|
+
const columns = ordered.map((r) => r.from);
|
|
715
|
+
fks.push({
|
|
716
|
+
name: `fk_${table}_${columns.join("_")}`,
|
|
717
|
+
columns,
|
|
718
|
+
refTable: ordered[0]?.table ?? "",
|
|
719
|
+
refColumns: ordered.map((r) => r.to)
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
return fks;
|
|
723
|
+
}
|
|
724
|
+
function sqliteUniques(driver, table) {
|
|
725
|
+
const indexes = driver.execute(`PRAGMA index_list(${JSON.stringify(table)})`, []).rows;
|
|
726
|
+
const uniques = [];
|
|
727
|
+
for (const idx of indexes) {
|
|
728
|
+
if (Number(idx.unique) !== 1 || idx.origin === "pk") continue;
|
|
729
|
+
const cols = driver.execute(`PRAGMA index_info(${JSON.stringify(idx.name)})`, []).rows.sort((a, b) => a.seqno - b.seqno).map((c) => c.name);
|
|
730
|
+
if (cols.length > 0) {
|
|
731
|
+
uniques.push({ name: `uq_${table}_${cols.join("_")}`, columns: cols });
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
return uniques;
|
|
735
|
+
}
|
|
520
736
|
function introspectSqlite(driver) {
|
|
521
737
|
const tablesRows = driver.execute(
|
|
522
738
|
"SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' AND name != 'tempest_db_js_migrations'",
|
|
@@ -536,14 +752,39 @@ function introspectSqlite(driver) {
|
|
|
536
752
|
type: { kind: affinityToKind(affinity), meta: {} },
|
|
537
753
|
notNull: Number(col.notnull) === 1 || isPk,
|
|
538
754
|
primaryKey: isPk,
|
|
539
|
-
default: null
|
|
755
|
+
default: null,
|
|
756
|
+
unique: false,
|
|
757
|
+
references: null
|
|
540
758
|
};
|
|
541
759
|
if (isPk) primaryKey.push(col.name);
|
|
542
760
|
}
|
|
543
|
-
tables[tableName] = {
|
|
761
|
+
tables[tableName] = {
|
|
762
|
+
name: tableName,
|
|
763
|
+
columns,
|
|
764
|
+
primaryKey,
|
|
765
|
+
uniqueConstraints: sqliteUniques(driver, tableName),
|
|
766
|
+
foreignKeys: sqliteForeignKeys(driver, tableName)
|
|
767
|
+
};
|
|
544
768
|
}
|
|
545
769
|
return { tables };
|
|
546
770
|
}
|
|
771
|
+
function constraintKeys(table) {
|
|
772
|
+
const fks = /* @__PURE__ */ new Set();
|
|
773
|
+
const uniques = /* @__PURE__ */ new Set();
|
|
774
|
+
for (const col of Object.values(table.columns)) {
|
|
775
|
+
if (col.unique) uniques.add(col.name);
|
|
776
|
+
if (col.references) {
|
|
777
|
+
fks.add(`${col.name}=>${col.references.table}(${col.references.column})`);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
for (const uc of table.uniqueConstraints) {
|
|
781
|
+
uniques.add([...uc.columns].sort().join(","));
|
|
782
|
+
}
|
|
783
|
+
for (const fk of table.foreignKeys) {
|
|
784
|
+
fks.add(`${fk.columns.join(",")}=>${fk.refTable}(${fk.refColumns.join(",")})`);
|
|
785
|
+
}
|
|
786
|
+
return { fks, uniques };
|
|
787
|
+
}
|
|
547
788
|
function checkDrift(driver, models) {
|
|
548
789
|
const actual = introspectSqlite(driver);
|
|
549
790
|
const expected = reflectSchema(models);
|
|
@@ -581,6 +822,34 @@ function checkDrift(driver, models) {
|
|
|
581
822
|
);
|
|
582
823
|
}
|
|
583
824
|
}
|
|
825
|
+
const expectedKeys = constraintKeys(expectedTable);
|
|
826
|
+
const actualKeys = constraintKeys(actualTable);
|
|
827
|
+
for (const fk of expectedKeys.fks) {
|
|
828
|
+
if (!actualKeys.fks.has(fk)) {
|
|
829
|
+
issues.push(`foreign key "${tableName}: ${fk}" is missing from the database`);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
for (const fk of actualKeys.fks) {
|
|
833
|
+
if (!expectedKeys.fks.has(fk)) {
|
|
834
|
+
issues.push(
|
|
835
|
+
`foreign key "${tableName}: ${fk}" exists in the database but not in the model`
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
for (const uq of expectedKeys.uniques) {
|
|
840
|
+
if (!actualKeys.uniques.has(uq)) {
|
|
841
|
+
issues.push(
|
|
842
|
+
`unique constraint "${tableName}: (${uq})" is missing from the database`
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
for (const uq of actualKeys.uniques) {
|
|
847
|
+
if (!expectedKeys.uniques.has(uq)) {
|
|
848
|
+
issues.push(
|
|
849
|
+
`unique constraint "${tableName}: (${uq})" exists in the database but not in the model`
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
584
853
|
}
|
|
585
854
|
for (const tableName of Object.keys(actual.tables)) {
|
|
586
855
|
if (!expected.tables[tableName]) {
|
|
@@ -589,6 +858,54 @@ function checkDrift(driver, models) {
|
|
|
589
858
|
}
|
|
590
859
|
return issues;
|
|
591
860
|
}
|
|
861
|
+
function pgTypeToColumnType(dataType, udtName) {
|
|
862
|
+
if (dataType.toLowerCase() === "array") {
|
|
863
|
+
return {
|
|
864
|
+
kind: "array",
|
|
865
|
+
meta: { element: { kind: pgUdtToKind(udtName.replace(/^_/, "")), meta: {} } }
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
return { kind: pgTypeToKind(dataType, udtName), meta: {} };
|
|
869
|
+
}
|
|
870
|
+
function pgUdtToKind(udtName) {
|
|
871
|
+
switch (udtName) {
|
|
872
|
+
case "int2":
|
|
873
|
+
return "smallint";
|
|
874
|
+
case "int4":
|
|
875
|
+
return "integer";
|
|
876
|
+
case "int8":
|
|
877
|
+
return "bigint";
|
|
878
|
+
case "float4":
|
|
879
|
+
return "real";
|
|
880
|
+
case "float8":
|
|
881
|
+
return "double";
|
|
882
|
+
case "numeric":
|
|
883
|
+
return "numeric";
|
|
884
|
+
case "varchar":
|
|
885
|
+
return "varchar";
|
|
886
|
+
case "bpchar":
|
|
887
|
+
return "char";
|
|
888
|
+
case "bool":
|
|
889
|
+
return "boolean";
|
|
890
|
+
case "date":
|
|
891
|
+
return "date";
|
|
892
|
+
case "time":
|
|
893
|
+
case "timetz":
|
|
894
|
+
return "time";
|
|
895
|
+
case "timestamp":
|
|
896
|
+
case "timestamptz":
|
|
897
|
+
return "timestamp";
|
|
898
|
+
case "bytea":
|
|
899
|
+
return "blob";
|
|
900
|
+
case "json":
|
|
901
|
+
case "jsonb":
|
|
902
|
+
return "json";
|
|
903
|
+
case "uuid":
|
|
904
|
+
return "uuid";
|
|
905
|
+
default:
|
|
906
|
+
return "text";
|
|
907
|
+
}
|
|
908
|
+
}
|
|
592
909
|
function pgTypeToKind(dataType, udtName) {
|
|
593
910
|
const t = dataType.toLowerCase();
|
|
594
911
|
if (t === "user-defined") return "enum";
|
|
@@ -636,20 +953,66 @@ async function introspectPostgres(driver) {
|
|
|
636
953
|
const isPk = pkSet.has(name);
|
|
637
954
|
columns[name] = {
|
|
638
955
|
name,
|
|
639
|
-
type:
|
|
640
|
-
kind: pgTypeToKind(String(col.data_type), String(col.udt_name)),
|
|
641
|
-
meta: {}
|
|
642
|
-
},
|
|
956
|
+
type: pgTypeToColumnType(String(col.data_type), String(col.udt_name)),
|
|
643
957
|
notNull: col.is_nullable === "NO" || isPk,
|
|
644
958
|
primaryKey: isPk,
|
|
645
|
-
default: null
|
|
959
|
+
default: null,
|
|
960
|
+
unique: false,
|
|
961
|
+
references: null
|
|
646
962
|
};
|
|
647
963
|
if (isPk) primaryKey.push(name);
|
|
648
964
|
}
|
|
649
|
-
tables[tableName] = {
|
|
965
|
+
tables[tableName] = {
|
|
966
|
+
name: tableName,
|
|
967
|
+
columns,
|
|
968
|
+
primaryKey,
|
|
969
|
+
uniqueConstraints: await postgresUniques(driver, tableName),
|
|
970
|
+
foreignKeys: await postgresForeignKeys(driver, tableName)
|
|
971
|
+
};
|
|
650
972
|
}
|
|
651
973
|
return { tables };
|
|
652
974
|
}
|
|
975
|
+
async function postgresForeignKeys(driver, table) {
|
|
976
|
+
const result = await driver.execute(
|
|
977
|
+
`SELECT c.conname AS name,
|
|
978
|
+
(SELECT array_agg(a.attname ORDER BY k.ord)
|
|
979
|
+
FROM unnest(c.conkey) WITH ORDINALITY AS k(attnum, ord)
|
|
980
|
+
JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = k.attnum) AS cols,
|
|
981
|
+
cf.relname AS ref_table,
|
|
982
|
+
(SELECT array_agg(a.attname ORDER BY k.ord)
|
|
983
|
+
FROM unnest(c.confkey) WITH ORDINALITY AS k(attnum, ord)
|
|
984
|
+
JOIN pg_attribute a ON a.attrelid = c.confrelid AND a.attnum = k.attnum) AS ref_cols
|
|
985
|
+
FROM pg_constraint c
|
|
986
|
+
JOIN pg_class cf ON cf.oid = c.confrelid
|
|
987
|
+
WHERE c.contype = 'f' AND c.conrelid = $1::regclass`,
|
|
988
|
+
[table]
|
|
989
|
+
);
|
|
990
|
+
return result.rows.map((r) => ({
|
|
991
|
+
name: String(r.name),
|
|
992
|
+
columns: r.cols ?? [],
|
|
993
|
+
refTable: String(r.ref_table),
|
|
994
|
+
refColumns: r.ref_cols ?? []
|
|
995
|
+
}));
|
|
996
|
+
}
|
|
997
|
+
async function postgresUniques(driver, table) {
|
|
998
|
+
const result = await driver.execute(
|
|
999
|
+
`SELECT c.conname AS name,
|
|
1000
|
+
(SELECT array_agg(a.attname ORDER BY k.ord)
|
|
1001
|
+
FROM unnest(c.conkey) WITH ORDINALITY AS k(attnum, ord)
|
|
1002
|
+
JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = k.attnum) AS cols
|
|
1003
|
+
FROM pg_constraint c
|
|
1004
|
+
WHERE c.contype = 'u' AND c.conrelid = $1::regclass`,
|
|
1005
|
+
[table]
|
|
1006
|
+
);
|
|
1007
|
+
return result.rows.map((r) => ({
|
|
1008
|
+
name: String(r.name),
|
|
1009
|
+
columns: r.cols ?? []
|
|
1010
|
+
}));
|
|
1011
|
+
}
|
|
1012
|
+
function describeKind(type) {
|
|
1013
|
+
if (type.kind !== "array") return type.kind;
|
|
1014
|
+
return `${type.meta.element ? describeKind(type.meta.element) : "unknown"}[]`;
|
|
1015
|
+
}
|
|
653
1016
|
async function checkDriftPostgres(driver, models) {
|
|
654
1017
|
const actual = await introspectPostgres(driver);
|
|
655
1018
|
const expected = reflectSchema(models);
|
|
@@ -666,9 +1029,9 @@ async function checkDriftPostgres(driver, models) {
|
|
|
666
1029
|
issues.push(`column "${tableName}.${colName}" is missing from the database`);
|
|
667
1030
|
continue;
|
|
668
1031
|
}
|
|
669
|
-
if (expectedCol.type
|
|
1032
|
+
if (describeKind(expectedCol.type) !== describeKind(actualCol.type)) {
|
|
670
1033
|
issues.push(
|
|
671
|
-
`column "${tableName}.${colName}" type differs: model ${expectedCol.type
|
|
1034
|
+
`column "${tableName}.${colName}" type differs: model ${describeKind(expectedCol.type)}, db ${describeKind(actualCol.type)}`
|
|
672
1035
|
);
|
|
673
1036
|
}
|
|
674
1037
|
if (expectedCol.notNull !== actualCol.notNull) {
|
|
@@ -682,6 +1045,20 @@ async function checkDriftPostgres(driver, models) {
|
|
|
682
1045
|
);
|
|
683
1046
|
}
|
|
684
1047
|
}
|
|
1048
|
+
const expectedKeys = constraintKeys(expectedTable);
|
|
1049
|
+
const actualKeys = constraintKeys(actualTable);
|
|
1050
|
+
for (const fk of expectedKeys.fks) {
|
|
1051
|
+
if (!actualKeys.fks.has(fk)) {
|
|
1052
|
+
issues.push(`foreign key "${tableName}: ${fk}" is missing from the database`);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
for (const uq of expectedKeys.uniques) {
|
|
1056
|
+
if (!actualKeys.uniques.has(uq)) {
|
|
1057
|
+
issues.push(
|
|
1058
|
+
`unique constraint "${tableName}: (${uq})" is missing from the database`
|
|
1059
|
+
);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
685
1062
|
}
|
|
686
1063
|
for (const tableName of Object.keys(actual.tables)) {
|
|
687
1064
|
if (!expected.tables[tableName]) {
|
|
@@ -697,7 +1074,9 @@ function columnShape(col) {
|
|
|
697
1074
|
type: col.type,
|
|
698
1075
|
notNull: col.notNull,
|
|
699
1076
|
primaryKey: col.primaryKey,
|
|
700
|
-
default: col.default
|
|
1077
|
+
default: col.default,
|
|
1078
|
+
unique: col.unique,
|
|
1079
|
+
references: col.references
|
|
701
1080
|
});
|
|
702
1081
|
}
|
|
703
1082
|
function tableShape(columns) {
|
|
@@ -816,6 +1195,14 @@ var Op = class {
|
|
|
816
1195
|
recreateTable(from, to) {
|
|
817
1196
|
this.run({ kind: "recreate_table", from, to });
|
|
818
1197
|
}
|
|
1198
|
+
/** Add a table-level unique / foreign-key constraint. */
|
|
1199
|
+
addConstraint(table, constraint) {
|
|
1200
|
+
this.run({ kind: "add_constraint", table, constraint });
|
|
1201
|
+
}
|
|
1202
|
+
/** Drop a table-level unique / foreign-key constraint. */
|
|
1203
|
+
dropConstraint(table, constraint) {
|
|
1204
|
+
this.run({ kind: "drop_constraint", table, constraint });
|
|
1205
|
+
}
|
|
819
1206
|
/** Raw SQL escape hatch (e.g. a data migration). `down` may be `null`. */
|
|
820
1207
|
execute(up, down = null) {
|
|
821
1208
|
this.run({ kind: "execute", up, down });
|
|
@@ -1084,6 +1471,30 @@ function applyOperation(schema, op) {
|
|
|
1084
1471
|
}
|
|
1085
1472
|
break;
|
|
1086
1473
|
}
|
|
1474
|
+
case "add_constraint": {
|
|
1475
|
+
const t = tables[op.table];
|
|
1476
|
+
if (t) {
|
|
1477
|
+
tables[op.table] = op.constraint.type === "unique" ? {
|
|
1478
|
+
...t,
|
|
1479
|
+
uniqueConstraints: [...t.uniqueConstraints, op.constraint.constraint]
|
|
1480
|
+
} : { ...t, foreignKeys: [...t.foreignKeys, op.constraint.constraint] };
|
|
1481
|
+
}
|
|
1482
|
+
break;
|
|
1483
|
+
}
|
|
1484
|
+
case "drop_constraint": {
|
|
1485
|
+
const t = tables[op.table];
|
|
1486
|
+
if (t) {
|
|
1487
|
+
const dropName = op.constraint.constraint.name;
|
|
1488
|
+
tables[op.table] = op.constraint.type === "unique" ? {
|
|
1489
|
+
...t,
|
|
1490
|
+
uniqueConstraints: t.uniqueConstraints.filter((u) => u.name !== dropName)
|
|
1491
|
+
} : {
|
|
1492
|
+
...t,
|
|
1493
|
+
foreignKeys: t.foreignKeys.filter((f) => f.name !== dropName)
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
break;
|
|
1497
|
+
}
|
|
1087
1498
|
}
|
|
1088
1499
|
return { tables };
|
|
1089
1500
|
}
|
|
@@ -1221,5 +1632,5 @@ function runMigrationCli(argv, config) {
|
|
|
1221
1632
|
}
|
|
1222
1633
|
|
|
1223
1634
|
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-
|
|
1635
|
+
//# sourceMappingURL=chunk-EPMLFNFK.js.map
|
|
1636
|
+
//# sourceMappingURL=chunk-EPMLFNFK.js.map
|