diff --git a/core/INSTALL.txt b/core/INSTALL.txt index 8c399305..1b2707da 100644 --- a/core/INSTALL.txt +++ b/core/INSTALL.txt @@ -51,10 +51,10 @@ Drupal requires: - PHP 7.3.0 (or greater) (http://php.net/). For better security support it is recommended to update to at least 7.3.13. - One of the following databases: - - MySQL 5.5.3 (or greater) (http://www.mysql.com/). - - MariaDB 5.5.20 (or greater) (https://mariadb.org/). MariaDB is a fully + - MySQL 5.7.8 (or greater) (http://www.mysql.com/). + - MariaDB 10.2.7 (or greater) (https://mariadb.org/). MariaDB is a fully compatible drop-in replacement for MySQL. - - Percona Server 5.5.8 (or greater) (http://www.percona.com/). Percona + - Percona Server 5.7.8 (or greater) (http://www.percona.com/). Percona Server is a backwards-compatible replacement for MySQL. - PostgreSQL 9.1.2 (or greater) (http://www.postgresql.org/). - SQLite 3.7.11 (or greater) (http://www.sqlite.org/). diff --git a/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php b/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php index 42b5feb0..86e2f572 100644 --- a/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php +++ b/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php @@ -546,6 +546,53 @@ public function driver() { return 'mysql'; } + /** + * {@inheritdoc} + */ + public function version() { + if ($this->isMariaDb()) { + return $this->getMariaDbVersionMatch(); + } + + return $this->getServerVersion(); + } + + /** + * Determines whether the MySQL distribution is MariaDB or not. + * + * @return bool + * Returns TRUE if the distribution is MariaDB, or FALSE if not. + */ + public function isMariaDb(): bool { + return (bool) $this->getMariaDbVersionMatch(); + } + + /** + * Gets the MariaDB portion of the server version. + * + * @return string + * The MariaDB portion of the server version if present, or NULL if not. + */ + protected function getMariaDbVersionMatch(): ?string { + // MariaDB may prefix its version string with '5.5.5-', which should be + // ignored. + // @see https://github.com/MariaDB/server/blob/f6633bf058802ad7da8196d01fd19d75c53f7274/include/mysql_com.h#L42. + $regex = '/^(?:5\.5\.5-)?(\d+\.\d+\.\d+.*-mariadb.*)/i'; + + preg_match($regex, $this->getServerVersion(), $matches); + return (empty($matches[1])) ? NULL : $matches[1]; + } + + /** + * Gets the server version. + * + * @return string + * The PDO server version. + */ + protected function getServerVersion(): string { + return $this->connection->getAttribute(\PDO::ATTR_SERVER_VERSION); + } + public function databaseType() { return 'mysql'; } diff --git a/core/lib/Drupal/Core/Database/Driver/mysql/Install/Tasks.php b/core/lib/Drupal/Core/Database/Driver/mysql/Install/Tasks.php index ff441d3c..9265195f 100644 --- a/core/lib/Drupal/Core/Database/Driver/mysql/Install/Tasks.php +++ b/core/lib/Drupal/Core/Database/Driver/mysql/Install/Tasks.php @@ -2,8 +2,9 @@ namespace Drupal\Core\Database\Driver\mysql\Install; -use Drupal\Core\Database\Install\Tasks as InstallTasks; +use Drupal\Core\Database\ConnectionNotDefinedException; use Drupal\Core\Database\Database; +use Drupal\Core\Database\Install\Tasks as InstallTasks; use Drupal\Core\Database\Driver\mysql\Connection; use Drupal\Core\Database\DatabaseNotFoundException; @@ -12,6 +13,22 @@ */ class Tasks extends InstallTasks { + /** + * Minimum required MySQL version. + * + * 5.7.8 is the minimum version that supports the JSON datatype. + * @see https://dev.mysql.com/doc/refman/5.7/en/json.html + */ + const MYSQL_MINIMUM_VERSION = '5.7.8'; + + /** + * Minimum required MariaDB version. + * + * 10.2.7 is the minimum version that supports the JSON datatype (alias). + * @see https://mariadb.com/kb/en/json-data-type/ + */ + const MARIADB_MINIMUM_VERSION = '10.2.7'; + /** * Minimum required MySQLnd version. */ @@ -43,18 +60,28 @@ public function __construct() { * {@inheritdoc} */ public function name() { - return t('MySQL, MariaDB, Percona Server, or equivalent'); + try { + if (!$this->isConnectionActive() || !$this->getConnection() instanceof Connection) { + throw new ConnectionNotDefinedException('The database connection is not active or not a MySql connection'); + } + if ($this->getConnection()->isMariaDb()) { + return $this->t('MariaDB'); + } + return $this->t('MySQL, Percona Server, or equivalent'); + } + catch (ConnectionNotDefinedException $e) { + return $this->t('MySQL, MariaDB, Percona Server, or equivalent'); + } } /** * {@inheritdoc} */ public function minimumVersion() { - // This can not be increased above '5.5.5' without dropping support for all - // MariaDB versions. MariaDB prefixes its version string with '5.5.5-'. For - // more information, see - // https://github.com/MariaDB/server/blob/f6633bf058802ad7da8196d01fd19d75c53f7274/include/mysql_com.h#L42. - return '5.5.3'; + if ($this->getConnection()->isMariaDb()) { + return static::MARIADB_MINIMUM_VERSION; + } + return static::MYSQL_MINIMUM_VERSION; } /** diff --git a/core/lib/Drupal/Core/Database/Install/Tasks.php b/core/lib/Drupal/Core/Database/Install/Tasks.php index 6ba3ba73..1cc1d131 100644 --- a/core/lib/Drupal/Core/Database/Install/Tasks.php +++ b/core/lib/Drupal/Core/Database/Install/Tasks.php @@ -3,6 +3,7 @@ namespace Drupal\Core\Database\Install; use Drupal\Core\Database\Database; +use Drupal\Core\StringTranslation\TranslatableMarkup; /** * Database installer structure. @@ -300,4 +301,34 @@ public function validateDatabaseSettings($database) { return $errors; } + /** + * Translates a string to the current language or to a given language. + * + * @see \Drupal\Core\StringTranslation\TranslatableMarkup::__construct() + */ + protected function t($string, array $args = [], array $options = []) { + return new TranslatableMarkup($string, $args, $options); + } + + /** + * Determines if there is an active connection. + * + * @return bool + * TRUE if there is at least one database connection established, FALSE + * otherwise. + */ + protected function isConnectionActive() { + return Database::isActiveConnection(); + } + + /** + * Returns the database connection. + * + * @return \Drupal\Core\Database\Connection + * The database connection. + */ + protected function getConnection() { + return Database::getConnection(); + } + } diff --git a/core/tests/Drupal/Tests/Core/Database/Driver/mysql/ConnectionTest.php b/core/tests/Drupal/Tests/Core/Database/Driver/mysql/ConnectionTest.php new file mode 100644 index 00000000..be9081db --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Database/Driver/mysql/ConnectionTest.php @@ -0,0 +1,134 @@ +pdoConnection = $this->prophesize(\PDO::class); + } + + /** + * Creates a Connection object for testing. + * + * @return \Drupal\Core\Database\Driver\mysql\Connection + */ + private function createConnection(): Connection { + /** @var \PDO $pdo_connection */ + $pdo_connection = $this->pdoConnection->reveal(); + + return new class($pdo_connection) extends Connection { + + public function __construct(\PDO $connection) { + $this->connection = $connection; + } + + }; + } + + /** + * @covers ::version + * @covers ::isMariaDb + * @dataProvider providerVersionAndIsMariaDb + */ + public function testVersionAndIsMariaDb(bool $expected_is_mariadb, string $server_version, string $expected_version): void { + $this->pdoConnection + ->getAttribute(\PDO::ATTR_SERVER_VERSION) + ->shouldBeCalled() + ->willReturn($server_version); + $connection = $this->createConnection(); + + $is_mariadb = $connection->isMariaDb(); + $version = $connection->version(); + + $this->assertSame($expected_is_mariadb, $is_mariadb); + $this->assertSame($expected_version, $version); + } + + /** + * Provides test data. + * + * @return array + */ + public function providerVersionAndIsMariaDb(): array { + return [ + // MariaDB. + [ + TRUE, + '10.2.0-MariaDB', + '10.2.0-MariaDB', + ], + [ + TRUE, + '10.2.1-MARIADB', + '10.2.1-MARIADB', + ], + [ + TRUE, + '10.2.2-alphaX-MARIADB', + '10.2.2-alphaX-MARIADB', + ], + [ + TRUE, + '5.5.5-10.2.20-MariaDB-1:10.2.20+maria~bionic', + '10.2.20-MariaDB-1:10.2.20+maria~bionic', + ], + [ + TRUE, + '5.5.5-10.3.22-MariaDB-0+deb10u1', + '10.3.22-MariaDB-0+deb10u1', + ], + [ + TRUE, + '5.5.5-10.3.22-buzz+-MariaDB-0+deb10u1', + '10.3.22-buzz+-MariaDB-0+deb10u1', + ], + // MySQL. + [ + FALSE, + '5.5.5-10.2.20-notMariaDB', + '5.5.5-10.2.20-notMariaDB', + ], + [ + FALSE, + '5.5.5', + '5.5.5', + ], + [ + FALSE, + '5.5.5-', + '5.5.5-', + ], + [ + FALSE, + '5.7.28', + '5.7.28', + ], + [ + FALSE, + '5.7.28-31', + '5.7.28-31', + ], + ]; + } + +} diff --git a/core/tests/Drupal/Tests/Core/Database/Driver/mysql/install/TasksTest.php b/core/tests/Drupal/Tests/Core/Database/Driver/mysql/install/TasksTest.php new file mode 100644 index 00000000..d19a45ce --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Database/Driver/mysql/install/TasksTest.php @@ -0,0 +1,134 @@ +connection = $this->prophesize(Connection::class); + } + + /** + * Creates a Tasks object for testing. + * + * @return \Drupal\Core\Database\Driver\mysql\Install\Tasks + */ + private function createTasks(): Tasks { + /** @var \Drupal\Core\Database\Driver\mysql\Connection $connection */ + $connection = $this->connection->reveal(); + + return new class($connection) extends Tasks { + + private $connection; + + public function __construct(Connection $connection) { + $this->connection = $connection; + } + + protected function isConnectionActive() { + return TRUE; + } + + protected function getConnection() { + return $this->connection; + } + + protected function t($string, array $args = [], array $options = []) { + return $string; + } + + }; + } + + /** + * Creates a Tasks object for testing, without connection. + * + * @return \Drupal\Core\Database\Driver\mysql\Install\Tasks + */ + private function createTasksNoConnection(): Tasks { + return new class() extends Tasks { + + protected function isConnectionActive() { + return FALSE; + } + + protected function getConnection() { + return NULL; + } + + protected function t($string, array $args = [], array $options = []) { + return $string; + } + + }; + } + + /** + * @covers ::minimumVersion + * @covers ::name + * @dataProvider providerNameAndMinimumVersion + */ + public function testNameAndMinimumVersion(bool $is_mariadb, string $expected_name, string $expected_minimum_version): void { + $this->connection + ->isMariaDb() + ->shouldBeCalledTimes(2) + ->willReturn($is_mariadb); + $tasks = $this->createTasks(); + + $minimum_version = $tasks->minimumVersion(); + $name = $tasks->name(); + + $this->assertSame($expected_minimum_version, $minimum_version); + $this->assertSame($expected_name, $name); + + } + + /** + * Provides test data. + * + * @return array + */ + public function providerNameAndMinimumVersion(): array { + return [ + [ + TRUE, + 'MariaDB', + Tasks::MARIADB_MINIMUM_VERSION, + ], + [ + FALSE, + 'MySQL, Percona Server, or equivalent', + Tasks::MYSQL_MINIMUM_VERSION, + ], + ]; + } + + /** + * @covers ::name + */ + public function testNameWithNoConnection() { + $tasks = $this->createTasksNoConnection(); + $this->assertSame('MySQL, MariaDB, Percona Server, or equivalent', $tasks->name()); + } + +}