bun-query-builder 0.1.36 → 0.1.38
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/actions/introspect-db.d.ts +10 -0
- package/dist/bin/cli.js +681 -72
- package/dist/drivers/mysql.d.ts +7 -1
- package/dist/drivers/postgres.d.ts +7 -1
- package/dist/drivers/sqlite.d.ts +7 -1
- package/dist/migrations.d.ts +84 -10
- package/dist/orm.d.ts +5 -1
- package/dist/src/index.js +683 -71
- package/dist/types.d.ts +4 -0
- package/package.json +1 -1
package/dist/src/index.js
CHANGED
|
@@ -17449,12 +17449,12 @@ async function readColumns(qb, dialect, table) {
|
|
|
17449
17449
|
}
|
|
17450
17450
|
if (dialect === "postgres") {
|
|
17451
17451
|
const rows2 = await qb.unsafe(`SELECT c.column_name, c.data_type, c.is_nullable,
|
|
17452
|
-
|
|
17452
|
+
(SELECT COUNT(*) FROM information_schema.table_constraints tc
|
|
17453
17453
|
JOIN information_schema.key_column_usage k ON k.constraint_name = tc.constraint_name
|
|
17454
17454
|
WHERE tc.constraint_type = 'PRIMARY KEY' AND tc.table_name = c.table_name AND k.column_name = c.column_name) AS is_pk
|
|
17455
|
-
|
|
17456
|
-
|
|
17457
|
-
|
|
17455
|
+
FROM information_schema.columns c
|
|
17456
|
+
WHERE c.table_name = $1 AND c.table_schema = 'public'
|
|
17457
|
+
ORDER BY c.ordinal_position`, [table]);
|
|
17458
17458
|
return rows2.map((r) => ({
|
|
17459
17459
|
name: r.column_name,
|
|
17460
17460
|
sqlType: String(r.data_type || ""),
|
|
@@ -17463,9 +17463,9 @@ async function readColumns(qb, dialect, table) {
|
|
|
17463
17463
|
}));
|
|
17464
17464
|
}
|
|
17465
17465
|
const rows = await qb.unsafe(`SELECT column_name, data_type, is_nullable, column_key
|
|
17466
|
-
|
|
17467
|
-
|
|
17468
|
-
|
|
17466
|
+
FROM information_schema.columns
|
|
17467
|
+
WHERE table_name = ? AND table_schema = DATABASE()
|
|
17468
|
+
ORDER BY ordinal_position`, [table]);
|
|
17469
17469
|
return rows.map((r) => ({
|
|
17470
17470
|
name: r.column_name ?? r.COLUMN_NAME,
|
|
17471
17471
|
sqlType: String(r.data_type ?? r.DATA_TYPE ?? ""),
|
|
@@ -17512,6 +17512,281 @@ async function introspectDatabase(opts = {}) {
|
|
|
17512
17512
|
}
|
|
17513
17513
|
return out;
|
|
17514
17514
|
}
|
|
17515
|
+
function sqlTypeToNormalized(rawType, dialect, ctx) {
|
|
17516
|
+
if (ctx?.enumValues && ctx.enumValues.length > 0)
|
|
17517
|
+
return "enum";
|
|
17518
|
+
const t = String(rawType || "").toLowerCase().trim();
|
|
17519
|
+
const base = t.replace(/\(.*$/, "").replace(/\s+/g, " ").trim();
|
|
17520
|
+
if (dialect === "mysql" && /^tinyint\(1\)/.test(t))
|
|
17521
|
+
return "boolean";
|
|
17522
|
+
if (/^bool/.test(base))
|
|
17523
|
+
return "boolean";
|
|
17524
|
+
if (base === "int8" || base === "bigint" || base === "bigserial")
|
|
17525
|
+
return "bigint";
|
|
17526
|
+
if (/^(?:int|integer|smallint|mediumint|serial|int2|int4|tinyint)/.test(base))
|
|
17527
|
+
return "integer";
|
|
17528
|
+
if (/^(?:varchar|character varying|nvarchar|nchar|char|character|bpchar)/.test(base)) {
|
|
17529
|
+
const m = t.match(/\((\d+)\)/);
|
|
17530
|
+
const len = m ? Number(m[1]) : undefined;
|
|
17531
|
+
return len !== undefined && len > 255 ? "text" : "string";
|
|
17532
|
+
}
|
|
17533
|
+
if (/text|clob/.test(base))
|
|
17534
|
+
return "text";
|
|
17535
|
+
if (base === "float8" || /^double/.test(base))
|
|
17536
|
+
return "double";
|
|
17537
|
+
if (/^(?:real|float4|float)/.test(base))
|
|
17538
|
+
return "float";
|
|
17539
|
+
if (/^(?:numeric|decimal|money)/.test(base))
|
|
17540
|
+
return "decimal";
|
|
17541
|
+
if (base === "date")
|
|
17542
|
+
return "date";
|
|
17543
|
+
if (/^(?:timestamp|datetime)/.test(base))
|
|
17544
|
+
return "datetime";
|
|
17545
|
+
if (/^time/.test(base))
|
|
17546
|
+
return "string";
|
|
17547
|
+
if (/json/.test(base))
|
|
17548
|
+
return "json";
|
|
17549
|
+
if (/^enum/.test(base))
|
|
17550
|
+
return "enum";
|
|
17551
|
+
return "string";
|
|
17552
|
+
}
|
|
17553
|
+
function stripIndexPrefix(physicalName, table) {
|
|
17554
|
+
const prefix = `${table}_`;
|
|
17555
|
+
return physicalName.startsWith(prefix) ? physicalName.slice(prefix.length) : physicalName;
|
|
17556
|
+
}
|
|
17557
|
+
function asAction(raw2) {
|
|
17558
|
+
const v = String(raw2 ?? "").toLowerCase().trim();
|
|
17559
|
+
if (v === "cascade" || v === "restrict" || v === "set null")
|
|
17560
|
+
return v;
|
|
17561
|
+
return;
|
|
17562
|
+
}
|
|
17563
|
+
async function buildSqliteTable(qb, table) {
|
|
17564
|
+
const colRows = await qb.unsafe(`PRAGMA table_info(${JSON.stringify(table)})`);
|
|
17565
|
+
const idxList = await qb.unsafe(`PRAGMA index_list(${JSON.stringify(table)})`);
|
|
17566
|
+
const fkList = await qb.unsafe(`PRAGMA foreign_key_list(${JSON.stringify(table)})`);
|
|
17567
|
+
const ddlRows = await qb.unsafe(`SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?`, [table]);
|
|
17568
|
+
const ddl = String(ddlRows?.[0]?.sql ?? "");
|
|
17569
|
+
const fkByColumn = new Map;
|
|
17570
|
+
for (const fk of fkList) {
|
|
17571
|
+
fkByColumn.set(String(fk.from), {
|
|
17572
|
+
table: String(fk.table),
|
|
17573
|
+
column: String(fk.to ?? "id"),
|
|
17574
|
+
onDelete: asAction(fk.on_delete),
|
|
17575
|
+
onUpdate: asAction(fk.on_update)
|
|
17576
|
+
});
|
|
17577
|
+
}
|
|
17578
|
+
const indexes = [];
|
|
17579
|
+
const uniqueColumns = new Set;
|
|
17580
|
+
for (const idx of idxList) {
|
|
17581
|
+
const origin = String(idx.origin ?? "");
|
|
17582
|
+
const physName = String(idx.name ?? "");
|
|
17583
|
+
if (origin === "pk" || physName.startsWith("sqlite_autoindex_"))
|
|
17584
|
+
continue;
|
|
17585
|
+
const info = await qb.unsafe(`PRAGMA index_info(${JSON.stringify(physName)})`);
|
|
17586
|
+
const columns2 = info.map((r) => String(r.name)).filter(Boolean);
|
|
17587
|
+
if (columns2.length === 0)
|
|
17588
|
+
continue;
|
|
17589
|
+
const isUnique = Number(idx.unique) === 1;
|
|
17590
|
+
if (isUnique && columns2.length === 1)
|
|
17591
|
+
uniqueColumns.add(columns2[0]);
|
|
17592
|
+
indexes.push({ name: stripIndexPrefix(physName, table), columns: columns2, type: isUnique ? "unique" : "index" });
|
|
17593
|
+
}
|
|
17594
|
+
const columns = colRows.map((r) => {
|
|
17595
|
+
const name = String(r.name);
|
|
17596
|
+
const enumValues = parseSqliteCheckEnum(ddl, name);
|
|
17597
|
+
const isPrimaryKey = Number(r.pk) > 0;
|
|
17598
|
+
const dflt = r.dflt_value;
|
|
17599
|
+
const hasDefault = dflt !== null && dflt !== undefined;
|
|
17600
|
+
return {
|
|
17601
|
+
name,
|
|
17602
|
+
type: sqlTypeToNormalized(String(r.type ?? ""), "sqlite", { enumValues }),
|
|
17603
|
+
isPrimaryKey,
|
|
17604
|
+
isUnique: uniqueColumns.has(name),
|
|
17605
|
+
isNullable: isPrimaryKey ? false : Number(r.notnull) === 0,
|
|
17606
|
+
hasDefault,
|
|
17607
|
+
defaultValue: hasDefault ? normalizeRawDefault(dflt) : undefined,
|
|
17608
|
+
enumValues,
|
|
17609
|
+
references: fkByColumn.get(name)
|
|
17610
|
+
};
|
|
17611
|
+
});
|
|
17612
|
+
return { table, columns, indexes };
|
|
17613
|
+
}
|
|
17614
|
+
function parseSqliteCheckEnum(ddl, column) {
|
|
17615
|
+
if (!ddl)
|
|
17616
|
+
return;
|
|
17617
|
+
const re = new RegExp(`(?:"${column}"|\`${column}\`|\\b${column}\\b)\\s+IN\\s*\\(([^)]*)\\)`, "i");
|
|
17618
|
+
const m = ddl.match(re);
|
|
17619
|
+
if (!m)
|
|
17620
|
+
return;
|
|
17621
|
+
const values = m[1].split(",").map((s) => s.trim().replace(/^'(.*)'$/s, "$1").replace(/''/g, "'")).filter((s) => s.length > 0);
|
|
17622
|
+
return values.length > 0 ? values : undefined;
|
|
17623
|
+
}
|
|
17624
|
+
function normalizeRawDefault(raw2) {
|
|
17625
|
+
if (typeof raw2 === "number" || typeof raw2 === "boolean")
|
|
17626
|
+
return raw2;
|
|
17627
|
+
return String(raw2);
|
|
17628
|
+
}
|
|
17629
|
+
async function buildPgTable(qb, table) {
|
|
17630
|
+
const colRows = await qb.unsafe(`SELECT c.column_name, c.data_type, c.udt_name, c.is_nullable, c.column_default, c.character_maximum_length,
|
|
17631
|
+
(SELECT COUNT(*) FROM information_schema.table_constraints tc
|
|
17632
|
+
JOIN information_schema.key_column_usage k ON k.constraint_name = tc.constraint_name
|
|
17633
|
+
WHERE tc.constraint_type = 'PRIMARY KEY' AND tc.table_name = c.table_name AND k.column_name = c.column_name) AS is_pk
|
|
17634
|
+
FROM information_schema.columns c
|
|
17635
|
+
WHERE c.table_name = $1 AND c.table_schema = 'public'
|
|
17636
|
+
ORDER BY c.ordinal_position`, [table]);
|
|
17637
|
+
const idxRows = await qb.unsafe(`SELECT i.relname AS index_name, ix.indisunique AS is_unique, ix.indisprimary AS is_primary,
|
|
17638
|
+
a.attname AS column_name, array_position(ix.indkey, a.attnum) AS ord,
|
|
17639
|
+
pg_get_expr(ix.indpred, ix.indrelid) AS where_clause
|
|
17640
|
+
FROM pg_class t
|
|
17641
|
+
JOIN pg_index ix ON t.oid = ix.indrelid
|
|
17642
|
+
JOIN pg_class i ON i.oid = ix.indexrelid
|
|
17643
|
+
JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(ix.indkey)
|
|
17644
|
+
WHERE t.relname = $1 AND t.relkind = 'r'
|
|
17645
|
+
ORDER BY i.relname, ord`, [table]);
|
|
17646
|
+
const fkRows = await qb.unsafe(`SELECT kcu.column_name, ccu.table_name AS ref_table, ccu.column_name AS ref_column,
|
|
17647
|
+
rc.delete_rule, rc.update_rule
|
|
17648
|
+
FROM information_schema.table_constraints tc
|
|
17649
|
+
JOIN information_schema.key_column_usage kcu ON kcu.constraint_name = tc.constraint_name
|
|
17650
|
+
JOIN information_schema.constraint_column_usage ccu ON ccu.constraint_name = tc.constraint_name
|
|
17651
|
+
JOIN information_schema.referential_constraints rc ON rc.constraint_name = tc.constraint_name
|
|
17652
|
+
WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_name = $1 AND tc.table_schema = 'public'`, [table]);
|
|
17653
|
+
const fkByColumn = new Map;
|
|
17654
|
+
for (const fk of fkRows) {
|
|
17655
|
+
fkByColumn.set(String(fk.column_name), {
|
|
17656
|
+
table: String(fk.ref_table),
|
|
17657
|
+
column: String(fk.ref_column ?? "id"),
|
|
17658
|
+
onDelete: asAction(fk.delete_rule),
|
|
17659
|
+
onUpdate: asAction(fk.update_rule)
|
|
17660
|
+
});
|
|
17661
|
+
}
|
|
17662
|
+
const { indexes, uniqueColumns } = groupPhysicalIndexes(table, idxRows.map((r) => ({
|
|
17663
|
+
indexName: String(r.index_name),
|
|
17664
|
+
isUnique: r.is_unique === true || r.is_unique === "t",
|
|
17665
|
+
isPrimary: r.is_primary === true || r.is_primary === "t",
|
|
17666
|
+
column: String(r.column_name),
|
|
17667
|
+
where: r.where_clause ? String(r.where_clause) : undefined
|
|
17668
|
+
})));
|
|
17669
|
+
const columns = colRows.map((r) => {
|
|
17670
|
+
const name = String(r.column_name);
|
|
17671
|
+
const isPrimaryKey = Number(r.is_pk) > 0;
|
|
17672
|
+
const rawDefault = r.column_default == null ? null : String(r.column_default);
|
|
17673
|
+
const isSerial = rawDefault != null && /nextval\(/i.test(rawDefault);
|
|
17674
|
+
const hasDefault = rawDefault != null && !isSerial;
|
|
17675
|
+
return {
|
|
17676
|
+
name,
|
|
17677
|
+
type: sqlTypeToNormalized(String(r.data_type ?? r.udt_name ?? ""), "postgres"),
|
|
17678
|
+
isPrimaryKey,
|
|
17679
|
+
isUnique: uniqueColumns.has(name),
|
|
17680
|
+
isNullable: isPrimaryKey ? false : String(r.is_nullable).toUpperCase() === "YES",
|
|
17681
|
+
hasDefault,
|
|
17682
|
+
defaultValue: hasDefault ? normalizeRawDefault(rawDefault) : undefined,
|
|
17683
|
+
references: fkByColumn.get(name)
|
|
17684
|
+
};
|
|
17685
|
+
});
|
|
17686
|
+
return { table, columns, indexes };
|
|
17687
|
+
}
|
|
17688
|
+
async function buildMysqlTable(qb, table) {
|
|
17689
|
+
const colRows = await qb.unsafe(`SELECT column_name, data_type, column_type, is_nullable, column_default, column_key
|
|
17690
|
+
FROM information_schema.columns
|
|
17691
|
+
WHERE table_name = ? AND table_schema = DATABASE()
|
|
17692
|
+
ORDER BY ordinal_position`, [table]);
|
|
17693
|
+
const idxRows = await qb.unsafe(`SELECT index_name, non_unique, seq_in_index, column_name
|
|
17694
|
+
FROM information_schema.statistics
|
|
17695
|
+
WHERE table_name = ? AND table_schema = DATABASE()
|
|
17696
|
+
ORDER BY index_name, seq_in_index`, [table]);
|
|
17697
|
+
const fkRows = await qb.unsafe(`SELECT kcu.column_name, kcu.referenced_table_name AS ref_table, kcu.referenced_column_name AS ref_column,
|
|
17698
|
+
rc.delete_rule, rc.update_rule
|
|
17699
|
+
FROM information_schema.key_column_usage kcu
|
|
17700
|
+
JOIN information_schema.referential_constraints rc
|
|
17701
|
+
ON rc.constraint_name = kcu.constraint_name AND rc.constraint_schema = kcu.table_schema
|
|
17702
|
+
WHERE kcu.table_name = ? AND kcu.table_schema = DATABASE() AND kcu.referenced_table_name IS NOT NULL`, [table]);
|
|
17703
|
+
const fkByColumn = new Map;
|
|
17704
|
+
for (const fk of fkRows) {
|
|
17705
|
+
fkByColumn.set(String(fk.column_name ?? fk.COLUMN_NAME), {
|
|
17706
|
+
table: String(fk.ref_table ?? fk.REF_TABLE),
|
|
17707
|
+
column: String(fk.ref_column ?? fk.REF_COLUMN ?? "id"),
|
|
17708
|
+
onDelete: asAction(fk.delete_rule ?? fk.DELETE_RULE),
|
|
17709
|
+
onUpdate: asAction(fk.update_rule ?? fk.UPDATE_RULE)
|
|
17710
|
+
});
|
|
17711
|
+
}
|
|
17712
|
+
const { indexes, uniqueColumns } = groupPhysicalIndexes(table, idxRows.map((r) => ({
|
|
17713
|
+
indexName: String(r.index_name ?? r.INDEX_NAME),
|
|
17714
|
+
isUnique: Number(r.non_unique ?? r.NON_UNIQUE) === 0,
|
|
17715
|
+
isPrimary: String(r.index_name ?? r.INDEX_NAME).toUpperCase() === "PRIMARY",
|
|
17716
|
+
column: String(r.column_name ?? r.COLUMN_NAME)
|
|
17717
|
+
})));
|
|
17718
|
+
const columns = colRows.map((r) => {
|
|
17719
|
+
const name = String(r.column_name ?? r.COLUMN_NAME);
|
|
17720
|
+
const columnType = String(r.column_type ?? r.COLUMN_TYPE ?? "");
|
|
17721
|
+
const enumValues = parseMysqlEnum(columnType);
|
|
17722
|
+
const key = String(r.column_key ?? r.COLUMN_KEY ?? "").toUpperCase();
|
|
17723
|
+
const isPrimaryKey = key === "PRI";
|
|
17724
|
+
const rawDefault = r.column_default ?? r.COLUMN_DEFAULT;
|
|
17725
|
+
const hasDefault = rawDefault != null;
|
|
17726
|
+
return {
|
|
17727
|
+
name,
|
|
17728
|
+
type: sqlTypeToNormalized(String(r.data_type ?? r.DATA_TYPE ?? columnType), "mysql", { enumValues }),
|
|
17729
|
+
isPrimaryKey,
|
|
17730
|
+
isUnique: uniqueColumns.has(name),
|
|
17731
|
+
isNullable: isPrimaryKey ? false : String(r.is_nullable ?? r.IS_NULLABLE).toUpperCase() === "YES",
|
|
17732
|
+
hasDefault,
|
|
17733
|
+
defaultValue: hasDefault ? normalizeRawDefault(rawDefault) : undefined,
|
|
17734
|
+
enumValues,
|
|
17735
|
+
references: fkByColumn.get(name)
|
|
17736
|
+
};
|
|
17737
|
+
});
|
|
17738
|
+
return { table, columns, indexes };
|
|
17739
|
+
}
|
|
17740
|
+
function parseMysqlEnum(columnType) {
|
|
17741
|
+
const m = columnType.match(/^enum\((.*)\)$/i);
|
|
17742
|
+
if (!m)
|
|
17743
|
+
return;
|
|
17744
|
+
return m[1].split(",").map((s) => s.trim().replace(/^'(.*)'$/s, "$1").replace(/''/g, "'")).filter((s) => s.length > 0);
|
|
17745
|
+
}
|
|
17746
|
+
function groupPhysicalIndexes(table, rows) {
|
|
17747
|
+
const byName = new Map;
|
|
17748
|
+
for (const r of rows) {
|
|
17749
|
+
if (r.isPrimary)
|
|
17750
|
+
continue;
|
|
17751
|
+
const entry = byName.get(r.indexName) ?? { isUnique: r.isUnique, isPrimary: r.isPrimary, columns: [], where: r.where };
|
|
17752
|
+
entry.columns.push(r.column);
|
|
17753
|
+
byName.set(r.indexName, entry);
|
|
17754
|
+
}
|
|
17755
|
+
const indexes = [];
|
|
17756
|
+
const uniqueColumns = new Set;
|
|
17757
|
+
for (const [physName, entry] of byName) {
|
|
17758
|
+
if (entry.columns.length === 0)
|
|
17759
|
+
continue;
|
|
17760
|
+
if (entry.isUnique && entry.columns.length === 1)
|
|
17761
|
+
uniqueColumns.add(entry.columns[0]);
|
|
17762
|
+
indexes.push({
|
|
17763
|
+
name: stripIndexPrefix(physName, table),
|
|
17764
|
+
columns: entry.columns,
|
|
17765
|
+
type: entry.isUnique ? "unique" : "index",
|
|
17766
|
+
where: entry.where
|
|
17767
|
+
});
|
|
17768
|
+
}
|
|
17769
|
+
return { indexes, uniqueColumns };
|
|
17770
|
+
}
|
|
17771
|
+
async function buildPlanFromDatabase(dialect, opts = {}) {
|
|
17772
|
+
const d = dialect || config5.dialect || "postgres";
|
|
17773
|
+
const qb = createQueryBuilder();
|
|
17774
|
+
const allTables = opts.tables?.length ? opts.tables : await listTables(qb, d);
|
|
17775
|
+
const tables = allTables.filter((t) => t !== "migrations");
|
|
17776
|
+
const out = [];
|
|
17777
|
+
for (const table of tables) {
|
|
17778
|
+
let plan;
|
|
17779
|
+
if (d === "sqlite")
|
|
17780
|
+
plan = await buildSqliteTable(qb, table);
|
|
17781
|
+
else if (d === "postgres")
|
|
17782
|
+
plan = await buildPgTable(qb, table);
|
|
17783
|
+
else
|
|
17784
|
+
plan = await buildMysqlTable(qb, table);
|
|
17785
|
+
if (plan.columns.length > 0)
|
|
17786
|
+
out.push(plan);
|
|
17787
|
+
}
|
|
17788
|
+
return { dialect: d, tables: out };
|
|
17789
|
+
}
|
|
17515
17790
|
var init_introspect_db = __esm(() => {
|
|
17516
17791
|
init_config();
|
|
17517
17792
|
init_src2();
|
|
@@ -17696,6 +17971,15 @@ class MySQLDriver {
|
|
|
17696
17971
|
}
|
|
17697
17972
|
return `ALTER TABLE ${this.quoteIdentifier(tableName)} MODIFY COLUMN ${parts.join(" ")};`;
|
|
17698
17973
|
}
|
|
17974
|
+
renameColumn(tableName, from, to) {
|
|
17975
|
+
return `ALTER TABLE ${this.quoteIdentifier(tableName)} RENAME COLUMN ${this.quoteIdentifier(from)} TO ${this.quoteIdentifier(to)};`;
|
|
17976
|
+
}
|
|
17977
|
+
renameTable(from, to) {
|
|
17978
|
+
return `RENAME TABLE ${this.quoteIdentifier(from)} TO ${this.quoteIdentifier(to)};`;
|
|
17979
|
+
}
|
|
17980
|
+
rebuildTable() {
|
|
17981
|
+
throw new Error("[migrations] rebuildTable is only implemented for SQLite; MySQL uses in-place ALTER.");
|
|
17982
|
+
}
|
|
17699
17983
|
dropTable(tableName) {
|
|
17700
17984
|
return `DROP TABLE IF EXISTS ${this.quoteIdentifier(tableName)}`;
|
|
17701
17985
|
}
|
|
@@ -17858,6 +18142,15 @@ class PostgresDriver {
|
|
|
17858
18142
|
const typeSql = this.getColumnType(column);
|
|
17859
18143
|
return `ALTER TABLE ${this.quoteIdentifier(tableName)} ALTER COLUMN ${this.quoteIdentifier(column.name)} TYPE ${typeSql} USING ${this.quoteIdentifier(column.name)}::${typeSql};`;
|
|
17860
18144
|
}
|
|
18145
|
+
renameColumn(tableName, from, to) {
|
|
18146
|
+
return `ALTER TABLE ${this.quoteIdentifier(tableName)} RENAME COLUMN ${this.quoteIdentifier(from)} TO ${this.quoteIdentifier(to)};`;
|
|
18147
|
+
}
|
|
18148
|
+
renameTable(from, to) {
|
|
18149
|
+
return `ALTER TABLE ${this.quoteIdentifier(from)} RENAME TO ${this.quoteIdentifier(to)};`;
|
|
18150
|
+
}
|
|
18151
|
+
rebuildTable() {
|
|
18152
|
+
throw new Error("[migrations] rebuildTable is only implemented for SQLite; Postgres uses in-place ALTER.");
|
|
18153
|
+
}
|
|
17861
18154
|
dropTable(tableName) {
|
|
17862
18155
|
return `DROP TABLE IF EXISTS ${this.quoteIdentifier(tableName)} CASCADE`;
|
|
17863
18156
|
}
|
|
@@ -18005,7 +18298,42 @@ class SQLiteDriver {
|
|
|
18005
18298
|
return `ALTER TABLE ${this.quoteIdentifier(tableName)} ADD COLUMN ${parts.join(" ")};`;
|
|
18006
18299
|
}
|
|
18007
18300
|
modifyColumn(tableName, column) {
|
|
18008
|
-
return `-- SQLite does not support ALTER COLUMN
|
|
18301
|
+
return `-- SQLite does not support ALTER COLUMN; a table rebuild is required to change ${this.quoteIdentifier(tableName)}.${this.quoteIdentifier(column.name)}`;
|
|
18302
|
+
}
|
|
18303
|
+
renameColumn(tableName, from, to) {
|
|
18304
|
+
return `ALTER TABLE ${this.quoteIdentifier(tableName)} RENAME COLUMN ${this.quoteIdentifier(from)} TO ${this.quoteIdentifier(to)};`;
|
|
18305
|
+
}
|
|
18306
|
+
renameTable(from, to) {
|
|
18307
|
+
return `ALTER TABLE ${this.quoteIdentifier(from)} RENAME TO ${this.quoteIdentifier(to)};`;
|
|
18308
|
+
}
|
|
18309
|
+
rebuildTable(spec) {
|
|
18310
|
+
const { target, tempName, columnSource } = spec;
|
|
18311
|
+
const q = (id) => this.quoteIdentifier(id);
|
|
18312
|
+
const columnsSql = target.columns.map((c) => this.renderColumn(c)).join(`,
|
|
18313
|
+
`);
|
|
18314
|
+
const createTmp = `CREATE TABLE ${q(tempName)} (
|
|
18315
|
+
${columnsSql}
|
|
18316
|
+
);`;
|
|
18317
|
+
const carried = target.columns.map((c) => c.name).filter((name) => columnSource[name] !== undefined);
|
|
18318
|
+
const statements = [
|
|
18319
|
+
"PRAGMA foreign_keys=OFF;",
|
|
18320
|
+
"BEGIN;",
|
|
18321
|
+
createTmp
|
|
18322
|
+
];
|
|
18323
|
+
if (carried.length > 0) {
|
|
18324
|
+
const insertCols = carried.map(q).join(", ");
|
|
18325
|
+
const selectCols = carried.map((name) => q(columnSource[name])).join(", ");
|
|
18326
|
+
statements.push(`INSERT INTO ${q(tempName)} (${insertCols}) SELECT ${selectCols} FROM ${q(target.table)};`);
|
|
18327
|
+
}
|
|
18328
|
+
statements.push(`DROP TABLE ${q(target.table)};`);
|
|
18329
|
+
statements.push(`ALTER TABLE ${q(tempName)} RENAME TO ${q(target.table)};`);
|
|
18330
|
+
for (const idx of target.indexes)
|
|
18331
|
+
statements.push(this.createIndex(target.table, idx));
|
|
18332
|
+
statements.push("PRAGMA foreign_key_check;");
|
|
18333
|
+
statements.push("COMMIT;");
|
|
18334
|
+
statements.push("PRAGMA foreign_keys=ON;");
|
|
18335
|
+
return statements.join(`
|
|
18336
|
+
`);
|
|
18009
18337
|
}
|
|
18010
18338
|
dropTable(tableName) {
|
|
18011
18339
|
return `DROP TABLE IF EXISTS ${this.quoteIdentifier(tableName)}`;
|
|
@@ -18172,6 +18500,19 @@ async function generateMigration(dir, opts = {}) {
|
|
|
18172
18500
|
}
|
|
18173
18501
|
} catch {}
|
|
18174
18502
|
}
|
|
18503
|
+
if (!previous || opts.fromDb) {
|
|
18504
|
+
try {
|
|
18505
|
+
const livePlan = await buildPlanFromDatabase(dialect);
|
|
18506
|
+
const modelTables = new Set(plan.tables.map((t) => t.table));
|
|
18507
|
+
const scoped = livePlan.tables.filter((t) => modelTables.has(t.table));
|
|
18508
|
+
if (scoped.length > 0) {
|
|
18509
|
+
previous = { dialect: livePlan.dialect, tables: scoped };
|
|
18510
|
+
info(`-- No snapshot; reconciling against live database (${scoped.length} matching table${scoped.length === 1 ? "" : "s"})`);
|
|
18511
|
+
}
|
|
18512
|
+
} catch (err) {
|
|
18513
|
+
info(`-- Could not introspect live database, treating as no previous state: ${err instanceof Error ? err.message : String(err)}`);
|
|
18514
|
+
}
|
|
18515
|
+
}
|
|
18175
18516
|
if (!previous) {
|
|
18176
18517
|
info("-- No previous snapshot found, generating full migration");
|
|
18177
18518
|
}
|
|
@@ -18179,7 +18520,9 @@ async function generateMigration(dir, opts = {}) {
|
|
|
18179
18520
|
} else {
|
|
18180
18521
|
info("-- Full migration requested, ignoring any previous state");
|
|
18181
18522
|
}
|
|
18182
|
-
const
|
|
18523
|
+
const diff = opts.full ? { statements: generateSql(plan, { dryRun: opts.dryRun }), operations: plan.tables.map((t) => ({ kind: "create_table", table: t.table, destructive: false, sql: "" })) } : generateDiffOperations(previous, plan, { applyRenames: opts.applyRenames, dryRun: opts.dryRun });
|
|
18524
|
+
const sqlStatements = diff.statements;
|
|
18525
|
+
const operations = diff.operations;
|
|
18183
18526
|
const sql = sqlStatements.join(`
|
|
18184
18527
|
`);
|
|
18185
18528
|
const hasChanges = sqlStatements.some((stmt) => /\b(?:CREATE|ALTER|DROP)\b/i.test(stmt));
|
|
@@ -18198,8 +18541,9 @@ async function generateMigration(dir, opts = {}) {
|
|
|
18198
18541
|
throw err;
|
|
18199
18542
|
}
|
|
18200
18543
|
}
|
|
18201
|
-
|
|
18202
|
-
|
|
18544
|
+
if (!opts.dryRun)
|
|
18545
|
+
savePlanSnapshot(workspaceRoot, dialect, plan);
|
|
18546
|
+
return { sql, sqlStatements, hasChanges, plan, operations };
|
|
18203
18547
|
}
|
|
18204
18548
|
async function executeMigration(dir) {
|
|
18205
18549
|
if (!dir) {
|
|
@@ -18448,6 +18792,7 @@ var init_migrate = __esm(() => {
|
|
|
18448
18792
|
init_db();
|
|
18449
18793
|
init_drivers();
|
|
18450
18794
|
init_src2();
|
|
18795
|
+
init_introspect_db();
|
|
18451
18796
|
});
|
|
18452
18797
|
|
|
18453
18798
|
// src/actions/migrate-generate.ts
|
|
@@ -24621,8 +24966,45 @@ function resolveRelation(definition, relationName) {
|
|
|
24621
24966
|
pivotTimestamps: Boolean(config6?.pivot?.timestamps)
|
|
24622
24967
|
};
|
|
24623
24968
|
}
|
|
24969
|
+
const resolveThrough = (field, type) => {
|
|
24970
|
+
if (!field || typeof field !== "object" || Array.isArray(field))
|
|
24971
|
+
return null;
|
|
24972
|
+
const entry = field[relationName] ?? field[Object.keys(field).find((k2) => k2.toLowerCase() === relationName.toLowerCase()) ?? ""];
|
|
24973
|
+
if (!entry || typeof entry !== "object")
|
|
24974
|
+
return null;
|
|
24975
|
+
const throughModelName = entry.through;
|
|
24976
|
+
const targetModelName = entry.target;
|
|
24977
|
+
if (!throughModelName || !targetModelName)
|
|
24978
|
+
return null;
|
|
24979
|
+
const throughModel = getModelFromRegistry(throughModelName);
|
|
24980
|
+
const throughDef = throughModel?.getDefinition?.() || throughModel?.definition;
|
|
24981
|
+
const throughTable = throughModel?.getTable?.() || throughDef?.table || toTableName(throughModelName);
|
|
24982
|
+
const throughPk = throughDef?.primaryKey || "id";
|
|
24983
|
+
const targetModel = getModelFromRegistry(targetModelName);
|
|
24984
|
+
const targetTable = targetModel?.getTable?.() || toTableName(targetModelName);
|
|
24985
|
+
return {
|
|
24986
|
+
type,
|
|
24987
|
+
relatedModelName: targetModelName,
|
|
24988
|
+
relatedTable: targetTable,
|
|
24989
|
+
foreignKey: `${toSnakeCase(parentName)}_id`,
|
|
24990
|
+
localKey: parentPk,
|
|
24991
|
+
throughTable,
|
|
24992
|
+
throughForeignKey: `${toSnakeCase(parentName)}_id`,
|
|
24993
|
+
throughLocalKey: throughPk,
|
|
24994
|
+
targetForeignKey: `${singularizeWord(throughTable)}_id`
|
|
24995
|
+
};
|
|
24996
|
+
};
|
|
24997
|
+
const hmt = resolveThrough(definition.hasManyThrough, "hasManyThrough");
|
|
24998
|
+
if (hmt)
|
|
24999
|
+
return hmt;
|
|
25000
|
+
const hot = resolveThrough(definition.hasOneThrough, "hasOneThrough");
|
|
25001
|
+
if (hot)
|
|
25002
|
+
return hot;
|
|
24624
25003
|
return null;
|
|
24625
25004
|
}
|
|
25005
|
+
function singularizeWord(table) {
|
|
25006
|
+
return table.endsWith("s") ? table.slice(0, -1) : table;
|
|
25007
|
+
}
|
|
24626
25008
|
|
|
24627
25009
|
class BelongsToManyRelationBuilder {
|
|
24628
25010
|
_parent;
|
|
@@ -25259,6 +25641,40 @@ class ModelQueryBuilder {
|
|
|
25259
25641
|
instance.setRelation(relationName, relatedInstances);
|
|
25260
25642
|
}
|
|
25261
25643
|
}
|
|
25644
|
+
if (rel.type === "hasManyThrough" || rel.type === "hasOneThrough") {
|
|
25645
|
+
const parentIds = instances.map((i2) => i2.get(pk)).filter((id) => id != null);
|
|
25646
|
+
if (parentIds.length === 0)
|
|
25647
|
+
continue;
|
|
25648
|
+
const throughPk = rel.throughLocalKey || "id";
|
|
25649
|
+
const throughPh = parentIds.map(() => "?").join(", ");
|
|
25650
|
+
const throughRows = await exec.all(`SELECT ${throughPk}, ${rel.throughForeignKey} FROM ${rel.throughTable} WHERE ${rel.throughForeignKey} IN (${throughPh})`, parentIds);
|
|
25651
|
+
if (throughRows.length === 0) {
|
|
25652
|
+
for (const instance of instances)
|
|
25653
|
+
instance.setRelation(relationName, rel.type === "hasManyThrough" ? [] : null);
|
|
25654
|
+
continue;
|
|
25655
|
+
}
|
|
25656
|
+
const throughToParent = new Map;
|
|
25657
|
+
for (const t2 of throughRows)
|
|
25658
|
+
throughToParent.set(t2[throughPk], t2[rel.throughForeignKey]);
|
|
25659
|
+
const throughIds = [...new Set(throughRows.map((t2) => t2[throughPk]))];
|
|
25660
|
+
const targetPh = throughIds.map(() => "?").join(", ");
|
|
25661
|
+
const targetRows = await exec.all(`SELECT * FROM ${rel.relatedTable} WHERE ${rel.targetForeignKey} IN (${targetPh})`, throughIds);
|
|
25662
|
+
const relatedModelDef = getModelFromRegistry(rel.relatedModelName);
|
|
25663
|
+
const relDef = relatedModelDef?.getDefinition?.() || relatedModelDef?.definition || this._definition;
|
|
25664
|
+
const byParent = new Map;
|
|
25665
|
+
for (const row of targetRows) {
|
|
25666
|
+
const parentVal = throughToParent.get(row[rel.targetForeignKey]);
|
|
25667
|
+
if (parentVal == null)
|
|
25668
|
+
continue;
|
|
25669
|
+
if (!byParent.has(parentVal))
|
|
25670
|
+
byParent.set(parentVal, []);
|
|
25671
|
+
byParent.get(parentVal).push(new ModelInstance(relDef, row));
|
|
25672
|
+
}
|
|
25673
|
+
for (const instance of instances) {
|
|
25674
|
+
const group = byParent.get(instance.get(pk)) || [];
|
|
25675
|
+
instance.setRelation(relationName, rel.type === "hasManyThrough" ? group : group[0] ?? null);
|
|
25676
|
+
}
|
|
25677
|
+
}
|
|
25262
25678
|
}
|
|
25263
25679
|
}
|
|
25264
25680
|
async get() {
|
|
@@ -27731,11 +28147,12 @@ function buildMigrationPlan2(models, options) {
|
|
|
27731
28147
|
}
|
|
27732
28148
|
return { dialect: options.dialect, tables };
|
|
27733
28149
|
}
|
|
27734
|
-
function generateSql(plan) {
|
|
28150
|
+
function generateSql(plan, opts = {}) {
|
|
27735
28151
|
migrationCounter = 0;
|
|
27736
28152
|
migrationsCreatedCount = 0;
|
|
27737
28153
|
migrationsUpdatedCount = 0;
|
|
27738
28154
|
useDeterministicNames = true;
|
|
28155
|
+
const emit = opts.dryRun ? () => false : createMigrationFile;
|
|
27739
28156
|
const statements = [];
|
|
27740
28157
|
const driver = getDialectDriver(plan.dialect);
|
|
27741
28158
|
for (const t2 of plan.tables) {
|
|
@@ -27759,7 +28176,7 @@ function generateSql(plan) {
|
|
|
27759
28176
|
const combinedStatement = tableStatements.join(`
|
|
27760
28177
|
|
|
27761
28178
|
`);
|
|
27762
|
-
|
|
28179
|
+
emit(combinedStatement, `create-${t2.table}-table`);
|
|
27763
28180
|
}
|
|
27764
28181
|
for (const t2 of plan.tables) {
|
|
27765
28182
|
for (const c2 of t2.columns) {
|
|
@@ -27768,7 +28185,7 @@ function generateSql(plan) {
|
|
|
27768
28185
|
if (!alterTableStatement)
|
|
27769
28186
|
continue;
|
|
27770
28187
|
statements.push(alterTableStatement);
|
|
27771
|
-
|
|
28188
|
+
emit(alterTableStatement, `alter-${t2.table}-${c2.name}`);
|
|
27772
28189
|
}
|
|
27773
28190
|
}
|
|
27774
28191
|
}
|
|
@@ -27776,7 +28193,7 @@ function generateSql(plan) {
|
|
|
27776
28193
|
for (const idx of t2.indexes) {
|
|
27777
28194
|
const createIndexStatement = driver.createIndex(t2.table, idx);
|
|
27778
28195
|
statements.push(createIndexStatement);
|
|
27779
|
-
|
|
28196
|
+
emit(createIndexStatement, `create-${idx.name}-index-in-${t2.table}`);
|
|
27780
28197
|
}
|
|
27781
28198
|
}
|
|
27782
28199
|
const totalChanges = migrationsCreatedCount + migrationsUpdatedCount;
|
|
@@ -27827,31 +28244,138 @@ function mapColumnsByName(columns) {
|
|
|
27827
28244
|
map[c2.name] = c2;
|
|
27828
28245
|
return map;
|
|
27829
28246
|
}
|
|
27830
|
-
function
|
|
27831
|
-
if (
|
|
28247
|
+
function canonicalStorageType(col, dialect) {
|
|
28248
|
+
if (dialect === "sqlite") {
|
|
28249
|
+
if (col.name.endsWith("_id"))
|
|
28250
|
+
return "INTEGER";
|
|
28251
|
+
switch (col.type) {
|
|
28252
|
+
case "string":
|
|
28253
|
+
case "text":
|
|
28254
|
+
case "date":
|
|
28255
|
+
case "datetime":
|
|
28256
|
+
case "json":
|
|
28257
|
+
case "enum":
|
|
28258
|
+
return "TEXT";
|
|
28259
|
+
case "boolean":
|
|
28260
|
+
case "integer":
|
|
28261
|
+
case "bigint":
|
|
28262
|
+
return "INTEGER";
|
|
28263
|
+
case "float":
|
|
28264
|
+
case "double":
|
|
28265
|
+
case "decimal":
|
|
28266
|
+
return "REAL";
|
|
28267
|
+
default:
|
|
28268
|
+
return "TEXT";
|
|
28269
|
+
}
|
|
28270
|
+
}
|
|
28271
|
+
return col.type;
|
|
28272
|
+
}
|
|
28273
|
+
function canonicalizeDefault(col) {
|
|
28274
|
+
if (!col.hasDefault || col.defaultValue === undefined)
|
|
28275
|
+
return;
|
|
28276
|
+
const dv = col.defaultValue;
|
|
28277
|
+
if (dv instanceof Date)
|
|
28278
|
+
return dv.toISOString();
|
|
28279
|
+
if (typeof dv === "boolean")
|
|
28280
|
+
return dv ? "1" : "0";
|
|
28281
|
+
if (typeof dv === "bigint" || typeof dv === "number")
|
|
28282
|
+
return String(dv);
|
|
28283
|
+
let s2 = String(dv).trim();
|
|
28284
|
+
s2 = s2.replace(/::[\w ]+$/i, "").trim();
|
|
28285
|
+
if (s2.startsWith("'") && s2.endsWith("'") || s2.startsWith('"') && s2.endsWith('"'))
|
|
28286
|
+
s2 = s2.slice(1, -1);
|
|
28287
|
+
const upper = s2.toUpperCase();
|
|
28288
|
+
const sqlFns = ["CURRENT_TIMESTAMP", "NOW()", "CURRENT_DATE", "CURRENT_TIME", "NULL", "TRUE", "FALSE"];
|
|
28289
|
+
if (sqlFns.includes(upper))
|
|
28290
|
+
return upper === "NOW()" ? "CURRENT_TIMESTAMP" : upper;
|
|
28291
|
+
if (col.type === "boolean") {
|
|
28292
|
+
if (/^(?:1|t|true|yes)$/i.test(s2))
|
|
28293
|
+
return "1";
|
|
28294
|
+
if (/^(?:0|f|false|no)$/i.test(s2))
|
|
28295
|
+
return "0";
|
|
28296
|
+
}
|
|
28297
|
+
if (/^-?\d+(?:\.\d+)?$/.test(s2))
|
|
28298
|
+
return String(Number(s2));
|
|
28299
|
+
return s2;
|
|
28300
|
+
}
|
|
28301
|
+
function columnsAreDifferent(col1, col2, dialect) {
|
|
28302
|
+
if (canonicalStorageType(col1, dialect) !== canonicalStorageType(col2, dialect))
|
|
27832
28303
|
return true;
|
|
27833
28304
|
if (col1.isNullable !== col2.isNullable)
|
|
27834
28305
|
return true;
|
|
27835
28306
|
if (col1.hasDefault !== col2.hasDefault)
|
|
27836
28307
|
return true;
|
|
27837
|
-
if (col1
|
|
28308
|
+
if (canonicalizeDefault(col1) !== canonicalizeDefault(col2))
|
|
27838
28309
|
return true;
|
|
27839
28310
|
if (col1.isUnique !== col2.isUnique)
|
|
27840
28311
|
return true;
|
|
27841
28312
|
if (col1.type === "enum" && col2.type === "enum") {
|
|
27842
|
-
const enum1 = (col1.enumValues || []).sort().join(",");
|
|
27843
|
-
const enum2 = (col2.enumValues || []).sort().join(",");
|
|
28313
|
+
const enum1 = (col1.enumValues || []).slice().sort().join(",");
|
|
28314
|
+
const enum2 = (col2.enumValues || []).slice().sort().join(",");
|
|
27844
28315
|
if (enum1 !== enum2)
|
|
27845
28316
|
return true;
|
|
27846
28317
|
}
|
|
27847
28318
|
return false;
|
|
27848
28319
|
}
|
|
28320
|
+
function columnSignature(col, dialect) {
|
|
28321
|
+
return JSON.stringify([
|
|
28322
|
+
canonicalStorageType(col, dialect),
|
|
28323
|
+
col.isPrimaryKey,
|
|
28324
|
+
col.isUnique,
|
|
28325
|
+
col.isNullable,
|
|
28326
|
+
col.hasDefault,
|
|
28327
|
+
canonicalizeDefault(col) ?? null,
|
|
28328
|
+
(col.enumValues ?? []).slice().sort(),
|
|
28329
|
+
col.references ? [col.references.table, col.references.column, col.references.onDelete ?? null, col.references.onUpdate ?? null] : null
|
|
28330
|
+
]);
|
|
28331
|
+
}
|
|
28332
|
+
function detectColumnRenames(prevCols, currCols, dialect) {
|
|
28333
|
+
const removed = Object.keys(prevCols).filter((n2) => !currCols[n2]);
|
|
28334
|
+
const added = Object.keys(currCols).filter((n2) => !prevCols[n2]);
|
|
28335
|
+
const sigRemoved = new Map;
|
|
28336
|
+
const sigAdded = new Map;
|
|
28337
|
+
for (const n2 of removed) {
|
|
28338
|
+
const s2 = columnSignature(prevCols[n2], dialect);
|
|
28339
|
+
const list = sigRemoved.get(s2) ?? [];
|
|
28340
|
+
list.push(n2);
|
|
28341
|
+
sigRemoved.set(s2, list);
|
|
28342
|
+
}
|
|
28343
|
+
for (const n2 of added) {
|
|
28344
|
+
const s2 = columnSignature(currCols[n2], dialect);
|
|
28345
|
+
const list = sigAdded.get(s2) ?? [];
|
|
28346
|
+
list.push(n2);
|
|
28347
|
+
sigAdded.set(s2, list);
|
|
28348
|
+
}
|
|
28349
|
+
const renames = [];
|
|
28350
|
+
const usedRemoved = new Set;
|
|
28351
|
+
const usedAdded = new Set;
|
|
28352
|
+
for (const [sig, rem] of sigRemoved) {
|
|
28353
|
+
const add = sigAdded.get(sig);
|
|
28354
|
+
if (rem.length === 1 && add && add.length === 1) {
|
|
28355
|
+
renames.push({ from: rem[0], to: add[0] });
|
|
28356
|
+
usedRemoved.add(rem[0]);
|
|
28357
|
+
usedAdded.add(add[0]);
|
|
28358
|
+
}
|
|
28359
|
+
}
|
|
28360
|
+
return {
|
|
28361
|
+
renames,
|
|
28362
|
+
removed: removed.filter((n2) => !usedRemoved.has(n2)),
|
|
28363
|
+
added: added.filter((n2) => !usedAdded.has(n2))
|
|
28364
|
+
};
|
|
28365
|
+
}
|
|
28366
|
+
function isColumnConstrained(table, columnName) {
|
|
28367
|
+
const col = table.columns.find((c2) => c2.name === columnName);
|
|
28368
|
+
if (col && (col.isPrimaryKey || col.isUnique || col.references))
|
|
28369
|
+
return true;
|
|
28370
|
+
return table.indexes.some((idx) => idx.columns.includes(columnName));
|
|
28371
|
+
}
|
|
27849
28372
|
function referencesAreDifferent(r1, r2) {
|
|
27850
28373
|
if (Boolean(r1) !== Boolean(r2))
|
|
27851
28374
|
return true;
|
|
27852
28375
|
if (!r1 || !r2)
|
|
27853
28376
|
return false;
|
|
27854
|
-
|
|
28377
|
+
const act = (a2) => (a2 ?? "no action").toLowerCase();
|
|
28378
|
+
return r1.table !== r2.table || r1.column !== r2.column || act(r1.onDelete) !== act(r2.onDelete) || act(r1.onUpdate) !== act(r2.onUpdate);
|
|
27855
28379
|
}
|
|
27856
28380
|
function mapIndexesByKey(indexes) {
|
|
27857
28381
|
const map = {};
|
|
@@ -27861,22 +28385,35 @@ function mapIndexesByKey(indexes) {
|
|
|
27861
28385
|
}
|
|
27862
28386
|
return map;
|
|
27863
28387
|
}
|
|
27864
|
-
function
|
|
27865
|
-
if (!previous || previous.dialect !== next.dialect)
|
|
27866
|
-
|
|
28388
|
+
function generateDiffOperations(previous, next, opts = {}) {
|
|
28389
|
+
if (!previous || previous.dialect !== next.dialect) {
|
|
28390
|
+
const statements2 = generateSql(next, { dryRun: opts.dryRun });
|
|
28391
|
+
const operations2 = next.tables.map((t2) => ({
|
|
28392
|
+
kind: "create_table",
|
|
28393
|
+
table: t2.table,
|
|
28394
|
+
destructive: false,
|
|
28395
|
+
sql: ""
|
|
28396
|
+
}));
|
|
28397
|
+
return { statements: statements2, operations: operations2 };
|
|
28398
|
+
}
|
|
28399
|
+
const applyRenames = opts.applyRenames !== false;
|
|
28400
|
+
const emit = opts.dryRun ? () => false : createMigrationFile;
|
|
27867
28401
|
migrationCounter = 0;
|
|
27868
28402
|
migrationsCreatedCount = 0;
|
|
27869
28403
|
migrationsUpdatedCount = 0;
|
|
27870
28404
|
useDeterministicNames = false;
|
|
27871
28405
|
const chunks = [];
|
|
28406
|
+
const operations = [];
|
|
27872
28407
|
const driver = getDialectDriver(next.dialect);
|
|
28408
|
+
const dialect = next.dialect;
|
|
27873
28409
|
const prevTables = mapTablesByName(previous.tables);
|
|
27874
28410
|
const nextTables = mapTablesByName(next.tables);
|
|
27875
28411
|
for (const tableName of Object.keys(prevTables)) {
|
|
27876
28412
|
if (!nextTables[tableName]) {
|
|
27877
28413
|
const dropTableStatement = driver.dropTable(tableName);
|
|
27878
28414
|
chunks.push(dropTableStatement);
|
|
27879
|
-
|
|
28415
|
+
operations.push({ kind: "drop_table", table: tableName, destructive: true, sql: dropTableStatement });
|
|
28416
|
+
emit(dropTableStatement, `drop-${tableName}-table`);
|
|
27880
28417
|
info2(`-- Detected dropped table: ${tableName}`);
|
|
27881
28418
|
}
|
|
27882
28419
|
}
|
|
@@ -27900,10 +28437,11 @@ function generateDiffSql(previous, next) {
|
|
|
27900
28437
|
const createTableStatement = driver.createTable(t2);
|
|
27901
28438
|
tableStatements.push(createTableStatement);
|
|
27902
28439
|
chunks.push(...tableStatements);
|
|
28440
|
+
operations.push({ kind: "create_table", table: t2.table, destructive: false, sql: createTableStatement });
|
|
27903
28441
|
const combinedStatement = tableStatements.join(`
|
|
27904
28442
|
|
|
27905
28443
|
`);
|
|
27906
|
-
|
|
28444
|
+
emit(combinedStatement, `create-${t2.table}-table`);
|
|
27907
28445
|
}
|
|
27908
28446
|
}
|
|
27909
28447
|
for (const tableName of Object.keys(nextTables)) {
|
|
@@ -27912,7 +28450,8 @@ function generateDiffSql(previous, next) {
|
|
|
27912
28450
|
for (const idx of t2.indexes) {
|
|
27913
28451
|
const createIndexStatement = driver.createIndex(t2.table, idx);
|
|
27914
28452
|
chunks.push(createIndexStatement);
|
|
27915
|
-
|
|
28453
|
+
operations.push({ kind: "create_index", table: t2.table, column: idx.name, destructive: false, sql: createIndexStatement });
|
|
28454
|
+
emit(createIndexStatement, `create-${idx.name}-index-in-${t2.table}`);
|
|
27916
28455
|
}
|
|
27917
28456
|
}
|
|
27918
28457
|
}
|
|
@@ -27925,7 +28464,8 @@ function generateDiffSql(previous, next) {
|
|
|
27925
28464
|
if (!alterTableStatement)
|
|
27926
28465
|
continue;
|
|
27927
28466
|
chunks.push(alterTableStatement);
|
|
27928
|
-
|
|
28467
|
+
operations.push({ kind: "add_foreign_key", table: t2.table, column: c2.name, destructive: false, sql: alterTableStatement });
|
|
28468
|
+
emit(alterTableStatement, `alter-${t2.table}-${c2.name}`);
|
|
27929
28469
|
}
|
|
27930
28470
|
}
|
|
27931
28471
|
}
|
|
@@ -27947,7 +28487,8 @@ function generateDiffSql(previous, next) {
|
|
|
27947
28487
|
const createEnumStatement = driver.createEnumType(enumTypeName, c2.enumValues);
|
|
27948
28488
|
if (createEnumStatement) {
|
|
27949
28489
|
chunks.push(createEnumStatement);
|
|
27950
|
-
|
|
28490
|
+
operations.push({ kind: "create_enum", table: curr.table, column: c2.name, destructive: false, sql: createEnumStatement });
|
|
28491
|
+
emit(createEnumStatement, `create-${enumTypeName}-enum`);
|
|
27951
28492
|
}
|
|
27952
28493
|
enumTypes.add(enumTypeName);
|
|
27953
28494
|
}
|
|
@@ -27964,6 +28505,52 @@ function generateDiffSql(previous, next) {
|
|
|
27964
28505
|
const currCols = mapColumnsByName(curr.columns);
|
|
27965
28506
|
const prevIdx = mapIndexesByKey(prev.indexes);
|
|
27966
28507
|
const currIdx = mapIndexesByKey(curr.indexes);
|
|
28508
|
+
let renames = [];
|
|
28509
|
+
let removedCols = Object.keys(prevCols).filter((n2) => !currCols[n2]);
|
|
28510
|
+
let addedCols = Object.keys(currCols).filter((n2) => !prevCols[n2]);
|
|
28511
|
+
if (applyRenames) {
|
|
28512
|
+
const detected = detectColumnRenames(prevCols, currCols, dialect);
|
|
28513
|
+
renames = detected.renames;
|
|
28514
|
+
removedCols = detected.removed;
|
|
28515
|
+
addedCols = detected.added;
|
|
28516
|
+
}
|
|
28517
|
+
const modifiedCols = Object.keys(currCols).filter((n2) => prevCols[n2] && columnsAreDifferent(prevCols[n2], currCols[n2], dialect));
|
|
28518
|
+
const fkChangedCols = Object.keys(currCols).filter((n2) => prevCols[n2] && referencesAreDifferent(prevCols[n2].references, currCols[n2].references));
|
|
28519
|
+
if (dialect === "sqlite") {
|
|
28520
|
+
const needsRebuild = modifiedCols.length > 0 || fkChangedCols.length > 0 || removedCols.some((name) => isColumnConstrained(prev, name));
|
|
28521
|
+
if (needsRebuild) {
|
|
28522
|
+
const columnSource = {};
|
|
28523
|
+
const renameTo = new Map(renames.map((r2) => [r2.to, r2.from]));
|
|
28524
|
+
for (const c2 of curr.columns) {
|
|
28525
|
+
if (prevCols[c2.name])
|
|
28526
|
+
columnSource[c2.name] = c2.name;
|
|
28527
|
+
else if (renameTo.has(c2.name))
|
|
28528
|
+
columnSource[c2.name] = renameTo.get(c2.name);
|
|
28529
|
+
}
|
|
28530
|
+
let tempName = `_qb_tmp_${curr.table}`;
|
|
28531
|
+
let guard = 0;
|
|
28532
|
+
while ((prevTables[tempName] || nextTables[tempName]) && guard < 100) {
|
|
28533
|
+
guard += 1;
|
|
28534
|
+
tempName = `_qb_tmp_${curr.table}_${guard}`;
|
|
28535
|
+
}
|
|
28536
|
+
const rebuildStatement = driver.rebuildTable({ target: curr, tempName, columnSource });
|
|
28537
|
+
chunks.push(rebuildStatement);
|
|
28538
|
+
const typeChanged = modifiedCols.some((n2) => prevCols[n2].type !== currCols[n2].type);
|
|
28539
|
+
operations.push({
|
|
28540
|
+
kind: "rebuild_table",
|
|
28541
|
+
table: curr.table,
|
|
28542
|
+
destructive: removedCols.length > 0 || typeChanged,
|
|
28543
|
+
sql: rebuildStatement
|
|
28544
|
+
});
|
|
28545
|
+
for (const r2 of renames)
|
|
28546
|
+
operations.push({ kind: "rename_column", table: curr.table, from: r2.from, to: r2.to, destructive: false, confidence: "high", sql: rebuildStatement });
|
|
28547
|
+
for (const name of removedCols)
|
|
28548
|
+
operations.push({ kind: "drop_column", table: curr.table, column: name, destructive: true, sql: rebuildStatement });
|
|
28549
|
+
info2(`-- Detected SQLite table rebuild required: ${curr.table}`);
|
|
28550
|
+
emit(rebuildStatement, `alter-${curr.table}-table`);
|
|
28551
|
+
continue;
|
|
28552
|
+
}
|
|
28553
|
+
}
|
|
27967
28554
|
const tableChanges = [];
|
|
27968
28555
|
let hasChanges = false;
|
|
27969
28556
|
for (const key of Object.keys(prevIdx)) {
|
|
@@ -27972,51 +28559,70 @@ function generateDiffSql(previous, next) {
|
|
|
27972
28559
|
const dropIndexStatement = driver.dropIndex(curr.table, idx.name);
|
|
27973
28560
|
tableChanges.push(dropIndexStatement);
|
|
27974
28561
|
chunks.push(dropIndexStatement);
|
|
28562
|
+
operations.push({ kind: "drop_index", table: curr.table, column: idx.name, destructive: false, sql: dropIndexStatement });
|
|
27975
28563
|
info2(`-- Detected dropped index: ${idx.name} from ${curr.table}`);
|
|
27976
28564
|
hasChanges = true;
|
|
27977
28565
|
}
|
|
27978
28566
|
}
|
|
27979
|
-
for (const
|
|
27980
|
-
|
|
27981
|
-
|
|
27982
|
-
|
|
27983
|
-
|
|
27984
|
-
|
|
27985
|
-
|
|
27986
|
-
|
|
27987
|
-
|
|
27988
|
-
|
|
27989
|
-
|
|
27990
|
-
|
|
27991
|
-
|
|
27992
|
-
|
|
27993
|
-
|
|
27994
|
-
|
|
27995
|
-
|
|
27996
|
-
|
|
27997
|
-
|
|
27998
|
-
|
|
27999
|
-
|
|
28000
|
-
|
|
28001
|
-
|
|
28002
|
-
|
|
28003
|
-
|
|
28004
|
-
|
|
28005
|
-
|
|
28006
|
-
|
|
28567
|
+
for (const r2 of renames) {
|
|
28568
|
+
const renameStatement = driver.renameColumn(curr.table, r2.from, r2.to);
|
|
28569
|
+
tableChanges.push(renameStatement);
|
|
28570
|
+
chunks.push(renameStatement);
|
|
28571
|
+
operations.push({ kind: "rename_column", table: curr.table, from: r2.from, to: r2.to, destructive: false, confidence: "high", sql: renameStatement });
|
|
28572
|
+
info2(`-- Detected renamed column: ${curr.table}.${r2.from} -> ${r2.to}`);
|
|
28573
|
+
hasChanges = true;
|
|
28574
|
+
}
|
|
28575
|
+
for (const colName of removedCols) {
|
|
28576
|
+
const dropColumnStatement = driver.dropColumn(curr.table, colName);
|
|
28577
|
+
tableChanges.push(dropColumnStatement);
|
|
28578
|
+
chunks.push(dropColumnStatement);
|
|
28579
|
+
operations.push({ kind: "drop_column", table: curr.table, column: colName, destructive: true, sql: dropColumnStatement });
|
|
28580
|
+
info2(`-- Detected dropped column: ${curr.table}.${colName}`);
|
|
28581
|
+
hasChanges = true;
|
|
28582
|
+
}
|
|
28583
|
+
for (const colName of modifiedCols) {
|
|
28584
|
+
const prevCol = prevCols[colName];
|
|
28585
|
+
const currCol = currCols[colName];
|
|
28586
|
+
const modifyColumnStatement = driver.modifyColumn(curr.table, currCol);
|
|
28587
|
+
tableChanges.push(modifyColumnStatement);
|
|
28588
|
+
chunks.push(modifyColumnStatement);
|
|
28589
|
+
operations.push({
|
|
28590
|
+
kind: "modify_column",
|
|
28591
|
+
table: curr.table,
|
|
28592
|
+
column: colName,
|
|
28593
|
+
destructive: prevCol.type !== currCol.type,
|
|
28594
|
+
sql: modifyColumnStatement
|
|
28595
|
+
});
|
|
28596
|
+
info2(`-- Detected column change: ${curr.table}.${colName} (${prevCol.type} -> ${currCol.type})`);
|
|
28597
|
+
hasChanges = true;
|
|
28007
28598
|
}
|
|
28008
|
-
for (const colName of
|
|
28009
|
-
|
|
28010
|
-
|
|
28011
|
-
|
|
28012
|
-
|
|
28013
|
-
|
|
28014
|
-
|
|
28015
|
-
|
|
28016
|
-
|
|
28017
|
-
|
|
28599
|
+
for (const colName of fkChangedCols) {
|
|
28600
|
+
const currCol = currCols[colName];
|
|
28601
|
+
if (!currCol.references)
|
|
28602
|
+
continue;
|
|
28603
|
+
const addFkStatement = driver.addForeignKey(curr.table, currCol.name, currCol.references.table, currCol.references.column, currCol.references.onDelete, currCol.references.onUpdate);
|
|
28604
|
+
if (!addFkStatement)
|
|
28605
|
+
continue;
|
|
28606
|
+
tableChanges.push(addFkStatement);
|
|
28607
|
+
chunks.push(addFkStatement);
|
|
28608
|
+
operations.push({ kind: "add_foreign_key", table: curr.table, column: colName, destructive: false, sql: addFkStatement });
|
|
28609
|
+
info2(`-- Detected foreign-key change: ${curr.table}.${colName} -> ${currCol.references.table}(${currCol.references.column})`);
|
|
28610
|
+
hasChanges = true;
|
|
28611
|
+
}
|
|
28612
|
+
for (const colName of addedCols) {
|
|
28613
|
+
const c2 = currCols[colName];
|
|
28614
|
+
const addColumnStatement = driver.addColumn(curr.table, c2);
|
|
28615
|
+
tableChanges.push(addColumnStatement);
|
|
28616
|
+
chunks.push(addColumnStatement);
|
|
28617
|
+
operations.push({ kind: "add_column", table: curr.table, column: c2.name, destructive: false, sql: addColumnStatement });
|
|
28618
|
+
info2(`-- Detected new column: ${curr.table}.${c2.name}`);
|
|
28619
|
+
hasChanges = true;
|
|
28620
|
+
if (c2.references) {
|
|
28621
|
+
const addFkStatement = driver.addForeignKey(curr.table, c2.name, c2.references.table, c2.references.column, c2.references.onDelete, c2.references.onUpdate);
|
|
28622
|
+
if (addFkStatement) {
|
|
28018
28623
|
tableChanges.push(addFkStatement);
|
|
28019
28624
|
chunks.push(addFkStatement);
|
|
28625
|
+
operations.push({ kind: "add_foreign_key", table: curr.table, column: c2.name, destructive: false, sql: addFkStatement });
|
|
28020
28626
|
}
|
|
28021
28627
|
}
|
|
28022
28628
|
}
|
|
@@ -28026,6 +28632,7 @@ function generateDiffSql(previous, next) {
|
|
|
28026
28632
|
const createIndexStatement = driver.createIndex(curr.table, idx);
|
|
28027
28633
|
tableChanges.push(createIndexStatement);
|
|
28028
28634
|
chunks.push(createIndexStatement);
|
|
28635
|
+
operations.push({ kind: "create_index", table: curr.table, column: idx.name, destructive: false, sql: createIndexStatement });
|
|
28029
28636
|
info2(`-- Detected new index: ${idx.name} in ${curr.table}`);
|
|
28030
28637
|
hasChanges = true;
|
|
28031
28638
|
}
|
|
@@ -28034,7 +28641,7 @@ function generateDiffSql(previous, next) {
|
|
|
28034
28641
|
const combinedStatement = tableChanges.join(`
|
|
28035
28642
|
|
|
28036
28643
|
`);
|
|
28037
|
-
|
|
28644
|
+
emit(combinedStatement, `alter-${curr.table}-table`);
|
|
28038
28645
|
}
|
|
28039
28646
|
}
|
|
28040
28647
|
const totalChanges = migrationsCreatedCount + migrationsUpdatedCount;
|
|
@@ -28048,9 +28655,11 @@ function generateDiffSql(previous, next) {
|
|
|
28048
28655
|
parts.push(`${migrationsUpdatedCount} updated`);
|
|
28049
28656
|
info2(`-- Migration files: ${parts.join(", ")}`);
|
|
28050
28657
|
}
|
|
28051
|
-
|
|
28052
|
-
|
|
28053
|
-
|
|
28658
|
+
const statements = chunks.length === 0 ? ["-- No changes detected"] : chunks;
|
|
28659
|
+
return { statements, operations };
|
|
28660
|
+
}
|
|
28661
|
+
function generateDiffSql(previous, next, opts = {}) {
|
|
28662
|
+
return generateDiffOperations(previous, next, opts).statements;
|
|
28054
28663
|
}
|
|
28055
28664
|
var migrationCounter = 0, migrationsCreatedCount = 0, migrationsUpdatedCount = 0, useDeterministicNames = true;
|
|
28056
28665
|
var init_migrations = __esm(() => {
|
|
@@ -28166,6 +28775,7 @@ export {
|
|
|
28166
28775
|
generateMigration,
|
|
28167
28776
|
generateDiffSqlString,
|
|
28168
28777
|
generateDiffSql,
|
|
28778
|
+
generateDiffOperations,
|
|
28169
28779
|
relationDiagram as generateDiagram,
|
|
28170
28780
|
generateAccessPatterns,
|
|
28171
28781
|
freshDatabase,
|
|
@@ -28211,6 +28821,8 @@ export {
|
|
|
28211
28821
|
clearQueryCache,
|
|
28212
28822
|
clearModelRegistry,
|
|
28213
28823
|
checkSchema,
|
|
28824
|
+
canonicalizeDefault,
|
|
28825
|
+
canonicalStorageType,
|
|
28214
28826
|
cacheStats,
|
|
28215
28827
|
cacheConfig,
|
|
28216
28828
|
cacheClear,
|