Browse Source

Add support for FK constraints management for fixtures to the SQLite db server

Yves P 10 years ago
parent
commit
8af8282783

+ 94 - 0
src/Database/Schema/SqliteSchema.php

@@ -355,6 +355,100 @@ class SqliteSchema extends BaseSchema
     /**
      * {@inheritDoc}
      */
+    public function addConstraintSql(Table $table)
+    {
+        $tmpTableName = $this->_driver->quoteIdentifier('tmp_' . $table->name());
+        $tableName = $this->_driver->quoteIdentifier($table->name());
+
+        $columns = $indexes = $constraints = [];
+        foreach ($table->columns() as $column) {
+            $columns[] = $this->columnSql($table, $column);
+        }
+
+        foreach ($table->constraints() as $constraint) {
+            $constraints[] = $this->constraintSql($table, $constraint);
+        }
+
+        foreach ($table->indexes() as $index) {
+            $indexes[] = $this->indexSql($table, $index);
+        }
+        $createSql = $this->createTableSql($table, $columns, $constraints, $indexes);
+
+        $columnsList = implode(', ', $table->columns());
+        $copySql = sprintf(
+            'INSERT INTO %s(%s) SELECT %s FROM %s',
+            $tableName,
+            $columnsList,
+            $columnsList,
+            $tmpTableName
+        );
+
+        $dropSql = sprintf('DROP TABLE IF EXISTS %s', $tmpTableName);
+
+        $sql = [
+            $dropSql,
+            sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName),
+            $createSql[0],
+            $copySql,
+            $dropSql
+        ];
+
+        return $sql;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function dropConstraintSql(Table $table)
+    {
+        $tmpTableName = $this->_driver->quoteIdentifier('tmp_' . $table->name());
+        $tableName = $this->_driver->quoteIdentifier($table->name());
+
+        $columns = [];
+        foreach ($table->columns() as $column) {
+            $columns[$column] = $this->columnSql($table, $column);
+        }
+
+        $indexes = [];
+        foreach ($table->indexes() as $index) {
+            $indexes[$index] = $this->indexSql($table, $index);
+        }
+
+        $constraints = [];
+        foreach ($table->constraints() as $constraint) {
+            $constraintDefinition = $table->constraint($constraint);
+            if ($constraintDefinition['type'] == Table::CONSTRAINT_FOREIGN) {
+                $table->dropConstraint($constraint);
+            } else {
+                $constraints[$constraint] = $this->constraintSql($table, $constraint);
+            }
+        }
+        $createSql = $this->createTableSql($table, $columns, $constraints, $indexes);
+
+        $columnsList = implode(', ', $table->columns());
+        $copySql = sprintf(
+            'INSERT INTO %s(%s) SELECT %s FROM %s',
+            $tableName,
+            $columnsList,
+            $columnsList,
+            $tmpTableName
+        );
+
+        $dropSql = sprintf('DROP TABLE IF EXISTS %s', $tmpTableName);
+        $sql = [
+            $dropSql,
+            sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName),
+            $createSql[0],
+            $copySql,
+            $dropSql
+        ];
+
+        return $sql;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
     public function indexSql(Table $table, $name)
     {
         $data = $table->index($name);

+ 6 - 0
src/Database/Schema/Table.php

@@ -565,6 +565,12 @@ class Table
         return $this;
     }
 
