Skip to content

Commit 28ccb61

Browse files
committed
Fix foreign key handling for PostgreSQL and SQL Server
- PostgreSQL: Use CASCADE in DROP TABLE statement - SQL Server: Drop foreign key constraints first before dropping tables - MySQL/SQLite: Continue using session-level FK check toggle
1 parent 9ad5c0e commit 28ccb61

1 file changed

Lines changed: 62 additions & 11 deletions

File tree

src/Command/ResetCommand.php

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -222,24 +222,80 @@ protected function getTablesToDrop(Connection $connection): array
222222
protected function dropTables(Connection $connection, array $tables, ConsoleIo $io): void
223223
{
224224
$driver = $connection->getDriver();
225+
$driverClass = get_class($driver);
225226

226-
// Disable foreign key checks temporarily
227-
$this->setForeignKeyChecks($connection, false);
227+
// For PostgreSQL and SQL Server, we need to drop foreign keys first
228+
// or use CASCADE in the drop statement
229+
if (str_contains($driverClass, 'Postgres')) {
230+
foreach ($tables as $table) {
231+
$quotedTable = $driver->quoteIdentifier($table);
232+
$io->verbose("Dropping table: {$table}");
233+
$connection->execute("DROP TABLE IF EXISTS {$quotedTable} CASCADE");
234+
}
235+
} elseif (str_contains($driverClass, 'Sqlserver')) {
236+
// Drop all foreign key constraints first
237+
$this->dropForeignKeyConstraints($connection, $tables, $io);
228238

229-
try {
239+
// Then drop tables
230240
foreach ($tables as $table) {
231241
$quotedTable = $driver->quoteIdentifier($table);
232242
$io->verbose("Dropping table: {$table}");
233243
$connection->execute("DROP TABLE IF EXISTS {$quotedTable}");
234244
}
235-
} finally {
236-
// Re-enable foreign key checks
237-
$this->setForeignKeyChecks($connection, true);
245+
} else {
246+
// MySQL and SQLite support disabling foreign key checks
247+
$this->setForeignKeyChecks($connection, false);
248+
249+
try {
250+
foreach ($tables as $table) {
251+
$quotedTable = $driver->quoteIdentifier($table);
252+
$io->verbose("Dropping table: {$table}");
253+
$connection->execute("DROP TABLE IF EXISTS {$quotedTable}");
254+
}
255+
} finally {
256+
$this->setForeignKeyChecks($connection, true);
257+
}
238258
}
239259

240260
$io->success('Dropped ' . count($tables) . ' table(s).');
241261
}
242262

263+
/**
264+
* Drop all foreign key constraints from the given tables.
265+
*
266+
* @param \Cake\Database\Connection $connection Database connection
267+
* @param array<string> $tables Tables to process
268+
* @param \Cake\Console\ConsoleIo $io Console IO
269+
* @return void
270+
*/
271+
protected function dropForeignKeyConstraints(Connection $connection, array $tables, ConsoleIo $io): void
272+
{
273+
$driver = $connection->getDriver();
274+
$driverClass = get_class($driver);
275+
276+
if (!str_contains($driverClass, 'Sqlserver')) {
277+
return;
278+
}
279+
280+
// Query to find all foreign key constraints on the specified tables
281+
$tableList = implode("','", array_map(fn($t) => addslashes($t), $tables));
282+
283+
$sql = "SELECT
284+
fk.name AS constraint_name,
285+
OBJECT_NAME(fk.parent_object_id) AS table_name
286+
FROM sys.foreign_keys fk
287+
WHERE OBJECT_NAME(fk.parent_object_id) IN ('{$tableList}')";
288+
289+
$result = $connection->execute($sql)->fetchAll('assoc');
290+
291+
foreach ($result as $row) {
292+
$constraintName = $driver->quoteIdentifier($row['constraint_name']);
293+
$tableName = $driver->quoteIdentifier($row['table_name']);
294+
$io->verbose("Dropping foreign key: {$row['constraint_name']} on {$row['table_name']}");
295+
$connection->execute("ALTER TABLE {$tableName} DROP CONSTRAINT {$constraintName}");
296+
}
297+
}
298+
243299
/**
244300
* Enable or disable foreign key checks.
245301
*
@@ -254,13 +310,8 @@ protected function setForeignKeyChecks(Connection $connection, bool $enable): vo
254310

255311
if (str_contains($driverClass, 'Mysql')) {
256312
$connection->execute('SET FOREIGN_KEY_CHECKS = ' . ($enable ? '1' : '0'));
257-
} elseif (str_contains($driverClass, 'Postgres')) {
258-
// PostgreSQL handles this per-session via constraints
259-
// We'll use CASCADE in the DROP statement instead
260313
} elseif (str_contains($driverClass, 'Sqlite')) {
261314
$connection->execute('PRAGMA foreign_keys = ' . ($enable ? 'ON' : 'OFF'));
262-
} elseif (str_contains($driverClass, 'Sqlserver')) {
263-
// SQL Server doesn't have a global toggle, handled differently
264315
}
265316
}
266317

0 commit comments

Comments
 (0)