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