Skip to content

Commit

Permalink
Merge pull request #19881 from bobonov/master
Browse files Browse the repository at this point in the history
Migration & MySql Query addCheck() and dropCheck()
  • Loading branch information
mtangoo authored May 22, 2024
2 parents f2ea9be + 2ead4f3 commit 9975597
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 34 deletions.
1 change: 1 addition & 0 deletions framework/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Yii Framework 2 Change Log
- New #20137: Added `yii\caching\CallbackDependency` to allow using a callback to determine if a cache dependency is still valid (laxity7)
- Enh #20134: Raise minimum `PHP` version to `7.3` (@terabytesoftw)
- Bug #20141: Update `ezyang/htmlpurifier` dependency to version `4.17` (@terabytesoftw)
- Bug #19817: Add MySQL Query `addCheck()` and `dropCheck()` (@bobonov)

2.0.49.2 October 12, 2023
-------------------------
Expand Down
29 changes: 29 additions & 0 deletions framework/db/Migration.php
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,35 @@ public function dropIndex($name, $table)
$this->endCommand($time);
}

/**
* Creates a SQL command for adding a check constraint to an existing table.
* @param string $name the name of the check constraint.
* The name will be properly quoted by the method.
* @param string $table the table that the check constraint will be added to.
* The name will be properly quoted by the method.
* @param string $expression the SQL of the `CHECK` constraint.
*/
public function addCheck($name, $table, $expression)
{
$time = $this->beginCommand("add check $name in table $table");
$this->db->createCommand()->addCheck($name, $table, $expression)->execute();
$this->endCommand($time);
}

/**
* Creates a SQL command for dropping a check constraint.
* @param string $name the name of the check constraint to be dropped.
* The name will be properly quoted by the method.
* @param string $table the table whose check constraint is to be dropped.
* The name will be properly quoted by the method.
*/
public function dropCheck($name, $table)
{
$time = $this->beginCommand("drop check $name in table $table");
$this->db->createCommand()->dropCheck($name, $table)->execute();
$this->endCommand($time);
}

/**
* Builds and execute a SQL statement for adding comment to column.
*
Expand Down
18 changes: 0 additions & 18 deletions framework/db/mysql/QueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -151,24 +151,6 @@ public function dropUnique($name, $table)
return $this->dropIndex($name, $table);
}

/**
* {@inheritdoc}
* @throws NotSupportedException this is not supported by MySQL.
*/
public function addCheck($name, $table, $expression)
{
throw new NotSupportedException(__METHOD__ . ' is not supported by MySQL.');
}

/**
* {@inheritdoc}
* @throws NotSupportedException this is not supported by MySQL.
*/
public function dropCheck($name, $table)
{
throw new NotSupportedException(__METHOD__ . ' is not supported by MySQL.');
}

/**
* Creates a SQL statement for resetting the sequence value of a table's primary key.
* The sequence will be reset such that the primary key of the next new row inserted
Expand Down
46 changes: 44 additions & 2 deletions framework/db/mysql/Schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Yii;
use yii\base\InvalidConfigException;
use yii\base\NotSupportedException;
use yii\db\CheckConstraint;
use yii\db\Constraint;
use yii\db\ConstraintFinderInterface;
use yii\db\ConstraintFinderTrait;
Expand Down Expand Up @@ -201,11 +202,52 @@ protected function loadTableUniques($tableName)

/**
* {@inheritdoc}
* @throws NotSupportedException if this method is called.
*/
protected function loadTableChecks($tableName)
{
throw new NotSupportedException('MySQL does not support check constraints.');
// check version MySQL >= 8.0.16
if (version_compare($this->db->getSlavePdo()->getAttribute(\PDO::ATTR_SERVER_VERSION), '8.0.16', '<')) {
throw new NotSupportedException('MySQL < 8.0.16 does not support check constraints.');
}

$checks = [];

$sql = <<<SQL
SELECT cc.CONSTRAINT_NAME as constraint_name, cc.CHECK_CLAUSE as check_clause
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
JOIN INFORMATION_SCHEMA.CHECK_CONSTRAINTS cc
ON tc.CONSTRAINT_NAME = cc.CONSTRAINT_NAME
WHERE tc.TABLE_NAME = :tableName AND tc.CONSTRAINT_TYPE = 'CHECK';
SQL;

$resolvedName = $this->resolveTableName($tableName);
$tableRows = $this->db->createCommand($sql, [':tableName' => $resolvedName->name])->queryAll();

if ($tableRows === []) {
return $checks;
}

$tableRows = $this->normalizePdoRowKeyCase($tableRows, true);

foreach ($tableRows as $tableRow) {
$matches = [];
$columnName = null;

if (preg_match('/\(`?([a-zA-Z0-9_]+)`?\s*[><=]/', $tableRow['check_clause'], $matches)) {
$columnName = $matches[1];
}

$check = new CheckConstraint(
[
'name' => $tableRow['constraint_name'],
'columnNames' => [$columnName],
'expression' => $tableRow['check_clause'],
]
);
$checks[] = $check;
}

return $checks;
}

