Skip to content

Commit

Permalink
Merge pull request #101 from HavokInspiration/tables-order-fk
Browse files Browse the repository at this point in the history
Tables need to be ordered to respect foreign keys constraints dependencies
  • Loading branch information
lorenzo committed Jul 19, 2015
2 parents a53d924 + 55fe97f commit 09b282b
Show file tree
Hide file tree
Showing 7 changed files with 227 additions and 46 deletions.
69 changes: 67 additions & 2 deletions src/Shell/Task/MigrationSnapshotTask.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ public function templateData()
}

$collection = $this->getCollection($this->connection);
$tables = $collection->listTables();
$tables = $this->orderTables($collection->listTables());

if ($this->params['require-table'] === true) {
$tableNamesInModel = $this->getTableNames($this->plugin);
Expand Down Expand Up @@ -160,11 +160,76 @@ public function templateData()
];
}

/**
* Order tables based on foreign key dependencies so tables that are foreign keys
* to other are created first
*
* @param array $tables Tables to order.
* @return array Tables ordered.
*/
public function orderTables($tables)
{
if (empty($tables)) {
return $tables;
}

$orderedTables = [];
foreach ($tables as $table) {
$tableSchema = $this->getCollection($this->connection)->describe($table);
$tableConstraints = $tableSchema->constraints();
if (isset($tableConstraints[0]) && $tableConstraints[0] === 'primary') {
unset($tableConstraints[0]);
}

if (!empty($tableConstraints)) {
foreach ($tableConstraints as $tableConstraint) {
$constraint = $tableSchema->constraint($tableConstraint);

if ($constraint['type'] !== 'foreign') {
continue;
}

$refTable = $constraint['references'][0];

$refIndex = array_search($refTable, $orderedTables);
$tableIndex = array_search($table, $orderedTables);

if ($refIndex === false && $tableIndex === false) {
array_unshift($orderedTables, $refTable, $table);
continue;
}

if ($refIndex === false) {
array_splice($orderedTables, $tableIndex, 0, $refTable);
continue;
}

if ($tableIndex === false) {
array_splice($orderedTables, $refIndex + 1, 0, $table);
continue;
}

if ($refIndex > $tableIndex) {
unset($orderedTables[$refIndex]);
array_splice($orderedTables, $tableIndex, 0, $refTable);
continue;
}
}
}

if (!in_array($table, $orderedTables)) {
$orderedTables[] = $table;
}
}

return $orderedTables;
}

/**
* Get a collection from a database
*
* @param string $connection Database connection name.
* @return obj schemaCollection
* @return \Cake\Database\Schema\Collection
*/
public function getCollection($connection)
{
Expand Down
3 changes: 2 additions & 1 deletion src/Template/Bake/config/snapshot.ctp
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ class <%= $name %> extends AbstractMigration

public function down()
{
<%- foreach ($tables as $table): %>
<%- $tables = array_reverse($tables);
foreach ($tables as $table): %>
$this->dropTable('<%= $table%>');
<%- endforeach; %>
}
Expand Down
47 changes: 47 additions & 0 deletions tests/Fixture/ArticlesFixture.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Migrations\Test\Fixture;

use Cake\TestSuite\Fixture\TestFixture;

/**
* Class ArticlesFixture
*
*/
class ArticlesFixture extends TestFixture
{

/**
* fields property
*
* @var array
*/
public $fields = [
'id' => ['type' => 'integer'],
'title' => ['type' => 'string', 'null' => true, 'length' => 255],
'category_id' => ['type' => 'integer', 'length' => 11],
'created' => ['type' => 'timestamp', 'null' => true, 'default' => null],
'modified' => ['type' => 'timestamp', 'null' => true, 'default' => null],
'_constraints' => [
'primary' => ['type' => 'primary', 'columns' => ['id']],
'category_article_idx' => [
'type' => 'foreign',
'columns' => ['category_id'],
'references' => ['categories', 'id'],
'update' => 'cascade',
'delete' => 'cascade'
]
]
];
}
2 changes: 1 addition & 1 deletion tests/Fixture/ProductsFixture.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
use Cake\TestSuite\Fixture\TestFixture;

/**
* Class CategoriesFixture
* Class ProductsFixture
*
*/
class ProductsFixture extends TestFixture
Expand Down
4 changes: 3 additions & 1 deletion tests/TestCase/Shell/Task/MigrationSnapshotTaskTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class MigrationSnapshotTaskTest extends TestCase
'plugin.migrations.composite_pk',
'plugin.migrations.products',
'plugin.migrations.categories',
'plugin.migrations.articles',
'plugin.migrations.orders'
];

Expand Down Expand Up @@ -69,7 +70,8 @@ public function testNotEmptySnapshot()
'SpecialPk',
'CompositePk',
'Categories',
'Products'
'Products',
'Articles'
);

