Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions drizzle-kit/src/cli/commands/libSqlPushUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
SQLiteDropTableConvertor,
SqliteRenameTableConvertor,
} from '../../sqlgenerator';
import { collectCascadeDependents } from '../../utils/cascade';

export const getOldTableName = (
tableName: string,
Expand All @@ -29,11 +30,18 @@ export const _moveDataStatements = (
tableName: string,
json: SQLiteSchemaSquashed,
dataLoss: boolean = false,
cascadeDependents: string[] = [],
) => {
const statements: string[] = [];

const newTableName = `__new_${tableName}`;

for (const dep of cascadeDependents) {
statements.push(
`CREATE TEMP TABLE \`__bak_${dep}\` AS SELECT * FROM \`${dep}\`;`,
);
}

// create table statement from a new json2 with proper name
const tableColumns = Object.values(json.tables[tableName].columns);
const referenceData = Object.values(json.tables[tableName].foreignKeys);
Expand Down Expand Up @@ -107,6 +115,14 @@ export const _moveDataStatements = (
}),
);
}

for (const dep of cascadeDependents) {
statements.push(
`INSERT OR REPLACE INTO \`${dep}\` SELECT * FROM \`__bak_${dep}\`;`,
);
statements.push(`DROP TABLE \`__bak_${dep}\`;`);
}

return statements;
};

Expand Down Expand Up @@ -302,14 +318,17 @@ export const libSqlLogSuggestionsAndReturn = async (
tablesReferencingCurrent.push(...tablesRefs);
}