/**
Expand Down
9 changes: 7 additions & 2 deletions tests/framework/db/CommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,7 @@ public function invalidSelectColumns()
* Test INSERT INTO ... SELECT SQL statement with wrong query object.
*
* @dataProvider invalidSelectColumns
*
*
* @param mixed $invalidSelectColumns
*/
public function testInsertSelectFailed($invalidSelectColumns)
Expand Down Expand Up @@ -1206,9 +1206,13 @@ public function testAddDropUnique()
public function testAddDropCheck()
{
$db = $this->getConnection(false);

if (version_compare($db->getServerVersion(), '8.0.16', '<')) {
$this->markTestSkipped('MySQL < 8.0.16 does not support CHECK constraints.');
}

$tableName = 'test_ck';
$name = 'test_ck_constraint';
/** @var \yii\db\pgsql\Schema $schema */
$schema = $db->getSchema();

if ($schema->getTableSchema($tableName) !== null) {
Expand All @@ -1226,6 +1230,7 @@ public function testAddDropCheck()
);

$db->createCommand()->dropCheck($name, $tableName)->execute();

$this->assertEmpty($schema->getTableChecks($tableName, true));
}

Expand Down
24 changes: 24 additions & 0 deletions tests/framework/db/SchemaTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,14 @@ public function testTableSchemaConstraints($tableName, $type, $expected)
$this->expectException('yii\base\NotSupportedException');
}

if (
$this->driverName === 'mysql' &&
version_compare($this->getConnection(false)->getServerVersion(), '8.0.16', '<') &&
$type === 'checks'
) {
$this->expectException('yii\base\NotSupportedException');
}

$constraints = $this->getConnection(false)->getSchema()->{'getTable' . ucfirst($type)}($tableName);
$this->assertMetadataEquals($expected, $constraints);
}
Expand All @@ -789,6 +797,14 @@ public function testTableSchemaConstraintsWithPdoUppercase($tableName, $type, $e
$this->expectException('yii\base\NotSupportedException');
}

if (
$this->driverName === 'mysql' &&
version_compare($this->getConnection(false)->getServerVersion(), '8.0.16', '<') &&
$type === 'checks'
) {
$this->expectException('yii\base\NotSupportedException');
}

$connection = $this->getConnection(false);
$connection->getSlavePdo(true)->setAttribute(PDO::ATTR_CASE, PDO::CASE_UPPER);
$constraints = $connection->getSchema()->{'getTable' . ucfirst($type)}($tableName, true);
Expand All @@ -807,6 +823,14 @@ public function testTableSchemaConstraintsWithPdoLowercase($tableName, $type, $e
$this->expectException('yii\base\NotSupportedException');
}

if (
$this->driverName === 'mysql' &&
version_compare($this->getConnection(false)->getServerVersion(), '8.0.16', '<') &&
$type === 'checks'
) {
$this->expectException('yii\base\NotSupportedException');
}

