From d601f95d9a255f7d73b757bd6df7586e02b36179 Mon Sep 17 00:00:00 2001
From: catch <catch@35733.no-reply.drupal.org>
Date: Thu, 14 May 2020 15:25:41 +0100
Subject: [PATCH] Issue #3135629 by alexpott, dww, daffie, jungle, Rkumar,
 balsama, xjm: Minimum MySQL version requirement is not confirmed when
 upgrading existing sites from Drupal 8 to Drupal 9

(cherry picked from commit cefc84f9b4d16fd1a8e8f756d9620aa6ec95fa7e)
---
 .../Drupal/Core/Database/Install/Tasks.php    | 13 ++++
 core/modules/system/system.install            | 17 ++++-
 .../Connection.php                            | 49 +++++++++++++
 .../Insert.php                                | 10 +++
 .../Install/Tasks.php                         | 19 ++++++
 .../Schema.php                                | 10 +++
 .../Upsert.php                                | 10 +++
 .../Update/DatabaseVersionCheckUpdateTest.php | 68 +++++++++++++++++++
 8 files changed, 195 insertions(+), 1 deletion(-)
 create mode 100644 core/modules/system/tests/modules/driver_test/src/Driver/Database/DrivertestMysqlDeprecatedVersion/Connection.php
 create mode 100644 core/modules/system/tests/modules/driver_test/src/Driver/Database/DrivertestMysqlDeprecatedVersion/Insert.php
 create mode 100644 core/modules/system/tests/modules/driver_test/src/Driver/Database/DrivertestMysqlDeprecatedVersion/Install/Tasks.php
 create mode 100644 core/modules/system/tests/modules/driver_test/src/Driver/Database/DrivertestMysqlDeprecatedVersion/Schema.php
 create mode 100644 core/modules/system/tests/modules/driver_test/src/Driver/Database/DrivertestMysqlDeprecatedVersion/Upsert.php
 create mode 100644 core/modules/system/tests/src/Functional/Update/DatabaseVersionCheckUpdateTest.php

diff --git a/core/lib/Drupal/Core/Database/Install/Tasks.php b/core/lib/Drupal/Core/Database/Install/Tasks.php
index 47b973b9b007..b7fceace1196 100644
--- a/core/lib/Drupal/Core/Database/Install/Tasks.php
+++ b/core/lib/Drupal/Core/Database/Install/Tasks.php
@@ -151,6 +151,19 @@ public function runTasks() {
     return $this->results['fail'];
   }
 
+  /**
+   * Checks engine version requirements for the status report.
+   *
+   * This method is called during runtime and update requirements checks.
+   *
+   * @return \Drupal\Core\StringTranslation\TranslatableMarkup[]
+   *   A list of error messages.
+   */
+  final public function engineVersionRequirementsCheck() {
+    $this->checkEngineVersion();
+    return $this->results['fail'];
+  }
+
   /**
    * Check if we can connect to the database.
    */
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index ee35c1c79af8..39c369d98cdb 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -427,9 +427,11 @@ function system_requirements($phase) {
       $requirements['database_extensions']['value'] = t('Enabled');
     }
   }
