diff --git a/phalcon/db/adapter/pdo/mysql.zep b/phalcon/db/adapter/pdo/mysql.zep index 0043a6bc041..0f966653bac 100644 --- a/phalcon/db/adapter/pdo/mysql.zep +++ b/phalcon/db/adapter/pdo/mysql.zep @@ -25,6 +25,8 @@ use Phalcon\Db\Index; use Phalcon\Db\Reference; use Phalcon\Db\IndexInterface; use Phalcon\Db\Adapter\Pdo as PdoAdapter; +use Phalcon\Application\Exception; +use Phalcon\Db\ReferenceInterface; /** * Phalcon\Db\Adapter\Pdo\Mysql @@ -382,4 +384,19 @@ class Mysql extends PdoAdapter return referenceObjects; } + + /** + * Adds a foreign key to a table + */ + public function addForeignKey(string! tableName, string! schemaName, reference) -> boolean + { + var foreignKeyCheck; + + let foreignKeyCheck = this->{"prepare"}(this->_dialect->getForeignKeyChecks()); + if !foreignKeyCheck->execute() { + throw new Exception("DATABASE PARAMETER 'FOREIGN_KEY_CHECKS' HAS TO BE 1"); + } + + return this->{"execute"}(this->_dialect->addForeignKey(tableName, schemaName, reference)); + } } diff --git a/phalcon/db/dialect/mysql.zep b/phalcon/db/dialect/mysql.zep index b6e562e83a4..7a39d469d7b 100644 --- a/phalcon/db/dialect/mysql.zep +++ b/phalcon/db/dialect/mysql.zep @@ -348,7 +348,11 @@ class Mysql extends Dialect { var sql, onDelete, onUpdate; - let sql = "ALTER TABLE " . this->prepareTable(tableName, schemaName) . " ADD FOREIGN KEY `" . reference->getName() . "`(" . this->getColumnList(reference->getColumns()) . ") REFERENCES " . this->prepareTable(reference->getReferencedTable(), reference->getReferencedSchema()) . "(" . this->getColumnList(reference->getReferencedColumns()) . ")"; + let sql = "ALTER TABLE " . this->prepareTable(tableName, schemaName) . " ADD"; + if reference->getName() { + let sql .= " CONSTRAINT `" . $reference->getName() . "`"; + } + let sql .= " FOREIGN KEY (" . this->getColumnList(reference->getColumns()) . ") REFERENCES " . this->prepareTable(reference->getReferencedTable(), reference->getReferencedSchema()) . "(" . this->getColumnList(reference->getReferencedColumns()) . ")"; let onDelete = reference->getOnDelete(); if !empty onDelete { @@ -719,4 +723,16 @@ class Mysql extends Dialect return ""; } + + /** + * Generates SQL to check DB parameter FOREIGN_KEY_CHECKS. + */ + public function getForeignKeyChecks() -> string + { + var sql; + + let sql = "SELECT @@foreign_key_checks"; + + return sql; + } } diff --git a/tests/_data/schemas/mysql/phalcon_test.sql b/tests/_data/schemas/mysql/phalcon_test.sql index 5454a1c70cb..abf3ce752aa 100644 --- a/tests/_data/schemas/mysql/phalcon_test.sql +++ b/tests/_data/schemas/mysql/phalcon_test.sql @@ -602,6 +602,27 @@ ALTER TABLE `stock` ALTER TABLE `stock` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4; +-- +-- Table for testing foreign key +-- +DROP TABLE IF EXISTS `foreign_key_parent`; +CREATE TABLE `foreign_key_parent` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` VARCHAR(32) NOT NULL, + `refer_int` INT(10) NOT NULL, + PRIMARY KEY (`id`), + KEY (`refer_int`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +DROP TABLE IF EXISTS `foreign_key_child`; +CREATE TABLE `foreign_key_child` ( + `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `name` VARCHAR(32) NOT NULL, + `child_int` INT(10) NOT NULL, + PRIMARY KEY (`id`), + KEY (`child_int`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; diff --git a/tests/_support/Helper/Dialect/MysqlTrait.php b/tests/_support/Helper/Dialect/MysqlTrait.php index aee73f6f6e5..1f10afe0ad1 100644 --- a/tests/_support/Helper/Dialect/MysqlTrait.php +++ b/tests/_support/Helper/Dialect/MysqlTrait.php @@ -443,6 +443,44 @@ protected function getAddForeignKey() ]; } + protected function addForeignKey($foreignKeyName = '', $onUpdate = '', $onDelete = '') + { + $sql = 'ALTER TABLE `foreign_key_child` ADD'; + if ($foreignKeyName) { + $sql .= ' CONSTRAINT `' . $foreignKeyName . '`'; + } + $sql .= ' FOREIGN KEY (`child_int`) REFERENCES `foreign_key_parent`(`refer_int`)'; + + if ($onDelete) { + $sql .= ' ON DELETE ' . $onDelete; + } + if ($onUpdate) { + $sql .= ' ON UPDATE ' . $onUpdate; + } + + return $sql; + } + + protected function getForeignKey($foreignKeyName) + { + $sql = "SELECT + COUNT(`CONSTRAINT_NAME`) + FROM information_schema.REFERENTIAL_CONSTRAINTS + WHERE TABLE_NAME = 'foreign_key_child' AND + `UPDATE_RULE` = 'CASCADE' AND + `DELETE_RULE` = 'RESTRICT' AND + `CONSTRAINT_NAME` = '$foreignKeyName'"; + + return $sql; + } + + protected function dropForeignKey($foreignKeyName) + { + $sql = "ALTER TABLE `foreign_key_child` DROP FOREIGN KEY $foreignKeyName"; + + return $sql; + } + protected function getDropForeignKey() { return [ diff --git a/tests/unit/Db/Adapter/Pdo/MysqlTest.php b/tests/unit/Db/Adapter/Pdo/MysqlTest.php index 96ef2ba2e4f..fbd498e5e45 100644 --- a/tests/unit/Db/Adapter/Pdo/MysqlTest.php +++ b/tests/unit/Db/Adapter/Pdo/MysqlTest.php @@ -6,6 +6,7 @@ use Phalcon\Db\Reference; use Phalcon\Test\Module\UnitTest; use Phalcon\Db\Adapter\Pdo\Mysql; +use Helper\Dialect\MysqlTrait; /** * \Phalcon\Test\Unit\Db\Adapter\Pdo\MysqlTest @@ -26,6 +27,8 @@ */ class MysqlTest extends UnitTest { + use MysqlTrait; + /** * @var Mysql */ @@ -65,6 +68,8 @@ function () { 'artists', 'childs', 'customers', + 'foreign_key_child', + 'foreign_key_parent', 'issue12071_body', 'issue12071_head', 'issue_11036', @@ -156,4 +161,76 @@ function ($identifier, $expected) { ] ); } + + /** + * Tests Mysql::addForeignKey + * + * @test + * @issue 556 + * @author Sergii Svyrydenko + * @since 2017-07-03 + */ + public function shouldAddForeignKey() + { + $this->specify( + "Foreign key hasn't created", + function ($sql, $expected) { + expect($this->connection->execute($sql))->equals($expected); + }, + [ + 'examples' => [ + [$this->addForeignKey('test_name_key', 'CASCADE', 'RESTRICT'), true], + [$this->addForeignKey('', 'CASCADE', 'RESTRICT'), true] + ] + ] + ); + } + + /** + * Tests Mysql::getForeignKey + * + * @test + * @issue 556 + * @author Sergii Svyrydenko + * @since 2017-07-03 + */ + public function shouldCheckAddedForeignKey() + { + $this->specify( + "Foreign key isn't created", + function ($sql, $expected) { + expect($this->connection->execute($sql, ['MYSQL_ATTR_USE_BUFFERED_QUERY']))->equals($expected); + }, + [ + 'examples' => [ + [$this->getForeignKey('test_name_key'), true], + [$this->getForeignKey('foreign_key_child_ibfk_1'), true] + ] + ] + ); + } + + /** + * Tests Mysql::dropAddForeignKey + * + * @test + * @issue 556 + * @author Sergii Svyrydenko + * @since 2017-07-03 + */ + public function shouldDropForeignKey() + { + $this->specify( + "Foreign key can't be created", + function ($sql, $expected) { + expect($this->connection->execute($sql))->equals($expected); + }, + [ + 'examples' => [ + [$this->dropForeignKey('test_name_key'), true], + [$this->dropForeignKey('foreign_key_child_ibfk_1'), true] + ] + ] + ); + } }