From 1907ff75963af97ec0df5b78d1db5c75fd03ad38 Mon Sep 17 00:00:00 2001 From: Marc Neudert Date: Mon, 9 Sep 2024 20:59:40 +0200 Subject: [PATCH] Pass configured database collation to table creation statements --- config/global.ini.php | 5 ++- core/Db/Schema/Mysql.php | 10 +++++ core/Db/Schema/Tidb.php | 9 +++- core/Db/Settings.php | 5 +++ .../Integration/Db/Schema/MariadbTest.php | 41 +++++++++++++++++++ .../Integration/Db/Schema/MysqlTest.php | 41 +++++++++++++++++++ .../Integration/Db/Schema/TidbTest.php | 41 +++++++++++++++++++ 7 files changed, 148 insertions(+), 4 deletions(-) diff --git a/config/global.ini.php b/config/global.ini.php index d683536ce53..b8225832e2a 100644 --- a/config/global.ini.php +++ b/config/global.ini.php @@ -45,11 +45,12 @@ ; Matomo should work correctly without this setting but we recommend to have a charset set. charset = utf8 -; In some database setups the collation for the connection changes after a "SET NAMES" statement -; is issued, unless a "COLLATE" argument is added. +; In some database setups the collation used for queries and creating tables can have unexpected +; values, or change after a database version upgrade. ; If you encounter "Illegal mix of collation" errors, setting this config to the value matching ; your existing database tables can help. ; This setting will only be used if "charset" is also set. +; Matomo should work correctly without this setting but we recommend to have a collation set. collation = ; Database error codes to ignore during updates diff --git a/core/Db/Schema/Mysql.php b/core/Db/Schema/Mysql.php index 47c05756289..f5b24b4da39 100644 --- a/core/Db/Schema/Mysql.php +++ b/core/Db/Schema/Mysql.php @@ -678,10 +678,15 @@ public function getTableCreateOptions(): string { $engine = $this->getTableEngine(); $charset = $this->getUsedCharset(); + $collation = $this->getUsedCollation(); $rowFormat = $this->getTableRowFormat(); $options = "ENGINE=$engine DEFAULT CHARSET=$charset"; + if ('' !== $collation) { + $options .= " COLLATE=$collation"; + } + if ('' !== $rowFormat) { $options .= " $rowFormat"; } @@ -784,6 +789,11 @@ protected function getUsedCharset(): string return $this->getDbSettings()->getUsedCharset(); } + protected function getUsedCollation(): string + { + return $this->getDbSettings()->getUsedCollation(); + } + private function getTablePrefix() { return $this->getDbSettings()->getTablePrefix(); diff --git a/core/Db/Schema/Tidb.php b/core/Db/Schema/Tidb.php index 732b44f9315..cf020d71056 100644 --- a/core/Db/Schema/Tidb.php +++ b/core/Db/Schema/Tidb.php @@ -36,12 +36,17 @@ public function getTableCreateOptions(): string { $engine = $this->getTableEngine(); $charset = $this->getUsedCharset(); + $collation = $this->getUsedCollation(); $rowFormat = $this->getTableRowFormat(); + if ('utf8mb4' === $charset && '' === $collation) { + $collation = 'utf8mb4_0900_ai_ci'; + } + $options = "ENGINE=$engine DEFAULT CHARSET=$charset"; - if ('utf8mb4' === $charset) { - $options .= ' COLLATE=utf8mb4_0900_ai_ci'; + if ('' !== $collation) { + $options .= " COLLATE=$collation"; } if ('' !== $rowFormat) { diff --git a/core/Db/Settings.php b/core/Db/Settings.php index c9707fcce8c..e18a9dab8b6 100644 --- a/core/Db/Settings.php +++ b/core/Db/Settings.php @@ -33,6 +33,11 @@ public function getUsedCharset() return strtolower($this->getDbSetting('charset')); } + public function getUsedCollation() + { + return strtolower($this->getDbSetting('collation') ?? ''); + } + public function getRowFormat() { return $this->getUsedCharset() === 'utf8mb4' ? 'ROW_FORMAT=DYNAMIC' : ''; diff --git a/tests/PHPUnit/Integration/Db/Schema/MariadbTest.php b/tests/PHPUnit/Integration/Db/Schema/MariadbTest.php index d5e1fe92a63..78b230331a3 100644 --- a/tests/PHPUnit/Integration/Db/Schema/MariadbTest.php +++ b/tests/PHPUnit/Integration/Db/Schema/MariadbTest.php @@ -68,4 +68,45 @@ public function testOptimize() // should optimize when forced $this->assertTrue($schema->optimizeTables(['table3', 'table4'], true), true); } + + /** + * @dataProvider getTableCreateOptionsTestData + */ + public function testTableCreateOptions(array $optionOverrides, string $expected): void + { + if (DatabaseConfig::getConfigValue('schema') !== 'Mariadb') { + self::markTestSkipped('Mariadb is not available'); + } + + foreach ($optionOverrides as $name => $value) { + DatabaseConfig::setConfigValue($name, $value); + } + + $schema = Db\Schema::getInstance(); + + self::assertSame($expected, $schema->getTableCreateOptions()); + } + + public function getTableCreateOptionsTestData(): iterable + { + yield 'defaults' => [ + [], + 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC' + ]; + + yield 'override charset' => [ + ['charset' => 'utf8mb3'], + 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb3' + ]; + + yield 'override collation' => [ + ['collation' => 'utf8mb4_general_ci'], + 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC' + ]; + + yield 'override charset and collation' => [ + ['charset' => 'utf8mb3', 'collation' => 'utf8mb3_general_ci'], + 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci' + ]; + } } diff --git a/tests/PHPUnit/Integration/Db/Schema/MysqlTest.php b/tests/PHPUnit/Integration/Db/Schema/MysqlTest.php index 69094eb4386..b1c455ff93b 100644 --- a/tests/PHPUnit/Integration/Db/Schema/MysqlTest.php +++ b/tests/PHPUnit/Integration/Db/Schema/MysqlTest.php @@ -68,4 +68,45 @@ public function testOptimize() // should optimize when forced $this->assertTrue($schema->optimizeTables(['table3', 'table4'], true)); } + + /** + * @dataProvider getTableCreateOptionsTestData + */ + public function testTableCreateOptions(array $optionOverrides, string $expected): void + { + if (DatabaseConfig::getConfigValue('schema') !== 'Mysql') { + self::markTestSkipped('Mysql is not available'); + } + + foreach ($optionOverrides as $name => $value) { + DatabaseConfig::setConfigValue($name, $value); + } + + $schema = Db\Schema::getInstance(); + + self::assertSame($expected, $schema->getTableCreateOptions()); + } + + public function getTableCreateOptionsTestData(): iterable + { + yield 'defaults' => [ + [], + 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC' + ]; + + yield 'override charset' => [ + ['charset' => 'utf8mb3'], + 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb3' + ]; + + yield 'override collation' => [ + ['collation' => 'utf8mb4_general_ci'], + 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC' + ]; + + yield 'override charset and collation' => [ + ['charset' => 'utf8mb3', 'collation' => 'utf8mb3_general_ci'], + 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci' + ]; + } } diff --git a/tests/PHPUnit/Integration/Db/Schema/TidbTest.php b/tests/PHPUnit/Integration/Db/Schema/TidbTest.php index 3ed40f9039a..9ec52106479 100644 --- a/tests/PHPUnit/Integration/Db/Schema/TidbTest.php +++ b/tests/PHPUnit/Integration/Db/Schema/TidbTest.php @@ -43,4 +43,45 @@ public function testOptimize() $this->assertFalse($schema->optimizeTables(['table3', 'table4'])); $this->assertFalse($schema->optimizeTables(['table3', 'table4'], true)); } + + /** + * @dataProvider getTableCreateOptionsTestData + */ + public function testTableCreateOptions(array $optionOverrides, string $expected): void + { + if (DatabaseConfig::getConfigValue('schema') !== 'Tidb') { + self::markTestSkipped('Tidb is not available'); + } + + foreach ($optionOverrides as $name => $value) { + DatabaseConfig::setConfigValue($name, $value); + } + + $schema = Db\Schema::getInstance(); + + self::assertSame($expected, $schema->getTableCreateOptions()); + } + + public function getTableCreateOptionsTestData(): iterable + { + yield 'defaults' => [ + [], + 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC' + ]; + + yield 'override charset' => [ + ['charset' => 'utf8mb3'], + 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb4_0900_ai_ci' + ]; + + yield 'override collation' => [ + ['collation' => 'utf8mb4_general_ci'], + 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC' + ]; + + yield 'override charset and collation' => [ + ['charset' => 'utf8mb3', 'collation' => 'utf8mb3_general_ci'], + 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci' + ]; + } }