$this->Task->params['require-table'] = false;
Expand Down
81 changes: 57 additions & 24 deletions tests/comparisons/Migration/testCompositeConstraintsSnapshot.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,48 @@ public function up()
['unique' => true]
)
->create();
$table = $this->table('composite_pks', ['id' => false, 'primary_key' => ['id', 'name']]);
$table = $this->table('products');
$table
->addColumn('id', 'uuid', [
'default' => '',
->addColumn('title', 'string', [
'default' => null,
'limit' => 255,
'null' => true,
])
->addColumn('slug', 'string', [
'default' => null,
'limit' => 255,
'null' => true,
])
->addColumn('category_id', 'integer', [
'default' => null,
'limit' => 11,
'null' => true,
])
->addColumn('created', 'timestamp', [
'default' => null,
'limit' => null,
'null' => false,
'null' => true,
])
->addColumn('name', 'string', [
'default' => '',
'limit' => 50,
'null' => false,
->addColumn('modified', 'timestamp', [
'default' => null,
'limit' => null,
'null' => true,
])
->addIndex(
[
'slug',
],
['unique' => true]
)
->addForeignKey(
'category_id',
'categories',
'id',
[
'update' => 'CASCADE',
'delete' => 'CASCADE'
]
)
->create();
$table = $this->table('orders');
$table
Expand Down Expand Up @@ -80,18 +110,13 @@ public function up()
]
)
->create();
$table = $this->table('products');
$table = $this->table('articles');
$table
->addColumn('title', 'string', [
'default' => null,
'limit' => 255,
'null' => true,
])
->addColumn('slug', 'string', [
'default' => null,
'limit' => 255,
'null' => true,
])
->addColumn('category_id', 'integer', [
'default' => null,
'limit' => 11,
Expand All @@ -107,12 +132,6 @@ public function up()
'limit' => null,
'null' => true,
])
->addIndex(
[
'slug',
],
['unique' => true]
)
->addForeignKey(
'category_id',
'categories',
Expand All @@ -123,6 +142,19 @@ public function up()
]
)
->create();
$table = $this->table('composite_pks', ['id' => false, 'primary_key' => ['id', 'name']]);
$table
->addColumn('id', 'uuid', [
'default' => '',
'limit' => null,
'null' => false,
])
->addColumn('name', 'string', [
'default' => '',
'limit' => 50,
'null' => false,
])
->create();
$table = $this->table('special_pks', ['id' => false, 'primary_key' => ['id']]);
$table
->addColumn('id', 'uuid', [
Expand Down Expand Up @@ -197,12 +229,13 @@ public function up()

public function down()
{
$this->dropTable('categories');
$this->dropTable('users');
$this->dropTable('special_tags');
$this->dropTable('special_pks');
$this->dropTable('composite_pks');
$this->dropTable('articles');
$this->dropTable('orders');
$this->dropTable('products');
$this->dropTable('special_pks');
$this->dropTable('special_tags');
$this->dropTable('users');
$this->dropTable('categories');
}
}
67 changes: 50 additions & 17 deletions tests/comparisons/Migration/testNotEmptySnapshot.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,6 @@ public function up()
['unique' => true]
)
->create();
$table = $this->table('composite_pks', ['id' => false, 'primary_key' => ['id', 'name']]);
$table
->addColumn('id', 'uuid', [
'default' => '',
'limit' => null,
'null' => false,
])
->addColumn('name', 'string', [
'default' => '',
'limit' => 50,
'null' => false,
])
->create();
$table = $this->table('products');
$table
->addColumn('title', 'string', [
Expand Down Expand Up @@ -95,6 +82,51 @@ public function up()
]
)
->create();
$table = $this->table('articles');
$table
->addColumn('title', 'string', [
'default' => null,
'limit' => 255,
'null' => true,
])
->addColumn('category_id', 'integer', [
'default' => null,
'limit' => 11,
'null' => true,
])
->addColumn('created', 'timestamp', [
'default' => null,
'limit' => null,
'null' => true,
])
->addColumn('modified', 'timestamp', [
'default' => null,
'limit' => null,
'null' => true,
])
->addForeignKey(
'category_id',
'categories',
'id',
[
'update' => 'CASCADE',
'delete' => 'CASCADE'
]
)
->create();
$table = $this->table('composite_pks', ['id' => false, 'primary_key' => ['id', 'name']]);
$table
->addColumn('id', 'uuid', [
'default' => '',
'limit' => null,
'null' => false,
])
->addColumn('name', 'string', [
'default' => '',
'limit' => 50,
'null' => false,
])
->create();
$table = $this->table('special_pks', ['id' => false, 'primary_key' => ['id']]);
$table
->addColumn('id', 'uuid', [
Expand Down Expand Up @@ -169,11 +201,12 @@ public function up()

public function down()
{
$this->dropTable('categories');
$this->dropTable('users');
$this->dropTable('special_tags');
$this->dropTable('special_pks');
$this->dropTable('composite_pks');
$this->dropTable('articles');
$this->dropTable('products');
$this->dropTable('special_pks');
$this->dropTable('special_tags');
$this->dropTable('users');
$this->dropTable('categories');
}
}

0 comments on commit 09b282b

Please sign in to comment.