Skip to content

Commit 03ebf69

Browse files
committed
fix: use old column names in SELECT during SQLite table recreate on column rename
When a column rename coincides with a table recreation (e.g., renaming a column while also changing its type or default), the INSERT INTO...SELECT statement incorrectly uses the new column name in the SELECT clause. The old table still has the old column name, causing the migration to fail. This fix tracks column renames separately in the statement combiner and injects them into the recreate_table statement. The SQL generator then uses old column names in the SELECT and new column names in the INSERT.
1 parent 273c780 commit 03ebf69

4 files changed

Lines changed: 71 additions & 4 deletions

File tree

drizzle-kit/src/jsonStatements.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export interface JsonRecreateTableStatement {
7272
compositePKs: string[][];
7373
uniqueConstraints?: string[];
7474
checkConstraints: string[];
75+
columnRenames?: { oldName: string; newName: string }[];
7576
}
7677

7778
export interface JsonRecreateSingleStoreTableStatement {

drizzle-kit/src/sqlgenerator.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3769,11 +3769,23 @@ class SQLiteRecreateTableConvertor extends Convertor {
37693769
}
37703770

37713771
convert(statement: JsonRecreateTableStatement): string | string[] {
3772-
const { tableName, columns, compositePKs, referenceData, checkConstraints } = statement;
3772+
const { tableName, columns, compositePKs, referenceData, checkConstraints, columnRenames } = statement;
37733773

37743774
const columnNames = columns.map((it) => `"${it.name}"`).join(', ');
37753775
const newTableName = `__new_${tableName}`;
37763776

3777+
const renameMap = new Map<string, string>();
3778+
if (columnRenames) {
3779+
for (const { oldName, newName } of columnRenames) {
3780+
renameMap.set(newName, oldName);
3781+
}
3782+
}
3783+
3784+
const selectColumnNames = columns.map((it) => {
3785+
const oldName = renameMap.get(it.name);
3786+
return oldName ? `"${oldName}"` : `"${it.name}"`;
3787+
}).join(', ');
3788+
37773789
const sqlStatements: string[] = [];
37783790

37793791
sqlStatements.push(`PRAGMA foreign_keys=OFF;`);
@@ -3798,7 +3810,7 @@ class SQLiteRecreateTableConvertor extends Convertor {
37983810

37993811
// migrate data
38003812
sqlStatements.push(
3801-
`INSERT INTO \`${newTableName}\`(${columnNames}) SELECT ${columnNames} FROM \`${tableName}\`;`,
3813+
`INSERT INTO \`${newTableName}\`(${columnNames}) SELECT ${selectColumnNames} FROM \`${tableName}\`;`,
38023814
);
38033815

38043816
// drop table
@@ -3836,11 +3848,23 @@ class LibSQLRecreateTableConvertor extends Convertor {
38363848
}
38373849

38383850
convert(statement: JsonRecreateTableStatement): string[] {
3839-
const { tableName, columns, compositePKs, referenceData, checkConstraints } = statement;
3851+
const { tableName, columns, compositePKs, referenceData, checkConstraints, columnRenames } = statement;
38403852

38413853
const columnNames = columns.map((it) => `"${it.name}"`).join(', ');
38423854
const newTableName = `__new_${tableName}`;
38433855

3856+
const renameMap = new Map<string, string>();
3857+
if (columnRenames) {
3858+
for (const { oldName, newName } of columnRenames) {
3859+
renameMap.set(newName, oldName);
3860+
}
3861+
}
3862+
3863+
const selectColumnNames = columns.map((it) => {
3864+
const oldName = renameMap.get(it.name);
3865+
return oldName ? `"${oldName}"` : `"${it.name}"`;
3866+
}).join(', ');
3867+
38443868
const sqlStatements: string[] = [];
38453869

38463870
const mappedCheckConstraints: string[] = checkConstraints.map((it) =>
@@ -3864,7 +3888,7 @@ class LibSQLRecreateTableConvertor extends Convertor {
38643888

38653889
// migrate data
38663890
sqlStatements.push(
3867-
`INSERT INTO \`${newTableName}\`(${columnNames}) SELECT ${columnNames} FROM \`${tableName}\`;`,
3891+
`INSERT INTO \`${newTableName}\`(${columnNames}) SELECT ${selectColumnNames} FROM \`${tableName}\`;`,
38683892
);
38693893

38703894
// drop table

drizzle-kit/src/statementCombiner.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
JsonCreateIndexStatement,
33
JsonRecreateTableStatement,
4+
JsonRenameColumnStatement,
45
JsonStatement,
56
prepareCreateIndexesJson,
67
} from './jsonStatements';
@@ -80,7 +81,17 @@ export const libSQLCombineStatements = (
8081
) => {
8182
// const tablesContext: Record<string, string[]> = {};
8283
const newStatements: Record<string, JsonStatement[]> = {};
84+
const columnRenamesByTable: Record<string, { oldName: string; newName: string }[]> = {};
85+
8386
for (const statement of statements) {
87+
if (statement.type === 'alter_table_rename_column') {
88+
const s = statement as unknown as JsonRenameColumnStatement;
89+
if (!columnRenamesByTable[s.tableName]) {
90+
columnRenamesByTable[s.tableName] = [];
91+
}
92+
columnRenamesByTable[s.tableName].push({ oldName: s.oldColumnName, newName: s.newColumnName });
93+
}
94+
8495
if (
8596
statement.type === 'alter_table_alter_column_drop_autoincrement'
8697
|| statement.type === 'alter_table_alter_column_set_autoincrement'
@@ -293,6 +304,16 @@ export const libSQLCombineStatements = (
293304
}
294305
}
295306

307+
for (const [tableName, renames] of Object.entries(columnRenamesByTable)) {
308+
const stmts = newStatements[tableName];
309+
if (stmts) {
310+
const recreate = stmts.find((s) => s.type === 'recreate_table') as JsonRecreateTableStatement | undefined;
311+
if (recreate) {
312+
recreate.columnRenames = renames;
313+
}
314+
}
315+
}
316+
296317
const combinedStatements = Object.values(newStatements).flat();
297318
const renamedTables = combinedStatements.filter((it) => it.type === 'rename_table');
298319
const renamedColumns = combinedStatements.filter((it) => it.type === 'alter_table_rename_column');
@@ -309,7 +330,17 @@ export const sqliteCombineStatements = (
309330
) => {
310331
// const tablesContext: Record<string, string[]> = {};
311332
const newStatements: Record<string, JsonStatement[]> = {};
333+
const columnRenamesByTable: Record<string, { oldName: string; newName: string }[]> = {};
334+
312335
for (const statement of statements) {
336+
if (statement.type === 'alter_table_rename_column') {
337+
const s = statement as unknown as JsonRenameColumnStatement;
338+
if (!columnRenamesByTable[s.tableName]) {
339+
columnRenamesByTable[s.tableName] = [];
340+
}
341+
columnRenamesByTable[s.tableName].push({ oldName: s.oldColumnName, newName: s.newColumnName });
342+
}
343+
313344
if (
314345
statement.type === 'alter_table_alter_column_set_type'
315346
|| statement.type === 'alter_table_alter_column_set_default'
@@ -436,6 +467,16 @@ export const sqliteCombineStatements = (
436467
}
437468
}
438469

470+
for (const [tableName, renames] of Object.entries(columnRenamesByTable)) {
471+
const stmts = newStatements[tableName];
472+
if (stmts) {
473+
const recreate = stmts.find((s) => s.type === 'recreate_table') as JsonRecreateTableStatement | undefined;
474+
if (recreate) {
475+
recreate.columnRenames = renames;
476+
}
477+
}
478+
}
479+
439480
const combinedStatements = Object.values(newStatements).flat();
440481

441482
const renamedTables = combinedStatements.filter((it) => it.type === 'rename_table');

drizzle-kit/tests/statements-combiner/sqlite-statements-combiner.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ test(`renamed column and altered this column type`, async (t) => {
137137
referenceData: [],
138138
uniqueConstraints: [],
139139
checkConstraints: [],
140+
columnRenames: [{ oldName: 'lastName', newName: 'lastName123' }],
140141
},
141142
];
142143
expect(sqliteCombineStatements(statements, json2)).toStrictEqual(

0 commit comments

Comments
 (0)