Skip to content

Commit

Permalink
Backport doctrine#6370 into 3.8.x
Browse files Browse the repository at this point in the history
  • Loading branch information
fisharebest committed May 29, 2024
1 parent edbf307 commit 9f0e65a
Show file tree
Hide file tree
Showing 16 changed files with 473 additions and 9 deletions.
10 changes: 10 additions & 0 deletions src/Platforms/AbstractMySQLPlatform.php
Original file line number Diff line number Diff line change
Expand Up @@ -1468,4 +1468,14 @@ private function indexAssetsByLowerCaseName(array $assets): array

return $result;
}

/**
* INFORMATION_SCHEMA.CHARACTER_SETS will contain either 'utf8' or 'utf8mb3' but not both.
*/
public function informationSchemaUsesUtf8mb3(Connection $connection): bool
{
$sql = "SELECT character_set_name FROM INFORMATION_SCHEMA.CHARACTER_SETS WHERE character_set_name = 'utf8mb3'";

return $connection->fetchOne($sql) === 'utf8mb3';
}
}
13 changes: 13 additions & 0 deletions src/Platforms/MySQL/CharsetMetadataProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Doctrine\DBAL\Platforms\MySQL;

/** @internal */
interface CharsetMetadataProvider
{
public function normalizeCharset(string $charset): string;

public function getDefaultCharsetCollation(string $charset): ?string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace Doctrine\DBAL\Platforms\MySQL\CharsetMetadataProvider;

use Doctrine\DBAL\Platforms\MySQL\CharsetMetadataProvider;

use function array_key_exists;

/** @internal */
final class CachingCharsetMetadataProvider implements CharsetMetadataProvider
{
/** @var array<string,?string> */
private array $cache = [];

public function __construct(private readonly CharsetMetadataProvider $charsetMetadataProvider)
{
}

public function normalizeCharset(string $charset): string
{
return $this->charsetMetadataProvider->normalizeCharset($charset);
}

public function getDefaultCharsetCollation(string $charset): ?string
{
if (array_key_exists($charset, $this->cache)) {
return $this->cache[$charset];
}

return $this->cache[$charset] = $this->charsetMetadataProvider->getDefaultCharsetCollation($charset);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

namespace Doctrine\DBAL\Platforms\MySQL\CharsetMetadataProvider;

use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Platforms\MySQL\CharsetMetadataProvider;

/** @internal */
final class ConnectionCharsetMetadataProvider implements CharsetMetadataProvider
{
/** @var Connection */
private $connection;

/** @var bool */
private $useUtf8mb3;

public function __construct(Connection $connection, bool $useUtf8mb3)
{
$this->connection = $connection;
$this->useUtf8mb3 = $useUtf8mb3;
}

public function normalizeCharset(string $charset): string
{
if ($this->useUtf8mb3 && $charset === 'utf8') {
return 'utf8mb3';
}

if (! $this->useUtf8mb3 && $charset === 'utf8mb3') {
return 'utf8';
}

return $charset;
}

/** @throws Exception */
public function getDefaultCharsetCollation(string $charset): ?string
{
$charset = $this->normalizeCharset($charset);

$collation = $this->connection->fetchOne(
<<<'SQL'
SELECT DEFAULT_COLLATE_NAME
FROM information_schema.CHARACTER_SETS
WHERE CHARACTER_SET_NAME = ?;
SQL
,
[$charset],
);

if ($collation !== false) {
return $collation;
}

return null;
}
}
2 changes: 2 additions & 0 deletions src/Platforms/MySQL/CollationMetadataProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@
/** @internal */
interface CollationMetadataProvider
{
public function normalizeCollation(string $collation): string;

public function getCollationCharset(string $collation): ?string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ public function __construct(CollationMetadataProvider $collationMetadataProvider
$this->collationMetadataProvider = $collationMetadataProvider;
}

public function normalizeCollation(string $collation): string
{
return $this->collationMetadataProvider->normalizeCollation($collation);
}

public function getCollationCharset(string $collation): ?string
{
if (array_key_exists($collation, $this->cache)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,42 @@
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Platforms\MySQL\CollationMetadataProvider;

use function str_starts_with;
use function substr;

/** @internal */
final class ConnectionCollationMetadataProvider implements CollationMetadataProvider
{
/** @var Connection */
private $connection;

public function __construct(Connection $connection)
/** @var bool */
private $useUtf8mb3;

public function __construct(Connection $connection, bool $useUtf8mb3)
{
$this->connection = $connection;
$this->useUtf8mb3 = $useUtf8mb3;
}

public function normalizeCollation(string $collation): string
{
if ($this->useUtf8mb3 && str_starts_with($collation, 'utf8_')) {
return 'utf8mb3' . substr($collation, 4);
}

if (! $this->useUtf8mb3 && str_starts_with($collation, 'utf8mb3_')) {
return 'utf8' . substr($collation, 7);
}

return $collation;
}

/** @throws Exception */
public function getCollationCharset(string $collation): ?string
{
$collation = $this->normalizeCollation($collation);

$charset = $this->connection->fetchOne(
<<<'SQL'
SELECT CHARACTER_SET_NAME
Expand Down
29 changes: 22 additions & 7 deletions src/Platforms/MySQL/Comparator.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,21 @@
*/
class Comparator extends BaseComparator
{
/** @var CharsetMetadataProvider */
private $charsetMetadataProvider;

/** @var CollationMetadataProvider */
private $collationMetadataProvider;

/** @internal The comparator can be only instantiated by a schema manager. */
public function __construct(AbstractMySQLPlatform $platform, CollationMetadataProvider $collationMetadataProvider)
{
public function __construct(
AbstractMySQLPlatform $platform,
CharsetMetadataProvider $charsetMetadataProvider,
CollationMetadataProvider $collationMetadataProvider
) {
parent::__construct($platform);

$this->charsetMetadataProvider = $charsetMetadataProvider;
$this->collationMetadataProvider = $collationMetadataProvider;
}

Expand Down Expand Up @@ -77,15 +84,23 @@ private function normalizeColumns(Table $table): Table
/**
* @param array<string,string> $options
*
* @return array<string,string>
* @return array<string,string|null>
*/
private function normalizeOptions(array $options): array
{
if (isset($options['collation']) && ! isset($options['charset'])) {
$charset = $this->collationMetadataProvider->getCollationCharset($options['collation']);
if (isset($options['charset'])) {
$options['charset'] = $this->charsetMetadataProvider->normalizeCharset($options['charset']);

if (! isset($options['collation'])) {
$options['collation'] = $this->charsetMetadataProvider->getDefaultCharsetCollation($options['charset']);
}
}

if (isset($options['collation'])) {
$options['collation'] = $this->collationMetadataProvider->normalizeCollation($options['collation']);

if ($charset !== null) {
$options['charset'] = $charset;
if (! isset($options['charset'])) {
$options['charset'] = $this->collationMetadataProvider->getCollationCharset($options['collation']);
}
}

Expand Down
9 changes: 8 additions & 1 deletion src/Schema/MySQLSchemaManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
use Doctrine\DBAL\Platforms\MariaDb1027Platform;
use Doctrine\DBAL\Platforms\MySQL;
use Doctrine\DBAL\Platforms\MySQL\CharsetMetadataProvider\CachingCharsetMetadataProvider;
use Doctrine\DBAL\Platforms\MySQL\CharsetMetadataProvider\ConnectionCharsetMetadataProvider;
use Doctrine\DBAL\Platforms\MySQL\CollationMetadataProvider\CachingCollationMetadataProvider;
use Doctrine\DBAL\Platforms\MySQL\CollationMetadataProvider\ConnectionCollationMetadataProvider;
use Doctrine\DBAL\Result;
Expand Down Expand Up @@ -414,10 +416,15 @@ protected function _getPortableTableForeignKeyDefinition($tableForeignKey): Fore

public function createComparator(): Comparator
{
$useUtf8mb3 = $this->_platform->informationSchemaUsesUtf8mb3($this->_conn);

return new MySQL\Comparator(
$this->_platform,
new CachingCharsetMetadataProvider(
new ConnectionCharsetMetadataProvider($this->_conn, $useUtf8mb3),
),
new CachingCollationMetadataProvider(
new ConnectionCollationMetadataProvider($this->_conn),
new ConnectionCollationMetadataProvider($this->_conn, $useUtf8mb3),
),
);
}
Expand Down
86 changes: 86 additions & 0 deletions tests/Functional/Driver/DBAL6146Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php

declare(strict_types=1);

namespace Doctrine\DBAL\Tests\Functional\Driver;

use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Tests\FunctionalTestCase;
use Doctrine\DBAL\Tests\TestUtil;
use Doctrine\DBAL\Types\StringType;
use PHPUnit\Framework\Attributes\DataProvider;

class DBAL6146Test extends FunctionalTestCase
{
protected function setUp(): void
{
parent::setUp();

if (TestUtil::isDriverOneOf('pdo_mysql', 'mysqli')) {
return;
}

self::markTestSkipped('This test requires the pdo_mysql or the mysqli driver.');
}

/** @return iterable<array{array<string,string>,array<string,string>}> */
public static function equivalentCharsetAndCollationProvider(): iterable
{
yield [[], []];
yield [['charset' => 'utf8'], ['charset' => 'utf8']];
yield [['charset' => 'utf8'], ['charset' => 'utf8mb3']];
yield [['charset' => 'utf8mb3'], ['charset' => 'utf8']];
yield [['charset' => 'utf8mb3'], ['charset' => 'utf8mb3']];
yield [['collation' => 'utf8_unicode_ci'], ['collation' => 'utf8_unicode_ci']];
yield [['collation' => 'utf8_unicode_ci'], ['collation' => 'utf8mb3_unicode_ci']];
yield [['collation' => 'utf8mb3_unicode_ci'], ['collation' => 'utf8mb3_unicode_ci']];
yield [['collation' => 'utf8mb3_unicode_ci'], ['collation' => 'utf8_unicode_ci']];
yield [
['charset' => 'utf8', 'collation' => 'utf8_unicode_ci'],
['charset' => 'utf8', 'collation' => 'utf8_unicode_ci'],
];

yield [
['charset' => 'utf8', 'collation' => 'utf8_unicode_ci'],
['charset' => 'utf8mb3', 'collation' => 'utf8mb3_unicode_ci'],
];

yield [
['charset' => 'utf8mb3', 'collation' => 'utf8mb3_unicode_ci'],
['charset' => 'utf8', 'collation' => 'utf8_unicode_ci'],
];

yield [
['charset' => 'utf8mb3', 'collation' => 'utf8mb3_unicode_ci'],
['charset' => 'utf8mb3', 'collation' => 'utf8mb3_unicode_ci'],
];
}

/**
* @param array<string,string> $options1
* @param array<string,string> $options2
*
* @dataProvider equivalentCharsetAndCollationProvider
*/
public function testThereAreNoRedundantAlterTableStatements(array $options1, array $options2): void
{
$column1 = new Column('bar', new StringType(), ['length' => 32, 'platformOptions' => $options1]);
$table1 = new Table('foo6146', [$column1]);

$column2 = new Column('bar', new StringType(), ['length' => 32, 'platformOptions' => $options2]);
$table2 = new Table('foo6146', [$column2]);

$this->dropAndCreateTable($table1);

$schemaManager = $this->connection->createSchemaManager();
$oldSchema = $schemaManager->introspectSchema();
$newSchema = new Schema([$table2]);
$comparator = $schemaManager->createComparator();
$schemaDiff = $comparator->compareSchemas($oldSchema, $newSchema);
$alteredTables = $schemaDiff->getAlteredTables();

self::assertEmpty($alteredTables);
}
}
18 changes: 18 additions & 0 deletions tests/Platforms/AbstractMySQLPlatformTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\MySQL;
use Doctrine\DBAL\Platforms\MySQL\CharsetMetadataProvider;
use Doctrine\DBAL\Platforms\MySQL\CollationMetadataProvider;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Comparator;
Expand Down Expand Up @@ -738,6 +739,7 @@ public function testIgnoresDifferenceInDefaultValuesForUnsupportedColumnTypes():

$comparator = new MySQL\Comparator(
$this->platform,
$this->createMock(CharsetMetadataProvider::class),
$this->createMock(CollationMetadataProvider::class),
);

Expand Down Expand Up @@ -983,7 +985,23 @@ public static function comparatorProvider(): iterable
yield 'MySQL comparator' => [
new MySQL\Comparator(
new MySQLPlatform(),
new class implements MySQL\CharsetMetadataProvider {
public function normalizeCharset(string $charset): string
{
return $charset;
}

public function getDefaultCharsetCollation(string $charset): ?string
{
return null;
}
},
new class implements CollationMetadataProvider {
public function normalizeCollation(string $collation): string
{
return $collation;
}

public function getCollationCharset(string $collation): ?string
{
return null;
Expand Down
Loading

0 comments on commit 9f0e65a

Please sign in to comment.