+    /**
+     * Remove a constraint.
+     *
+     * @param string $name Name of the constraint to remove
+     * @return void
+     */
     public function dropConstraint($name)
     {
         if (isset($this->_constraints[$name])) {

+ 6 - 1
src/TestSuite/Fixture/FixtureManager.php

@@ -246,7 +246,6 @@ class FixtureManager
 
         try {
             $createTables = function ($db, $fixtures) use ($test) {
-                $db->enableForeignKeys();
                 $tables = $db->schemaCollection()->listTables();
                 $configName = $db->configName();
                 if (!isset($this->_insertionMap[$configName])) {
@@ -377,11 +376,17 @@ class FixtureManager
                 $db = ConnectionManager::get($fixture->connection());
             }
 
+            $fixture->dropConstraints($db);
+
             if (!$this->isFixtureSetup($db->configName(), $fixture)) {
                 $sources = $db->schemaCollection()->listTables();
                 $this->_setupTable($fixture, $db, $sources, $dropTables);
             }
+
+            $fixture->createConstraints($db);
+
             if (!$dropTables) {
+                $fixture->dropConstraints($db);
                 $fixture->truncate($db);
             }
             $fixture->insert($db);

+ 2 - 1
src/TestSuite/Fixture/TestFixture.php

@@ -14,7 +14,6 @@
 namespace Cake\TestSuite\Fixture;
 
 use Cake\Core\Exception\Exception as CakeException;
-use Cake\Database\Connection;
 use Cake\Database\Schema\Table;
 use Cake\Datasource\ConnectionInterface;
 use Cake\Datasource\ConnectionManager;
@@ -315,6 +314,7 @@ class TestFixture implements FixtureInterface
             return true;
         }
 
+        $db->disableForeignKeys();
         try {
             foreach ($sql as $stmt) {
                 $db->execute($stmt)->closeCursor();
@@ -340,6 +340,7 @@ class TestFixture implements FixtureInterface
             return true;
         }
 
+        $db->disableForeignKeys();
         try {
             foreach ($sql as $stmt) {
                 $db->execute($stmt)->closeCursor();

+ 149 - 0
tests/TestCase/Database/Schema/SqliteSchemaTest.php

@@ -545,6 +545,155 @@ SQL;
     }
 
     /**
+     * Test the addConstraintSql method.
+     *
+     * @return void
+     */
+    public function testAddConstraintSql()
+    {
+        $driver = $this->_getMockedDriver();
+        $connection = $this->getMock('Cake\Database\Connection', [], [], '', false);
+        $connection->expects($this->any())->method('driver')
+            ->will($this->returnValue($driver));
+
+        $table = (new Table('posts'))
+            ->addColumn('author_id', [
+                'type' => 'integer',
+                'null' => false
+            ])
+            ->addColumn('category_id', [
+                'type' => 'integer',
+                'null' => false
+            ])
+            ->addColumn('category_name', [
+                'type' => 'integer',
+                'null' => false
+            ])
+            ->addConstraint('author_fk', [
+                'type' => 'foreign',
+                'columns' => ['author_id'],
+                'references' => ['authors', 'id'],
+                'update' => 'cascade',
+                'delete' => 'cascade'
+            ]);
+
+        $expected = [
+            'DROP TABLE IF EXISTS "tmp_posts"',
+            'ALTER TABLE "posts" RENAME TO "tmp_posts"',
+            'CREATE TABLE "posts" (
+"author_id" INTEGER NOT NULL,
+"category_id" INTEGER NOT NULL,
+"category_name" INTEGER NOT NULL,
+CONSTRAINT "author_fk" FOREIGN KEY ("author_id") REFERENCES "authors" ("id") ON UPDATE CASCADE ON DELETE CASCADE
+)',
+            'INSERT INTO "posts"(author_id, category_id, category_name) SELECT author_id, category_id, category_name FROM "tmp_posts"',
+            'DROP TABLE IF EXISTS "tmp_posts"'
+        ];
+        $result = $table->addConstraintSql($connection);
+        $this->assertCount(5, $result);
+        $this->assertTextEquals($expected, $result);
+
+        $table
+            ->addConstraint('category_fk', [
+                'type' => 'foreign',
+                'columns' => ['category_id', 'category_name'],
+                'references' => ['categories', ['id', 'name']],
+                'update' => 'cascade',
+                'delete' => 'cascade'
+            ]);
+
+        $expected = [
+            'DROP TABLE IF EXISTS "tmp_posts"',
+            'ALTER TABLE "posts" RENAME TO "tmp_posts"',
+            'CREATE TABLE "posts" (
+"author_id" INTEGER NOT NULL,
+"category_id" INTEGER NOT NULL,
+"category_name" INTEGER NOT NULL,
+CONSTRAINT "author_fk" FOREIGN KEY ("author_id") REFERENCES "authors" ("id") ON UPDATE CASCADE ON DELETE CASCADE,
+CONSTRAINT "category_fk" FOREIGN KEY ("category_id", "category_name") REFERENCES "categories" ("id", "name") ON UPDATE CASCADE ON DELETE CASCADE
+)',
+            'INSERT INTO "posts"(author_id, category_id, category_name) SELECT author_id, category_id, category_name FROM "tmp_posts"',
+            'DROP TABLE IF EXISTS "tmp_posts"'
+        ];
+        $result = $table->addConstraintSql($connection);
+        $this->assertCount(5, $result);
+        $this->assertTextEquals($expected, $result);
+    }
+
+    /**
+     * Test the dropConstraintSql method.
+     *
+     * @return void
+     */
+    public function testDropConstraintSql()
+    {
+        $driver = $this->_getMockedDriver();
+        $connection = $this->getMock('Cake\Database\Connection', [], [], '', false);
+        $connection->expects($this->any())->method('driver')
+            ->will($this->returnValue($driver));
+
+        $table = (new Table('posts'))
+            ->addColumn('author_id', [
+                'type' => 'integer',
+                'null' => false
+            ])
+            ->addColumn('category_id', [
+                'type' => 'integer',
+                'null' => false
+            ])
+            ->addColumn('category_name', [
+                'type' => 'integer',
+                'null' => false
+            ])
+            ->addConstraint('author_fk', [
+                'type' => 'foreign',
+                'columns' => ['author_id'],
+                'references' => ['authors', 'id'],
+                'update' => 'cascade',
+                'delete' => 'cascade'
+            ]);
+
+        $expected = [
+            'DROP TABLE IF EXISTS "tmp_posts"',
+            'ALTER TABLE "posts" RENAME TO "tmp_posts"',
+            'CREATE TABLE "posts" (
+"author_id" INTEGER NOT NULL,
+"category_id" INTEGER NOT NULL,
+"category_name" INTEGER NOT NULL
+)',
+            'INSERT INTO "posts"(author_id, category_id, category_name) SELECT author_id, category_id, category_name FROM "tmp_posts"',
+            'DROP TABLE IF EXISTS "tmp_posts"'
+        ];
+        $result = $table->dropConstraintSql($connection);
+        $this->assertCount(5, $result);
+        $this->assertTextEquals($expected, $result);
+
+        $table
+            ->addConstraint('category_fk', [
+                'type' => 'foreign',
+                'columns' => ['category_id', 'category_name'],
+                'references' => ['categories', ['id', 'name']],
+                'update' => 'cascade',
+                'delete' => 'cascade'
+            ]);
+
+        $expected = [
+            'DROP TABLE IF EXISTS "tmp_posts"',
+            'ALTER TABLE "posts" RENAME TO "tmp_posts"',
+            'CREATE TABLE "posts" (
+"author_id" INTEGER NOT NULL,
+"category_id" INTEGER NOT NULL,
+"category_name" INTEGER NOT NULL
+)',
+            'INSERT INTO "posts"(author_id, category_id, category_name) SELECT author_id, category_id, category_name FROM "tmp_posts"',
+            'DROP TABLE IF EXISTS "tmp_posts"'
+        ];
+        $result = $table->dropConstraintSql($connection);
+        $this->assertCount(5, $result);
+        $this->assertTextEquals($expected, $result);
+    }
+
+    /**
      * Test generating column definitions
      *
      * @dataProvider columnSqlProvider

+ 1 - 1
tests/TestCase/Database/Schema/SqlserverSchemaTest.php

@@ -761,7 +761,7 @@ SQL;
             ]);
 
         $expected = <<<SQL
-ALTER TABLE [posts] DROP FOREIGN KEY [category_fk];
+ALTER TABLE [posts] DROP CONSTRAINT [category_fk];
 SQL;
         $result = $table->dropConstraintSql($connection);
         $this->assertCount(2, $result);

+ 1 - 6
tests/TestCase/TestSuite/FixtureManagerTest.php

@@ -15,7 +15,7 @@
 namespace Cake\Test\TestSuite;
 
 use Cake\Core\Plugin;
-use Cake\Database\ConnectionManager;
+use Cake\Datasource\ConnectionManager;
 use Cake\ORM\TableRegistry;
 use Cake\TestSuite\Fixture\FixtureManager;
 use Cake\TestSuite\TestCase;
@@ -67,9 +67,6 @@ class FixtureManagerTest extends TestCase
 
         $table = TableRegistry::get('ArticlesTags');
         $schema = $table->schema();
-
-        $this->assertEquals(['primary', 'tag_id_fk'], $schema->constraints());
-
         $expectedConstraint = [
             'type' => 'foreign',
             'columns' => [
@@ -89,8 +86,6 @@ class FixtureManagerTest extends TestCase
         $this->manager->load($test);
         $table = TableRegistry::get('ArticlesTags');
         $schema = $table->schema();
-
-        $this->assertEquals(['primary', 'tag_id_fk'], $schema->constraints());
         $expectedConstraint = [
             'type' => 'foreign',
             'columns' => [