Skip to content

Commit

Permalink
Merge pull request #1863 from MasterOdin/sqlite-fk
Browse files Browse the repository at this point in the history
Fixes problems in SQLite Adapter in foreign key creation
  • Loading branch information
dereuromark authored Aug 15, 2020
2 parents c9e1a2f + ed890fb commit 05902f4
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 21 deletions.
80 changes: 59 additions & 21 deletions src/Phinx/Db/Adapter/SQLiteAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ protected function getChangePrimaryKeyInstructions(Table $table, $newColumns)
}

/**
* @inheritDoc
* {@inheritDoc}
*
* SQLiteAdapter does not implement this functionality, and so will always throw an exception if used.
*
Expand Down Expand Up @@ -739,6 +739,26 @@ protected function getDeclaringSql($tableName)
return $sql;
}

/**
* Returns the original CREATE statement for the give index
*
* @param string $tableName The table name to get the create statement for
* @return string
*/
protected function getDeclaringIndexSql($tableName, $indexName)
{
$rows = $this->fetchAll("SELECT * FROM sqlite_master WHERE `type` = 'index'");

$sql = '';
foreach ($rows as $table) {
if ($table['tbl_name'] === $tableName && $table['name'] === $indexName) {
$sql = $table['sql'] . '; ';
}
}

return $sql;
}

/**
* Copies all the data from a tmp table to another table
*
Expand Down Expand Up @@ -1284,9 +1304,28 @@ protected function getAddForeignKeyInstructions(Table $table, ForeignKey $foreig
$instructions = $this->beginAlterByCopyTable($table->getName());

$tableName = $table->getName();
$instructions->addPostStep(function ($state) use ($foreignKey) {
$instructions->addPostStep(function ($state) use ($foreignKey, $tableName) {
$this->execute('pragma foreign_keys = ON');
$sql = substr($state['createSQL'], 0, -1) . ',' . $this->getForeignKeySqlDefinition($foreignKey) . ')';
$sql = substr($state['createSQL'], 0, -1) . ',' . $this->getForeignKeySqlDefinition($foreignKey) . '); ';

//Delete indexes from original table and recreate them in temporary table
$schema = $this->getSchemaName($tableName, true)['schema'];
$tmpTableName = $state['tmpTableName'];
$indexes = $this->getIndexes($tableName);
foreach (array_keys($indexes) as $indexName) {
$sql .= sprintf(
'DROP INDEX %s%s; ',
$schema,
$this->quoteColumnName($indexName)
);
$createIndexSQL = $this->getDeclaringIndexSQL($tableName, $indexName);
$sql .= preg_replace(
"/\b${tableName}\b/",
$tmpTableName,
$createIndexSQL
);
}

$this->execute($sql);

return $state;
Expand All @@ -1304,7 +1343,7 @@ protected function getAddForeignKeyInstructions(Table $table, ForeignKey $foreig
}

/**
* @inheritDoc
* {@inheritDoc}
*
* SQLiteAdapter does not implement this functionality, and so will always throw an exception if used.
*
Expand Down Expand Up @@ -1564,23 +1603,22 @@ protected function getForeignKeySqlDefinition(ForeignKey $foreignKey)
$def = '';
if ($foreignKey->getConstraint()) {
$def .= ' CONSTRAINT ' . $this->quoteColumnName($foreignKey->getConstraint());
} else {
$columnNames = [];
foreach ($foreignKey->getColumns() as $column) {
$columnNames[] = $this->quoteColumnName($column);
}
$def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')';
$refColumnNames = [];
foreach ($foreignKey->getReferencedColumns() as $column) {
$refColumnNames[] = $this->quoteColumnName($column);
}
$def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')';
if ($foreignKey->getOnDelete()) {
$def .= ' ON DELETE ' . $foreignKey->getOnDelete();
}
if ($foreignKey->getOnUpdate()) {
$def .= ' ON UPDATE ' . $foreignKey->getOnUpdate();
}
}
$columnNames = [];
foreach ($foreignKey->getColumns() as $column) {
$columnNames[] = $this->quoteColumnName($column);
}
$def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')';
$refColumnNames = [];
foreach ($foreignKey->getReferencedColumns() as $column) {
$refColumnNames[] = $this->quoteColumnName($column);
}
$def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')';
if ($foreignKey->getOnDelete()) {
$def .= ' ON DELETE ' . $foreignKey->getOnDelete();
}
if ($foreignKey->getOnUpdate()) {
$def .= ' ON UPDATE ' . $foreignKey->getOnUpdate();
}

return $def;
Expand Down
33 changes: 33 additions & 0 deletions tests/Phinx/Db/Adapter/SQLiteAdapterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,39 @@ public function testCreateTableWithForeignKey()
$this->assertTrue($this->adapter->hasForeignKey($table->getName(), ['ref_table_id']));
}

public function testCreateTableWithIndexesAndForeignKey()
{
$refTable = new \Phinx\Db\Table('tbl_master', [], $this->adapter);
$refTable->create();

$table = new \Phinx\Db\Table('tbl_child', [], $this->adapter);
$table
->addColumn('column1', 'integer')
->addColumn('column2', 'integer')
->addColumn('master_id', 'integer')
->addIndex(['column2'])
->addIndex(['column1', 'column2'], ['unique' => true, 'name' => 'uq_tbl_child_column1_column2_ndx'])
->addForeignKey(
'master_id',
'tbl_master',
'id',
['delete' => 'NO_ACTION', 'update' => 'NO_ACTION', 'constraint' => 'fk_master_id']
)
->create();

$this->assertTrue($this->adapter->hasIndex('tbl_child', 'column2'));
$this->assertTrue($this->adapter->hasIndex('tbl_child', ['column1', 'column2']));
$this->assertTrue($this->adapter->hasForeignKey('tbl_child', ['master_id']));

$row = $this->adapter->fetchRow(
"SELECT * FROM sqlite_master WHERE `type` = 'table' AND `tbl_name` = 'tbl_child'"
);
$this->assertRegExp(
'/CONSTRAINT `fk_master_id` FOREIGN KEY \(`master_id`\) REFERENCES `tbl_master` \(`id`\) ON DELETE NO ACTION ON UPDATE NO ACTION/',
$row['sql']
);
}

public function testAddPrimaryKey()
{
$table = new \Phinx\Db\Table('table1', ['id' => false], $this->adapter);
Expand Down

0 comments on commit 05902f4

Please sign in to comment.