$connection = $this->getConnection(false);
$connection->getSlavePdo(true)->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
$constraints = $connection->getSchema()->{'getTable' . ucfirst($type)}($tableName, true);
Expand Down
48 changes: 46 additions & 2 deletions tests/framework/db/mysql/CommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,52 @@ class CommandTest extends \yiiunit\framework\db\CommandTest

protected $upsertTestCharCast = 'CONVERT([[address]], CHAR)';

public function testAddDropCheck()
public function testAddDropCheckSeveral()
{
$this->markTestSkipped('MySQL does not support adding/dropping check constraints.');
$db = $this->getConnection(false);

if (version_compare($db->getServerVersion(), '8.0.16', '<')) {
$this->markTestSkipped('MySQL < 8.0.16 does not support CHECK constraints.');
}

$tableName = 'test_ck_several';
$schema = $db->getSchema();

if ($schema->getTableSchema($tableName) !== null) {
$db->createCommand()->dropTable($tableName)->execute();
}
$db->createCommand()->createTable($tableName, [
'int1' => 'integer',
'int2' => 'integer',
'int3' => 'integer',
])->execute();

$this->assertEmpty($schema->getTableChecks($tableName, true));

$constraints = [
['name' => 'check_int1_positive', 'expression' => '[[int1]] > 0', 'expected' => '(`int1` > 0)'],
['name' => 'check_int2_nonzero', 'expression' => '[[int2]] <> 0', 'expected' => '(`int2` <> 0)'],
['name' => 'check_int3_less_than_100', 'expression' => '[[int3]] < 100', 'expected' => '(`int3` < 100)'],
];

foreach ($constraints as $constraint) {
$db->createCommand()->addCheck($constraint['name'], $tableName, $constraint['expression'])->execute();
}

$tableChecks = $schema->getTableChecks($tableName, true);
$this->assertCount(3, $tableChecks);

foreach ($constraints as $index => $constraint) {
$this->assertSame(
$constraints[$index]['expected'],
$tableChecks[$index]->expression
);
}

foreach ($constraints as $constraint) {
$db->createCommand()->dropCheck($constraint['name'], $tableName)->execute();
}

$this->assertEmpty($schema->getTableChecks($tableName, true));
}
}
7 changes: 1 addition & 6 deletions tests/framework/db/mysql/QueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -196,11 +196,6 @@ public function uniquesProvider()
return $result;
}

public function checksProvider()
{
$this->markTestSkipped('Adding/dropping check constraints is not supported in MySQL.');
}

public function defaultValuesProvider()
{
$this->markTestSkipped('Adding/dropping default constraints is not supported in MySQL.');
Expand Down Expand Up @@ -403,7 +398,7 @@ public function testDefaultValues()
// primary key columns should have NULL as value
$sql = $command->insert('null_values', [])->getRawSql();
$this->assertEquals("INSERT INTO `null_values` (`id`) VALUES (NULL)", $sql);

// non-primary key columns should have DEFAULT as value
$sql = $command->insert('negative_default_values', [])->getRawSql();
$this->assertEquals("INSERT INTO `negative_default_values` (`tinyint_col`) VALUES (DEFAULT)", $sql);
Expand Down
6 changes: 2 additions & 4 deletions tests/framework/db/mysql/SchemaTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,16 +77,14 @@ public function testGetSchemaNames()
public function constraintsProvider()
{
$result = parent::constraintsProvider();
$result['1: check'][2] = false;

$result['1: check'][2][0]->expression = "(`C_check` <> _utf8mb4\\'\\')";

$result['2: primary key'][2]->name = null;
$result['2: check'][2] = false;

// Work aroung bug in MySQL 5.1 - it creates only this table in lowercase. O_o
$result['3: foreign key'][2][0]->foreignTableName = new AnyCaseValue('T_constraints_2');
$result['3: check'][2] = false;

$result['4: check'][2] = false;
return $result;
}

Expand Down

0 comments on commit 9975597

Please sign in to comment.