if (!tablesReferencingCurrent.length) {
statementsToExecute.push(..._moveDataStatements(tableName, json2, dataLoss));
const cascadeDependents = collectCascadeDependents(tableName, json2);

if (tablesReferencingCurrent.length === 0) {
statementsToExecute.push(
..._moveDataStatements(tableName, json2, dataLoss, cascadeDependents),
);
continue;
}

// recreate table
statementsToExecute.push(
..._moveDataStatements(tableName, json2, dataLoss),
..._moveDataStatements(tableName, json2, dataLoss, cascadeDependents),
);
} else if (
statement.type === 'alter_table_alter_column_set_generated'
Expand Down
37 changes: 29 additions & 8 deletions drizzle-kit/src/cli/commands/sqlitePushUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,25 @@ import {

import type { JsonStatement } from '../../jsonStatements';
import { findAddedAndRemoved, type SQLiteDB } from '../../utils';
import { collectCascadeDependents } from '../../utils/cascade';

export const _moveDataStatements = (
tableName: string,
json: SQLiteSchemaSquashed,
dataLoss: boolean = false,
cascadeDependents: string[] = [],
) => {
const statements: string[] = [];

const newTableName = `__new_${tableName}`;

// backup dependent tables to prevent ON DELETE CASCADE data loss when dropping parent
for (const dep of cascadeDependents) {
statements.push(
`CREATE TEMP TABLE \`__bak_${dep}\` AS SELECT * FROM \`${dep}\`;`,
);
}

// create table statement from a new json2 with proper name
const tableColumns = Object.values(json.tables[tableName].columns);
const referenceData = Object.values(json.tables[tableName].foreignKeys);
Expand Down Expand Up @@ -95,6 +104,14 @@ export const _moveDataStatements = (
);
}

// restore dependents data after parent recreation (works when PRAGMA foreign_keys cannot be disabled, e.g. D1)
for (const dep of cascadeDependents) {
statements.push(
`INSERT OR REPLACE INTO \`${dep}\` SELECT * FROM \`__bak_${dep}\`;`,
);
statements.push(`DROP TABLE \`__bak_${dep}\`;`);
}

return statements;
};

Expand Down Expand Up @@ -286,22 +303,26 @@ export const logSuggestionsAndReturn = async (
tablesReferencingCurrent.push(...tablesRefs);
}

const cascadeDependents = collectCascadeDependents(tableName, json2);

if (!tablesReferencingCurrent.length) {
statementsToExecute.push(..._moveDataStatements(tableName, json2, dataLoss));
statementsToExecute.push(
..._moveDataStatements(tableName, json2, dataLoss, cascadeDependents),
);
continue;
}

const [{ foreign_keys: pragmaState }] = await connection.query<{
foreign_keys: number;
}>(`PRAGMA foreign_keys;`);

if (pragmaState) {
statementsToExecute.push(`PRAGMA foreign_keys=OFF;`);
}
statementsToExecute.push(..._moveDataStatements(tableName, json2, dataLoss));
if (pragmaState) {
statementsToExecute.push(`PRAGMA foreign_keys=ON;`);
}
// In environments like Cloudflare D1, PRAGMA foreign_keys cannot be disabled, so we
// both toggle when possible and also use backups to prevent cascade data loss.
if (pragmaState) statementsToExecute.push(`PRAGMA foreign_keys=OFF;`);
statementsToExecute.push(
..._moveDataStatements(tableName, json2, dataLoss, cascadeDependents),
);
if (pragmaState) statementsToExecute.push(`PRAGMA foreign_keys=ON;`);
} else {
const fromJsonStatement = fromJson([statement], 'sqlite', 'push');
statementsToExecute.push(
Expand Down
1 change: 1 addition & 0 deletions drizzle-kit/src/jsonStatements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export interface JsonRecreateTableStatement {
compositePKs: string[][];
uniqueConstraints?: string[];
checkConstraints: string[];
cascadeDependents?: string[];
}

export interface JsonRecreateSingleStoreTableStatement {
Expand Down
22 changes: 20 additions & 2 deletions drizzle-kit/src/sqlgenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3769,7 +3769,7 @@ class SQLiteRecreateTableConvertor extends Convertor {
}

convert(statement: JsonRecreateTableStatement): string | string[] {
const { tableName, columns, compositePKs, referenceData, checkConstraints } = statement;
const { tableName, columns, compositePKs, referenceData, checkConstraints, cascadeDependents = [] } = statement;

const columnNames = columns.map((it) => `"${it.name}"`).join(', ');
const newTableName = `__new_${tableName}`;
Expand All @@ -3778,6 +3778,10 @@ class SQLiteRecreateTableConvertor extends Convertor {

sqlStatements.push(`PRAGMA foreign_keys=OFF;`);

for (const dep of cascadeDependents) {
sqlStatements.push(`CREATE TEMP TABLE \`__bak_${dep}\` AS SELECT * FROM \`${dep}\`;`);
}

// map all possible variants
const mappedCheckConstraints: string[] = checkConstraints.map((it) =>
it.replaceAll(`"${tableName}".`, `"${newTableName}".`).replaceAll(`\`${tableName}\`.`, `\`${newTableName}\`.`)
Expand Down Expand Up @@ -3821,6 +3825,11 @@ class SQLiteRecreateTableConvertor extends Convertor {
}),
);

for (const dep of cascadeDependents) {
sqlStatements.push(`INSERT OR REPLACE INTO \`${dep}\` SELECT * FROM \`__bak_${dep}\`;`);
sqlStatements.push(`DROP TABLE \`__bak_${dep}\`;`);
}

sqlStatements.push(`PRAGMA foreign_keys=ON;`);

return sqlStatements;
Expand All @@ -3836,13 +3845,17 @@ class LibSQLRecreateTableConvertor extends Convertor {
}

convert(statement: JsonRecreateTableStatement): string[] {
const { tableName, columns, compositePKs, referenceData, checkConstraints } = statement;
const { tableName, columns, compositePKs, referenceData, checkConstraints, cascadeDependents = [] } = statement;

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

const sqlStatements: string[] = [];

for (const dep of cascadeDependents) {
sqlStatements.push(`CREATE TEMP TABLE \`__bak_${dep}\` AS SELECT * FROM \`${dep}\`;`);
}

const mappedCheckConstraints: string[] = checkConstraints.map((it) =>
it.replaceAll(`"${tableName}".`, `"${newTableName}".`).replaceAll(`\`${tableName}\`.`, `\`${newTableName}\`.`)
.replaceAll(`${tableName}.`, `${newTableName}.`).replaceAll(`'${tableName}'.`, `\`${newTableName}\`.`)
Expand Down Expand Up @@ -3887,6 +3900,11 @@ class LibSQLRecreateTableConvertor extends Convertor {
}),
);

for (const dep of cascadeDependents) {
sqlStatements.push(`INSERT OR REPLACE INTO \`${dep}\` SELECT * FROM \`__bak_${dep}\`;`);
sqlStatements.push(`DROP TABLE \`__bak_${dep}\`;`);
}

sqlStatements.push(`PRAGMA foreign_keys=ON;`);

return sqlStatements;
Expand Down
41 changes: 25 additions & 16 deletions drizzle-kit/src/statementCombiner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import {
} from './jsonStatements';
import { SingleStoreSchemaSquashed } from './serializer/singlestoreSchema';
import { SQLiteSchemaSquashed, SQLiteSquasher } from './serializer/sqliteSchema';
import { collectCascadeDependents } from './utils/cascade';

export const prepareLibSQLRecreateTable = (
table: SQLiteSchemaSquashed['tables'][keyof SQLiteSchemaSquashed['tables']],
json2: SQLiteSchemaSquashed,
action?: 'push',
): (JsonRecreateTableStatement | JsonCreateIndexStatement)[] => {
const { name, columns, uniqueConstraints, indexes, checkConstraints } = table;
Expand All @@ -22,6 +24,8 @@ export const prepareLibSQLRecreateTable = (
action === 'push' ? SQLiteSquasher.unsquashPushFK(it) : SQLiteSquasher.unsquashFK(it)
);

const cascadeDependents = collectCascadeDependents(name, json2, action);

const statements: (JsonRecreateTableStatement | JsonCreateIndexStatement)[] = [
{
type: 'recreate_table',
Expand All @@ -31,6 +35,7 @@ export const prepareLibSQLRecreateTable = (
referenceData: fks,
uniqueConstraints: Object.values(uniqueConstraints),
checkConstraints: Object.values(checkConstraints),
cascadeDependents,
},
];

Expand All @@ -42,6 +47,7 @@ export const prepareLibSQLRecreateTable = (

export const prepareSQLiteRecreateTable = (
table: SQLiteSchemaSquashed['tables'][keyof SQLiteSchemaSquashed['tables']],
json2: SQLiteSchemaSquashed,
action?: 'push',
): JsonStatement[] => {
const { name, columns, uniqueConstraints, indexes, checkConstraints } = table;
Expand All @@ -55,6 +61,8 @@ export const prepareSQLiteRecreateTable = (
action === 'push' ? SQLiteSquasher.unsquashPushFK(it) : SQLiteSquasher.unsquashFK(it)
);

const cascadeDependents = collectCascadeDependents(name, json2, action);

const statements: JsonStatement[] = [
{
type: 'recreate_table',
Expand All @@ -64,6 +72,7 @@ export const prepareSQLiteRecreateTable = (
referenceData: fks,
uniqueConstraints: Object.values(uniqueConstraints),
checkConstraints: Object.values(checkConstraints),
cascadeDependents,
},
];

Expand Down Expand Up @@ -97,14 +106,14 @@ export const libSQLCombineStatements = (
const statementsForTable = newStatements[tableName];

if (!statementsForTable) {
newStatements[tableName] = prepareLibSQLRecreateTable(json2.tables[tableName], action);
newStatements[tableName] = prepareLibSQLRecreateTable(json2.tables[tableName], json2, action);

continue;
}

if (!statementsForTable.some(({ type }) => type === 'recreate_table')) {
const wasRename = statementsForTable.some(({ type }) => type === 'rename_table');
const preparedStatements = prepareLibSQLRecreateTable(json2.tables[tableName], action);
const preparedStatements = prepareLibSQLRecreateTable(json2.tables[tableName], json2, action);

if (wasRename) {
newStatements[tableName].push(...preparedStatements);
Expand Down Expand Up @@ -142,7 +151,7 @@ export const libSQLCombineStatements = (
if (
!statementsForTable && (columnIsPartOfForeignKey || columnPk)
) {
newStatements[tableName] = prepareLibSQLRecreateTable(json2.tables[tableName], action);
newStatements[tableName] = prepareLibSQLRecreateTable(json2.tables[tableName], json2, action);
continue;
}

Expand All @@ -151,7 +160,7 @@ export const libSQLCombineStatements = (
) {
if (!statementsForTable.some(({ type }) => type === 'recreate_table')) {
const wasRename = statementsForTable.some(({ type }) => type === 'rename_table');
const preparedStatements = prepareLibSQLRecreateTable(json2.tables[tableName], action);
const preparedStatements = prepareLibSQLRecreateTable(json2.tables[tableName], json2, action);

if (wasRename) {
newStatements[tableName].push(...preparedStatements);
Expand Down Expand Up @@ -186,7 +195,7 @@ export const libSQLCombineStatements = (

if (!statementsForTable) {
newStatements[tableName] = statement.isMulticolumn
? prepareLibSQLRecreateTable(json2.tables[tableName], action)
? prepareLibSQLRecreateTable(json2.tables[tableName], json2, action)
: [statement];

continue;
Expand All @@ -205,7 +214,7 @@ export const libSQLCombineStatements = (
if (statement.isMulticolumn) {
if (!statementsForTable.some(({ type }) => type === 'recreate_table')) {
const wasRename = statementsForTable.some(({ type }) => type === 'rename_table');
const preparedStatements = prepareLibSQLRecreateTable(json2.tables[tableName], action);
const preparedStatements = prepareLibSQLRecreateTable(json2.tables[tableName], json2, action);

if (wasRename) {
newStatements[tableName].push(...preparedStatements);
Expand All @@ -232,13 +241,13 @@ export const libSQLCombineStatements = (
const statementsForTable = newStatements[tableName];

if (!statementsForTable) {
newStatements[tableName] = prepareLibSQLRecreateTable(json2.tables[tableName], action);
newStatements[tableName] = prepareLibSQLRecreateTable(json2.tables[tableName], json2, action);
continue;
}

if (!statementsForTable.some(({ type }) => type === 'recreate_table')) {
const wasRename = statementsForTable.some(({ type }) => type === 'rename_table');
const preparedStatements = prepareLibSQLRecreateTable(json2.tables[tableName], action);
const preparedStatements = prepareLibSQLRecreateTable(json2.tables[tableName], json2, action);

if (wasRename) {
newStatements[tableName].push(...preparedStatements);
Expand All @@ -258,13 +267,13 @@ export const libSQLCombineStatements = (
const statementsForTable = newStatements[tableName];

if (!statementsForTable) {
newStatements[tableName] = prepareLibSQLRecreateTable(json2.tables[tableName], action);
newStatements[tableName] = prepareLibSQLRecreateTable(json2.tables[tableName], json2, action);
continue;
}

if (!statementsForTable.some(({ type }) => type === 'recreate_table')) {
const wasRename = statementsForTable.some(({ type }) => type === 'rename_table');
const preparedStatements = prepareLibSQLRecreateTable(json2.tables[tableName], action);
const preparedStatements = prepareLibSQLRecreateTable(json2.tables[tableName], json2, action);

if (wasRename) {
newStatements[tableName].push(...preparedStatements);
Expand Down Expand Up @@ -335,13 +344,13 @@ export const sqliteCombineStatements = (
const statementsForTable = newStatements[tableName];

if (!statementsForTable) {
newStatements[tableName] = prepareSQLiteRecreateTable(json2.tables[tableName], action);
newStatements[tableName] = prepareSQLiteRecreateTable(json2.tables[tableName], json2, action);
continue;
}

if (!statementsForTable.some(({ type }) => type === 'recreate_table')) {
const wasRename = statementsForTable.some(({ type }) => type === 'rename_table');
const preparedStatements = prepareSQLiteRecreateTable(json2.tables[tableName], action);
const preparedStatements = prepareSQLiteRecreateTable(json2.tables[tableName], json2, action);

if (wasRename) {
newStatements[tableName].push(...preparedStatements);
Expand All @@ -361,13 +370,13 @@ export const sqliteCombineStatements = (
const statementsForTable = newStatements[tableName];

if (!statementsForTable) {
newStatements[tableName] = prepareSQLiteRecreateTable(json2.tables[tableName], action);
newStatements[tableName] = prepareSQLiteRecreateTable(json2.tables[tableName], json2, action);
continue;
}

if (!statementsForTable.some(({ type }) => type === 'recreate_table')) {
const wasRename = statementsForTable.some(({ type }) => type === 'rename_table');
const preparedStatements = prepareSQLiteRecreateTable(json2.tables[tableName], action);
const preparedStatements = prepareSQLiteRecreateTable(json2.tables[tableName], json2, action);

if (wasRename) {
newStatements[tableName].push(...preparedStatements);
Expand All @@ -390,7 +399,7 @@ export const sqliteCombineStatements = (
const statementsForTable = newStatements[tableName];

if (!statementsForTable) {
newStatements[tableName] = prepareSQLiteRecreateTable(json2.tables[tableName], action);
newStatements[tableName] = prepareSQLiteRecreateTable(json2.tables[tableName], json2, action);
continue;
}

Expand All @@ -406,7 +415,7 @@ export const sqliteCombineStatements = (

if (!statementsForTable.some(({ type }) => type === 'recreate_table')) {
const wasRename = statementsForTable.some(({ type }) => type === 'rename_table');
const preparedStatements = prepareSQLiteRecreateTable(json2.tables[tableName], action);
const preparedStatements = prepareSQLiteRecreateTable(json2.tables[tableName], json2, action);

if (wasRename) {
newStatements[tableName].push(...preparedStatements);
Expand Down
Loading