forked from doctrine/dbal
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 3.7.x: Trigger a runtime deprecation for Connection::executeUpdate() MariaDb1043. Detect the need to migrate JSON columns to native JSON. MariaDb. Test that comparator ignores collation for JSON columns. AbstractMySQLDriver. Use MariaDb1043Platform where applicable. Add MariaDb1043Platform using JSON as json column type.
- Loading branch information
Showing
12 changed files
with
540 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Doctrine\DBAL\Platforms; | ||
|
||
use Doctrine\DBAL\Types\JsonType; | ||
|
||
use function sprintf; | ||
|
||
/** | ||
* Provides the behavior, features and SQL dialect of the MariaDB 10.4 (10.4.6 GA) database platform. | ||
* | ||
* Extend deprecated MariaDb1027Platform to ensure correct functions used in MySQLSchemaManager which | ||
* tests for MariaDb1027Platform not MariaDBPlatform. | ||
*/ | ||
class MariaDB1043Platform extends MariaDBPlatform | ||
{ | ||
/** | ||
* Use JSON rather than LONGTEXT for json columns. Since it is not a true native type, do not override | ||
* hasNativeJsonType() so the DC2Type comment will still be set. | ||
* | ||
* {@inheritdoc} | ||
*/ | ||
public function getJsonTypeDeclarationSQL(array $column): string | ||
{ | ||
return 'JSON'; | ||
} | ||
|
||
/** | ||
* Generate SQL snippets to reverse the aliasing of JSON to LONGTEXT. | ||
* | ||
* MariaDb aliases columns specified as JSON to LONGTEXT and sets a CHECK constraint to ensure the column | ||
* is valid json. This function generates the SQL snippets which reverse this aliasing i.e. report a column | ||
* as JSON where it was originally specified as such instead of LONGTEXT. | ||
* | ||
* The CHECK constraints are stored in information_schema.CHECK_CONSTRAINTS so JOIN that table. | ||
* | ||
* @return array{string, string} | ||
*/ | ||
public function getColumnTypeSQLSnippets(string $tableAlias = 'c'): array | ||
{ | ||
if ($this->getJsonTypeDeclarationSQL([]) !== 'JSON') { | ||
return parent::getColumnTypeSQLSnippets($tableAlias); | ||
} | ||
|
||
$columnTypeSQL = <<<SQL | ||
IF( | ||
x.CHECK_CLAUSE IS NOT NULL AND $tableAlias.COLUMN_TYPE = 'longtext', | ||
'json', | ||
$tableAlias.COLUMN_TYPE | ||
) | ||
SQL; | ||
|
||
$joinCheckConstraintSQL = <<<SQL | ||
LEFT JOIN information_schema.CHECK_CONSTRAINTS x | ||
ON ( | ||
$tableAlias.TABLE_SCHEMA = x.CONSTRAINT_SCHEMA | ||
AND $tableAlias.TABLE_NAME = x.TABLE_NAME | ||
AND x.CHECK_CLAUSE = CONCAT('json_valid(`', $tableAlias.COLUMN_NAME , '`)') | ||
) | ||
SQL; | ||
|
||
return [$columnTypeSQL, $joinCheckConstraintSQL]; | ||
} | ||
|
||
/** {@inheritDoc} */ | ||
public function getColumnDeclarationSQL(string $name, array $column): string | ||
{ | ||
// MariaDb forces column collation to utf8mb4_bin where the column was declared as JSON so ignore | ||
// collation and character set for json columns as attempting to set them can cause an error. | ||
if ($this->getJsonTypeDeclarationSQL([]) === 'JSON' && ($column['type'] ?? null) instanceof JsonType) { | ||
unset($column['collation']); | ||
unset($column['charset']); | ||
} | ||
|
||
return parent::getColumnDeclarationSQL($name, $column); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Doctrine\DBAL\Tests\Functional\Schema\MySQL; | ||
|
||
use Doctrine\DBAL\Platforms\AbstractPlatform; | ||
use Doctrine\DBAL\Platforms\MariaDB1043Platform; | ||
use Doctrine\DBAL\Schema\AbstractSchemaManager; | ||
use Doctrine\DBAL\Schema\Comparator; | ||
use Doctrine\DBAL\Schema\Table; | ||
use Doctrine\DBAL\Tests\FunctionalTestCase; | ||
use Iterator; | ||
|
||
use function array_filter; | ||
|
||
/** | ||
* Tests that character set and collation are ignored for columns declared as native JSON in MySQL and | ||
* MariaDb and cannot be changed. | ||
*/ | ||
final class JsonCollationTest extends FunctionalTestCase | ||
{ | ||
private AbstractPlatform $platform; | ||
|
||
private AbstractSchemaManager $schemaManager; | ||
|
||
private Comparator $comparator; | ||
|
||
protected function setUp(): void | ||
{ | ||
$this->platform = $this->connection->getDatabasePlatform(); | ||
|
||
if (! $this->platform instanceof MariaDB1043Platform) { | ||
self::markTestSkipped(); | ||
} | ||
|
||
$this->schemaManager = $this->connection->createSchemaManager(); | ||
$this->comparator = $this->schemaManager->createComparator(); | ||
} | ||
|
||
/** | ||
* Generates a number of tables comprising only json columns. The tables are identical but for character | ||
* set and collation. | ||
* | ||
* @return Iterator<array{Table}> | ||
*/ | ||
public function tableProvider(): iterable | ||
{ | ||
$tables = [ | ||
[ | ||
'name' => 'mariadb_json_column_comparator_test', | ||
'columns' => [ | ||
['name' => 'json_1', 'charset' => 'latin1', 'collation' => 'latin1_swedish_ci'], | ||
['name' => 'json_2', 'charset' => 'utf8', 'collation' => 'utf8_general_ci'], | ||
['name' => 'json_3'], | ||
], | ||
'charset' => 'latin1', | ||
'collation' => 'latin1_swedish_ci', | ||
], | ||
[ | ||
'name' => 'mariadb_json_column_comparator_test', | ||
'columns' => [ | ||
['name' => 'json_1', 'charset' => 'latin1', 'collation' => 'latin1_swedish_ci'], | ||
['name' => 'json_2', 'charset' => 'utf8', 'collation' => 'utf8_general_ci'], | ||
['name' => 'json_3'], | ||
], | ||
], | ||
[ | ||
'name' => 'mariadb_json_column_comparator_test', | ||
'columns' => [ | ||
['name' => 'json_1', 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_bin'], | ||
['name' => 'json_2', 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_bin'], | ||
['name' => 'json_3', 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_general_ci'], | ||
], | ||
], | ||
[ | ||
'name' => 'mariadb_json_column_comparator_test', | ||
'columns' => [ | ||
['name' => 'json_1'], | ||
['name' => 'json_2'], | ||
['name' => 'json_3'], | ||
], | ||
], | ||
]; | ||
|
||
foreach ($tables as $table) { | ||
yield [$this->setUpTable( | ||
$table['name'], | ||
$table['columns'], | ||
$table['charset'] ?? null, | ||
$table['collation'] ?? null, | ||
), | ||
]; | ||
} | ||
} | ||
|
||
/** @param array{name: string, type?: string, charset?: string, collation?: string}[] $columns */ | ||
private function setUpTable(string $name, array $columns, ?string $charset = null, ?string $collation = null): Table | ||
{ | ||
$tableOptions = array_filter(['charset' => $charset, 'collation' => $collation]); | ||
|
||
$table = new Table($name, [], [], [], [], $tableOptions); | ||
|
||
foreach ($columns as $column) { | ||
if (! isset($column['charset']) || ! isset($column['collation'])) { | ||
$table->addColumn($column['name'], $column['type'] ?? 'json'); | ||
} else { | ||
$table->addColumn($column['name'], $column['type'] ?? 'json') | ||
->setPlatformOption('charset', $column['charset']) | ||
->setPlatformOption('collation', $column['collation']); | ||
} | ||
} | ||
|
||
return $table; | ||
} | ||
|
||
/** @dataProvider tableProvider */ | ||
public function testJsonColumnComparison(Table $table): void | ||
{ | ||
$this->dropAndCreateTable($table); | ||
|
||
$onlineTable = $this->schemaManager->introspectTable('mariadb_json_column_comparator_test'); | ||
$diff = $this->comparator->compareTables($table, $onlineTable); | ||
|
||
self::assertTrue($diff->isEmpty(), 'Tables should be identical.'); | ||
|
||
$originalTable = clone $table; | ||
|
||
$table->getColumn('json_1') | ||
->setPlatformOption('charset', 'utf8') | ||
->setPlatformOption('collation', 'utf8_general_ci'); | ||
|
||
$diff = $this->comparator->compareTables($table, $onlineTable); | ||
self::assertTrue($diff->isEmpty(), 'Tables should be unchanged after attempted collation change.'); | ||
|
||
$diff = $this->comparator->compareTables($table, $originalTable); | ||
self::assertTrue($diff->isEmpty(), 'Tables should be unchanged after attempted collation change.'); | ||
} | ||
} |
Oops, something went wrong.