-  else {
+
+  if ($phase === 'runtime' || $phase === 'update') {
     // Database information.
     $class = Database::getConnection()->getDriverClass('Install\\Tasks');
+    /** @var \Drupal\Core\Database\Install\Tasks $tasks */
     $tasks = new $class();
     $requirements['database_system'] = [
       'title' => t('Database system'),
@@ -439,6 +441,19 @@ function system_requirements($phase) {
       'title' => t('Database system version'),
       'value' => Database::getConnection()->version(),
     ];
+
+    $errors = $tasks->engineVersionRequirementsCheck();
+    $error_count = count($errors);
+    if ($error_count > 0) {
+      $error_message = [
+        '#theme' => 'item_list',
+        '#items' => $errors,
+        // Use the comma-list style to display a single error without bullets.
+        '#context' => ['list_style' => $error_count === 1 ? 'comma-list' : ''],
+      ];
+      $requirements['database_system_version']['severity'] = REQUIREMENT_ERROR;
+      $requirements['database_system_version']['description'] = $error_message;
+    }
   }
 
   // Test PHP memory_limit
diff --git a/core/modules/system/tests/modules/driver_test/src/Driver/Database/DrivertestMysqlDeprecatedVersion/Connection.php b/core/modules/system/tests/modules/driver_test/src/Driver/Database/DrivertestMysqlDeprecatedVersion/Connection.php
new file mode 100644
index 000000000000..c7ec0fed72a8
--- /dev/null
+++ b/core/modules/system/tests/modules/driver_test/src/Driver/Database/DrivertestMysqlDeprecatedVersion/Connection.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace Drupal\driver_test\Driver\Database\DrivertestMysqlDeprecatedVersion;
+
+use Drupal\Core\Database\Driver\mysql\Connection as CoreConnection;
+
+/**
+ * MySQL test implementation of \Drupal\Core\Database\Connection.
+ */
+class Connection extends CoreConnection {
+
+  /**
+   * Hardcoded database server version.
+   *
+   * Faking that we are on a deprecated database.
+   *
+   * @var string
+   */
+  protected $databaseVersion = '10.2.31-MariaDB-1:10.2.31+maria~bionic-log';
+
+  /**
+   * {@inheritdoc}
+   */
+  public function driver() {
+    return 'DrivertestMysqlDeprecatedVersion';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isMariaDb(): bool {
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function version() {
+    return $this->databaseVersion;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getServerVersion(): string {
+    return $this->databaseVersion;
+  }
+
+}
diff --git a/core/modules/system/tests/modules/driver_test/src/Driver/Database/DrivertestMysqlDeprecatedVersion/Insert.php b/core/modules/system/tests/modules/driver_test/src/Driver/Database/DrivertestMysqlDeprecatedVersion/Insert.php
new file mode 100644
index 000000000000..86affc1a349c
--- /dev/null
+++ b/core/modules/system/tests/modules/driver_test/src/Driver/Database/DrivertestMysqlDeprecatedVersion/Insert.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace Drupal\driver_test\Driver\Database\DrivertestMysqlDeprecatedVersion;
+
+use Drupal\Core\Database\Driver\mysql\Insert as CoreInsert;
+
+/**
+ * MySQL test implementation of \Drupal\Core\Database\Query\Insert.
+ */
+class Insert extends CoreInsert {}
diff --git a/core/modules/system/tests/modules/driver_test/src/Driver/Database/DrivertestMysqlDeprecatedVersion/Install/Tasks.php b/core/modules/system/tests/modules/driver_test/src/Driver/Database/DrivertestMysqlDeprecatedVersion/Install/Tasks.php
new file mode 100644
index 000000000000..c768de5721ef
--- /dev/null
+++ b/core/modules/system/tests/modules/driver_test/src/Driver/Database/DrivertestMysqlDeprecatedVersion/Install/Tasks.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Drupal\driver_test\Driver\Database\DrivertestMysqlDeprecatedVersion\Install;
+
+use Drupal\Core\Database\Driver\mysql\Install\Tasks as CoreTasks;
+
+/**
+ * Specifies installation tasks for MySQL test databases.
+ */
+class Tasks extends CoreTasks {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function name() {
+    return t('MySQL deprecated version by the driver_test module');
+  }
+
+}
diff --git a/core/modules/system/tests/modules/driver_test/src/Driver/Database/DrivertestMysqlDeprecatedVersion/Schema.php b/core/modules/system/tests/modules/driver_test/src/Driver/Database/DrivertestMysqlDeprecatedVersion/Schema.php
new file mode 100644
index 000000000000..fef11ed4de0b
--- /dev/null
+++ b/core/modules/system/tests/modules/driver_test/src/Driver/Database/DrivertestMysqlDeprecatedVersion/Schema.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace Drupal\driver_test\Driver\Database\DrivertestMysqlDeprecatedVersion;
+
+use Drupal\Core\Database\Driver\mysql\Schema as CoreSchema;
+
+/**
+ * MySQL test implementation of \Drupal\Core\Database\Schema.
+ */
+class Schema extends CoreSchema {}
diff --git a/core/modules/system/tests/modules/driver_test/src/Driver/Database/DrivertestMysqlDeprecatedVersion/Upsert.php b/core/modules/system/tests/modules/driver_test/src/Driver/Database/DrivertestMysqlDeprecatedVersion/Upsert.php
new file mode 100644
index 000000000000..78ee82d34db4
--- /dev/null
+++ b/core/modules/system/tests/modules/driver_test/src/Driver/Database/DrivertestMysqlDeprecatedVersion/Upsert.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace Drupal\driver_test\Driver\Database\DrivertestMysqlDeprecatedVersion;
+
+use Drupal\Core\Database\Driver\mysql\Upsert as CoreUpsert;
+
+/**
+ * MySQL test implementation of \Drupal\Core\Database\Query\Upsert.
+ */
+class Upsert extends CoreUpsert {}
diff --git a/core/modules/system/tests/src/Functional/Update/DatabaseVersionCheckUpdateTest.php b/core/modules/system/tests/src/Functional/Update/DatabaseVersionCheckUpdateTest.php
new file mode 100644
index 000000000000..2202dc000b29
--- /dev/null
+++ b/core/modules/system/tests/src/Functional/Update/DatabaseVersionCheckUpdateTest.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Drupal\Tests\system\Functional\Update;
+
+use Drupal\Core\Database\Database;
+use Drupal\Core\Url;
+use Drupal\Tests\BrowserTestBase;
+use Drupal\Tests\UpdatePathTestTrait;
+
+/**
+ * Tests that updates fail if the database does not meet the minimum version.
+ *
+ * @group Update
+ */
+class DatabaseVersionCheckUpdateTest extends BrowserTestBase {
+  use UpdatePathTestTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+    $this->ensureUpdatesToRun();
+  }
+
+  /**
+   * Tests that updates fail if the database does not meet the minimum version.
+   */
+  public function testUpdate() {
+    if (Database::getConnection()->driver() !== 'mysql') {
+      $this->markTestSkipped('This test only works with the mysql driver');
+    }
+
+    // Use a database driver that reports a fake database version that does
+    // not meet requirements. Only change the necessary settings in the database
+    // settings array so that run-tests.sh continues to work.
+    $autoload = Database::findDriverAutoloadDirectory('Drupal\driver_test\Driver\Database\DrivertestMysqlDeprecatedVersion', \Drupal::root());
+    $settings['databases']['default']['default']['driver'] = (object) [
+      'value' => 'DrivertestMysqlDeprecatedVersion',
+      'required' => TRUE,
+    ];
+    $settings['databases']['default']['default']['namespace'] = (object) [
+      'value' => 'Drupal\\driver_test\\Driver\\Database\\DrivertestMysqlDeprecatedVersion',
+      'required' => TRUE,
+    ];
+    $settings['databases']['default']['default']['autoload'] = (object) [
+      'value' => $autoload,
+      'required' => TRUE,
+    ];
+    $settings['settings'] = [
+      'update_free_access' => (object) [
+        'value' => TRUE,
+        'required' => TRUE,
+      ],
+    ];
+    $this->writeSettings($settings);
+
+    $this->drupalGet(Url::fromRoute('system.db_update'));
+    $this->assertSession()->pageTextContains('Errors found');
+    $this->assertSession()->pageTextContains('The database server version 10.2.31-MariaDB-1:10.2.31+maria~bionic-log is less than the minimum required version');
+  }
+
+}
-- 
GitLab