From 39a543768ece7efcef4ef91e198779d9d5f48465 Mon Sep 17 00:00:00 2001
From: catch <catch@35733.no-reply.drupal.org>
Date: Thu, 5 Mar 2020 08:06:37 +0000
Subject: [PATCH] =?UTF-8?q?Issue=20#3109534=20by=20TravisCarden,=20mondrak?=
 =?UTF-8?q?e,=20andypost,=20G=C3=A1bor=20Hojtsy,=20daffie,=20effulgentsia,?=
 =?UTF-8?q?=20xjm:=20Raise=20the=20minimum=20MySQL=20version=20to=205.7=20?=
 =?UTF-8?q?and=20MariaDB=20version=20to=2010.2=20in=20Drupal=209?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../Core/Database/Driver/mysql/Connection.php |  47 ++++++
 .../Database/Driver/mysql/Install/Tasks.php   |  30 +++-
 .../Drupal/Core/Database/Install/Tasks.php    |  20 +++
 .../Database/Driver/mysql/ConnectionTest.php  | 134 ++++++++++++++++++
 .../Driver/mysql/install/TasksTest.php        | 115 +++++++++++++++
 5 files changed, 340 insertions(+), 6 deletions(-)
 create mode 100644 core/tests/Drupal/Tests/Core/Database/Driver/mysql/ConnectionTest.php
 create mode 100644 core/tests/Drupal/Tests/Core/Database/Driver/mysql/install/TasksTest.php

diff --git a/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php b/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php
index 42b5feb033e5..d08fafebb375 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 ff441d3cae8f..c83b36ccbe03 100644
--- a/core/lib/Drupal/Core/Database/Driver/mysql/Install/Tasks.php
+++ b/core/lib/Drupal/Core/Database/Driver/mysql/Install/Tasks.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Core\Database\Driver\mysql\Install;
 
+use Drupal\Core\Database\ConnectionNotDefinedException;
 use Drupal\Core\Database\Install\Tasks as InstallTasks;
 use Drupal\Core\Database\Database;
 use Drupal\Core\Database\Driver\mysql\Connection;
@@ -12,6 +13,16 @@
  */
 class Tasks extends InstallTasks {
 
+  /**
+   * Minimum required MySQL version.
+   */
+  const MYSQL_MINIMUM_VERSION = '5.7.0';
+
+  /**
+   * Minimum required MariaDB version.
+   */
+  const MARIADB_MINIMUM_VERSION = '10.2.0';
+
   /**
    * Minimum required MySQLnd version.
    */
@@ -43,18 +54,25 @@ public function __construct() {
    * {@inheritdoc}
    */
   public function name() {
-    return t('MySQL, MariaDB, Percona Server, or equivalent');
+    try {
+      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 6ba3ba733c67..ef071efc508f 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,23 @@ 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);
+  }
+
+  /**
+   * 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 000000000000..be9081db3d17
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Database/Driver/mysql/ConnectionTest.php
@@ -0,0 +1,134 @@
+<?php
+
+namespace Drupal\Tests\Core\Database\Driver\mysql;
+
+use Drupal\Core\Database\Driver\mysql\Connection;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests MySQL database connections.
+ *
+ * @coversDefaultClass \Drupal\Core\Database\Driver\mysql\Connection
+ * @group Database
+ */
+class ConnectionTest extends UnitTestCase {
+
+  /**
+   * A PDO object prophecy.
+   *
+   * @var \PDO|\Prophecy\Prophecy\ObjectProphecy
+   */
+  private $pdoConnection;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    $this->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 000000000000..4dbf3a5e1f0d
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Database/Driver/mysql/install/TasksTest.php
@@ -0,0 +1,115 @@
+<?php
+
+namespace Drupal\Tests\Core\Database\Driver\mysql\install;
+
+use Drupal\Core\Database\ConnectionNotDefinedException;
+use Drupal\Core\Database\Driver\mysql\Connection;
+use Drupal\Core\Database\Driver\mysql\Install\Tasks;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests the MySQL install tasks.
+ *
+ * @coversDefaultClass \Drupal\Core\Database\Driver\mysql\Install\Tasks
+ * @group Database
+ */
+class TasksTest extends UnitTestCase {
+
+  /**
+   * A connection object prophecy.
+   *
+   * @var \Drupal\Core\Database\Driver\mysql\Connection|\Prophecy\Prophecy\ObjectProphecy
+   */
+  private $connection;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    $this->connection = $this->prophesize(Connection::class);
+  }
+
+  /**
+   * Creates a Connection object for testing.
+   *
+   * @return \Drupal\Core\Database\Driver\mysql\Connection
+   */
+  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 getConnection() {
+        return $this->connection;
+      }
+
+      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() {
+    $this->connection
+      ->isMariaDb()
+      ->shouldBeCalledOnce()
+      ->willThrow(ConnectionNotDefinedException::class);
+    $tasks = $this->createTasks();
+
+    $name = $tasks->name();
+
+    $this->assertSame('MySQL, MariaDB, Percona Server, or equivalent', $name);
+  }
+
+}
-- 
GitLab