From cff1307c85086a9cbe5369f44f509a526fd6c503 Mon Sep 17 00:00:00 2001
From: catch <catch@35733.no-reply.drupal.org>
Date: Mon, 16 Aug 2021 15:38:07 +0100
Subject: [PATCH] Issue #3218978 by effulgentsia, daffie, mcdruid, Wim Leers:
 MySQL driver allows settings.php to remove ANSI_QUOTES from sql_mode, but
 doesn't work when it is

---
 .../Core/Database/Driver/mysql/Connection.php | 29 +++++++++++
 .../KernelTests/Core/Database/SqlModeTest.php | 50 +++++++++++++++++++
 2 files changed, 79 insertions(+)
 create mode 100644 core/tests/Drupal/KernelTests/Core/Database/SqlModeTest.php

diff --git a/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php b/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php
index 687700e7859f..ef6027256ccc 100644
--- a/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php
+++ b/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php
@@ -89,6 +89,35 @@ class Connection extends DatabaseConnection {
    */
   protected $identifierQuotes = ['"', '"'];
 
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(\PDO $connection, array $connection_options) {
+    // If the SQL mode doesn't include 'ANSI_QUOTES' (explicitly or via a
+    // combination mode), then MySQL doesn't interpret a double quote as an
+    // identifier quote, in which case use the non-ANSI-standard backtick.
+    //
+    // Because we still support MySQL 5.7, check for the deprecated combination
+    // modes as well.
+    //
+    // @see https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_ansi_quotes
+    $ansi_quotes_modes = ['ANSI_QUOTES', 'ANSI', 'DB2', 'MAXDB', 'MSSQL', 'ORACLE', 'POSTGRESQL'];
+    $is_ansi_quotes_mode = FALSE;
+    foreach ($ansi_quotes_modes as $mode) {
+      // None of the modes in $ansi_quotes_modes are substrings of other modes
+      // that are not in $ansi_quotes_modes, so a simple stripos() does not
+      // return false positives.
+      if (stripos($connection_options['init_commands']['sql_mode'], $mode) !== FALSE) {
+        $is_ansi_quotes_mode = TRUE;
+        break;
+      }
+    }
+    if ($this->identifierQuotes === ['"', '"'] && !$is_ansi_quotes_mode) {
+      $this->identifierQuotes = ['`', '`'];
+    }
+    parent::__construct($connection, $connection_options);
+  }
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/tests/Drupal/KernelTests/Core/Database/SqlModeTest.php b/core/tests/Drupal/KernelTests/Core/Database/SqlModeTest.php
new file mode 100644
index 000000000000..49dd61373db2
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/Database/SqlModeTest.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Drupal\KernelTests\Core\Database;
+
+/**
+ * Tests compatibility of the MySQL driver with various sql_mode options.
+ *
+ * @group Database
+ */
+class SqlModeTest extends DatabaseTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    if ($this->connection->databaseType() !== 'mysql') {
+      $this->markTestSkipped("Skipping test since sql_mode is a MySQL-only feature.");
+    }
+  }
+
+  /**
+   * Tests quoting identifiers in queries.
+   */
+  public function testQuotingIdentifiers() {
+    // Use SQL-reserved words for both the table and column names.
+    $query = $this->connection->query('SELECT [update] FROM {select}');
+    $this->assertEquals('Update value 1', $query->fetchObject()->update);
+    $this->assertStringContainsString('SELECT `update` FROM `', $query->getQueryString());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getDatabaseConnectionInfo() {
+    $info = parent::getDatabaseConnectionInfo();
+
+    // This runs during setUp(), so is not yet skipped for non MySQL databases.
+    // We defer skipping the test to later in setUp(), so that that can be
+    // based on databaseType() rather than 'driver', but here all we have to go
+    // on is 'driver'.
+    if ($info['default']['driver'] === 'mysql') {
+      $info['default']['init_commands']['sql_mode'] = "SET sql_mode = ''";
+    }
+
+    return $info;
+  }
+
+}
-- 
GitLab