Skip to content

Commit 2f83549

Browse files
committed
SQLite3 now supports dropping foreign keys. Fixes #1982
1 parent 0cdf086 commit 2f83549

7 files changed

Lines changed: 127 additions & 32 deletions

File tree

system/Database/Forge.php

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -416,16 +416,16 @@ public function addForeignKey(string $fieldName = '', string $tableName = '', st
416416
/**
417417
* Foreign Key Drop
418418
*
419-
* @param string $table Table name
420-
* @param string $foreign_name Foreign name
419+
* @param string $table Table name
420+
* @param string $foreignName Foreign name
421421
*
422422
* @return boolean|\CodeIgniter\Database\BaseResult|\CodeIgniter\Database\Query|false|mixed
423423
* @throws \CodeIgniter\Database\Exceptions\DatabaseException
424424
*/
425-
public function dropForeignKey(string $table, string $foreign_name)
425+
public function dropForeignKey(string $table, string $foreignName)
426426
{
427427
$sql = sprintf($this->dropConstraintStr, $this->db->escapeIdentifiers($this->db->DBPrefix . $table),
428-
$this->db->escapeIdentifiers($this->db->DBPrefix . $foreign_name));
428+
$this->db->escapeIdentifiers($this->db->DBPrefix . $foreignName));
429429

430430
if ($sql === false)
431431
{
@@ -484,7 +484,10 @@ public function createTable(string $table, bool $if_not_exists = false, array $a
484484

485485
if (($result = $this->db->query($sql)) !== false)
486486
{
487-
empty($this->db->dataCache['table_names']) || ($this->db->dataCache['table_names'][] = $table);
487+
if (! isset($this->db->dataCache['table_names'][$table]))
488+
{
489+
$this->db->dataCache['table_names'][] = $table;
490+
}
488491

489492
// Most databases don't support creating indexes from within the CREATE TABLE statement
490493
if (! empty($this->keys))
@@ -582,16 +585,16 @@ protected function _createTableAttributes(array $attributes): string
582585
/**
583586
* Drop Table
584587
*
585-
* @param string $table_name Table name
586-
* @param boolean $if_exists Whether to add an IF EXISTS condition
587-
* @param boolean $cascade Whether to add an CASCADE condition
588+
* @param string $tableName Table name
589+
* @param boolean $ifExists Whether to add an IF EXISTS condition
590+
* @param boolean $cascade Whether to add an CASCADE condition
588591
*
589592
* @return mixed
590593
* @throws \CodeIgniter\Database\Exceptions\DatabaseException
591594
*/
592-
public function dropTable(string $table_name, bool $if_exists = false, bool $cascade = false)
595+
public function dropTable(string $tableName, bool $ifExists = false, bool $cascade = false)
593596
{
594-
if ($table_name === '')
597+
if ($tableName === '')
595598
{
596599
if ($this->db->DBDebug)
597600
{
@@ -602,22 +605,26 @@ public function dropTable(string $table_name, bool $if_exists = false, bool $cas
602605
}
603606

604607
// If the prefix is already starting the table name, remove it...
605-
if (! empty($this->db->DBPrefix) && strpos($table_name, $this->db->DBPrefix) === 0)
608+
if (! empty($this->db->DBPrefix) && strpos($tableName, $this->db->DBPrefix) === 0)
606609
{
607-
$table_name = substr($table_name, strlen($this->db->DBPrefix));
610+
$tableName = substr($tableName, strlen($this->db->DBPrefix));
608611
}
609612

610-
if (($query = $this->_dropTable($this->db->DBPrefix . $table_name, $if_exists, $cascade)) === true)
613+
if (($query = $this->_dropTable($this->db->DBPrefix . $tableName, $ifExists, $cascade)) === true)
611614
{
612615
return true;
613616
}
614617

618+
$this->db->query('PRAGMA foreign_keys = OFF');
619+
615620
$query = $this->db->query($query);
616621

622+
$this->db->query('PRAGMA foreign_keys = ON');
623+
617624
// Update table list cache
618625
if ($query && ! empty($this->db->dataCache['table_names']))
619626
{
620-
$key = array_search(strtolower($this->db->DBPrefix . $table_name),
627+
$key = array_search(strtolower($this->db->DBPrefix . $tableName),
621628
array_map('strtolower', $this->db->dataCache['table_names']), true);
622629
if ($key !== false)
623630
{

system/Database/SQLite3/Connection.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,7 @@ public function isWriteType($sql): bool
504504
*
505505
* @return boolean
506506
*/
507-
protected function supportsForeignKeys(): bool
507+
public function supportsForeignKeys(): bool
508508
{
509509
$result = $this->simpleQuery('PRAGMA foreign_keys');
510510

system/Database/SQLite3/Forge.php

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -303,15 +303,27 @@ protected function _attributeAutoIncrement(array &$attributes, array &$field)
303303
/**
304304
* Foreign Key Drop
305305
*
306-
* @param string $table Table name
307-
* @param string $foreign_name Foreign name
306+
* @param string $table Table name
307+
* @param string $foreignName Foreign name
308308
*
309309
* @return boolean
310310
* @throws \CodeIgniter\Database\Exceptions\DatabaseException
311311
*/
312-
public function dropForeignKey(string $table, string $foreign_name): bool
312+
public function dropForeignKey(string $table, string $foreignName): bool
313313
{
314-
throw new DatabaseException(lang('Database.dropForeignKeyUnsupported'));
314+
// If this version of SQLite doesn't support it, we're done here
315+
if ($this->db->supportsForeignKeys() !== true)
316+
{
317+
return true;
318+
}
319+
320+
// Otherwise we have to copy the table and recreate
321+
// without the foreign key being involved now
322+
$sqlTable = new Table($this->db, $this);
323+
324+
return $sqlTable->fromTable($this->db->DBPrefix . $table)
325+
->dropForeignKey($foreignName)
326+
->run();
315327
}
316328

317329
//--------------------------------------------------------------------

system/Database/SQLite3/Table.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,40 @@ public function modifyColumn(array $field)
217217
return $this;
218218
}
219219

220+
/**
221+
* Drops a foreign key from this table so that
222+
* it won't be recreated in the future.
223+
*
224+
* @param string $column
225+
*
226+
* @return \CodeIgniter\Database\SQLite3\Table
227+
*/
228+
public function dropForeignKey(string $column)
229+
{
230+
if (empty($this->foreignKeys))
231+
{
232+
return $this;
233+
}
234+
235+
for ($i = 0; $i < count($this->foreignKeys); $i++)
236+
{
237+
if ($this->foreignKeys[$i]->table_name !== $this->tableName)
238+
{
239+
continue;
240+
}
241+
242+
// The column name should be the first thing in the constraint name
243+
if (strpos($this->foreignKeys[$i]->constraint_name, $column) !== 0)
244+
{
245+
continue;
246+
}
247+
248+
unset($this->foreignKeys[$i]);
249+
}
250+
251+
return $this;
252+
}
253+
220254
/**
221255
* Creates the new table based on our current fields.
222256
*

tests/system/Database/Live/ForgeTest.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -388,10 +388,6 @@ public function testDropForeignKey()
388388
{
389389
$attributes = ['ENGINE' => 'InnoDB'];
390390
}
391-
if ($this->db->DBDriver === 'SQLite3')
392-
{
393-
$this->expectException(DatabaseException::class);
394-
}
395391

396392
$this->forge->addField([
397393
'id' => [

tests/system/Database/Live/SQLite/AlterTableTest.php

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public function tearDown()
4545
parent::tearDown();
4646

4747
$this->forge->dropTable('foo', true);
48+
$this->forge->dropTable('foo_fk', true);
4849
}
4950

5051
/**
@@ -66,7 +67,7 @@ public function testFromTableFillsDetails()
6667

6768
$fields = $this->getPrivateProperty($this->table, 'fields');
6869

69-
$this->assertCount(3, $fields);
70+
$this->assertCount(4, $fields);
7071
$this->assertTrue(array_key_exists('id', $fields));
7172
$this->assertNull($fields['id']['default']);
7273
$this->assertTrue($fields['id']['nullable']);
@@ -156,14 +157,38 @@ public function testModifyColumnSuccess()
156157
$this->assertTrue($this->db->fieldExists('serial', 'janky'));
157158
}
158159

160+
public function testDropForeignKeySuccess()
161+
{
162+
$this->createTable('aliens');
163+
164+
$keys = $this->db->getForeignKeyData('aliens');
165+
$this->assertEquals('key_id to aliens_fk.id', $keys[0]->constraint_name);
166+
167+
$result = $this->table
168+
->fromTable('aliens')
169+
->dropForeignKey('key_id')
170+
->run();
171+
172+
$this->assertTrue($result);
173+
174+
$keys = $this->db->getForeignKeyData('aliens');
175+
$this->assertTrue(empty($keys));
176+
}
177+
159178
public function testProcessCopiesOldData()
160179
{
161180
$this->createTable('foo');
162181

182+
$this->db->table('foo_fk')->insert([
183+
'id' => 1,
184+
'name' => 'bar',
185+
]);
186+
163187
$this->db->table('foo')->insert([
164-
'id' => 1,
165-
'name' => 'George Clinton',
166-
'email' => 'funkalicious@example.com',
188+
'id' => 1,
189+
'name' => 'George Clinton',
190+
'email' => 'funkalicious@example.com',
191+
'key_id' => 1,
167192
]);
168193

169194
$this->seeInDatabase('foo', ['name' => 'George Clinton']);
@@ -179,27 +204,50 @@ public function testProcessCopiesOldData()
179204

180205
protected function createTable(string $tableName = 'foo')
181206
{
207+
// Create support table for foreign keys
182208
$this->forge->addField([
183-
'id' => [
209+
'id' => [
184210
'type' => 'integer',
185211
'constraint' => 11,
186212
'unsigned' => true,
187213
'auto_increment' => true,
188214
],
189-
'name' => [
215+
'name' => [
190216
'type' => 'varchar',
191217
'constraint' => 255,
192218
'null' => false,
193219
],
194-
'email' => [
220+
]);
221+
$this->forge->createTable($tableName . '_fk');
222+
223+
// Create main table
224+
$this->forge->addField([
225+
'id' => [
226+
'type' => 'integer',
227+
'constraint' => 11,
228+
'unsigned' => true,
229+
'auto_increment' => true,
230+
],
231+
'name' => [
232+
'type' => 'varchar',
233+
'constraint' => 255,
234+
'null' => false,
235+
],
236+
'email' => [
195237
'type' => 'varchar',
196238
'constraint' => 255,
197239
'null' => true,
198240
],
241+
'key_id' => [
242+
'type' => 'integer',
243+
'constraint' => 11,
244+
'unsigned' => true,
245+
],
199246
]);
200247
$this->forge->addPrimaryKey('id');
201248
$this->forge->addKey('name');
202249
$this->forge->addUniqueKey('email');
250+
$this->forge->addForeignKey('key_id', $tableName . '_fk', 'id');
203251
$this->forge->createTable($tableName);
204252
}
205253
}

user_guide_src/source/dbmgmt/forge.rst

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,8 +258,6 @@ Execute a DROP FOREIGN KEY.
258258
// Produces: ALTER TABLE 'tablename' DROP FOREIGN KEY 'users_foreign'
259259
$forge->dropForeignKey('tablename','users_foreign');
260260

261-
.. note:: SQLite database driver does not support dropping of foreign keys.
262-
263261
Renaming a table
264262
================
265263

0 commit comments

Comments
 (0)