diff --git a/config/global.ini.php b/config/global.ini.php
index e7b7f7405c9..f41e167a7e6 100644
--- a/config/global.ini.php
+++ b/config/global.ini.php
@@ -45,6 +45,14 @@
; Matomo should work correctly without this setting but we recommend to have a charset set.
charset = utf8
+; 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
;
;ignore_error_codes[] = 1105
@@ -84,6 +92,7 @@
type = InnoDB
schema = Mysql
charset = utf8mb4
+collation = utf8mb4_general_ci
enable_ssl = 0
ssl_ca =
ssl_cert =
diff --git a/core/Db.php b/core/Db.php
index dc7b0a45beb..1732afc0853 100644
--- a/core/Db.php
+++ b/core/Db.php
@@ -183,6 +183,7 @@ public static function createReaderDatabaseObject($dbConfig = null)
$dbConfig['type'] = $masterDbConfig['type'];
$dbConfig['tables_prefix'] = $masterDbConfig['tables_prefix'];
$dbConfig['charset'] = $masterDbConfig['charset'];
+ $dbConfig['collation'] = $masterDbConfig['collation'] ?? null;
$db = @Adapter::factory($dbConfig['adapter'], $dbConfig);
diff --git a/core/Db/Schema.php b/core/Db/Schema.php
index 08a044633f2..4818142523d 100644
--- a/core/Db/Schema.php
+++ b/core/Db/Schema.php
@@ -89,6 +89,27 @@ private function getSchema(): SchemaInterface
return $this->schema;
}
+ /**
+ * Returns the default collation for a charset.
+ *
+ * @param string $charset
+ * @return string
+ */
+ public function getDefaultCollationForCharset(string $charset): string
+ {
+ return $this->getSchema()->getDefaultCollationForCharset($charset);
+ }
+
+ /**
+ * Get the table options to use for a CREATE TABLE statement.
+ *
+ * @return string
+ */
+ public function getTableCreateOptions(): string
+ {
+ return $this->getSchema()->getTableCreateOptions();
+ }
+
/**
* Get the SQL to create a specific Piwik table
*
diff --git a/core/Db/Schema/Mysql.php b/core/Db/Schema/Mysql.php
index 8d6cf008c11..65bec751364 100644
--- a/core/Db/Schema/Mysql.php
+++ b/core/Db/Schema/Mysql.php
@@ -39,10 +39,8 @@ class Mysql implements SchemaInterface
*/
public function getTablesCreateSql()
{
- $engine = $this->getTableEngine();
$prefixTables = $this->getTablePrefix();
- $dbSettings = new Db\Settings();
- $charset = $dbSettings->getUsedCharset();
+ $tableOptions = $this->getTableCreateOptions();
$tables = array(
'user' => "CREATE TABLE {$prefixTables}user (
@@ -62,7 +60,7 @@ public function getTablesCreateSql()
ts_changes_shown TIMESTAMP NULL,
PRIMARY KEY(login),
UNIQUE INDEX `uniq_email` (`email`)
- ) ENGINE=$engine DEFAULT CHARSET=$charset
+ ) $tableOptions
",
'user_token_auth' => "CREATE TABLE {$prefixTables}user_token_auth (
idusertokenauth BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
@@ -77,7 +75,7 @@ public function getTablesCreateSql()
secure_only TINYINT(2) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY(idusertokenauth),
UNIQUE KEY uniq_password(password)
- ) ENGINE=$engine DEFAULT CHARSET=$charset
+ ) $tableOptions
",
'twofactor_recovery_code' => "CREATE TABLE {$prefixTables}twofactor_recovery_code (
@@ -85,7 +83,7 @@ public function getTablesCreateSql()
login VARCHAR(100) NOT NULL,
recovery_code VARCHAR(40) NOT NULL,
PRIMARY KEY(idrecoverycode)
- ) ENGINE=$engine DEFAULT CHARSET=$charset
+ ) $tableOptions
",
'access' => "CREATE TABLE {$prefixTables}access (
@@ -95,7 +93,7 @@ public function getTablesCreateSql()
access VARCHAR(50) NULL,
PRIMARY KEY(idaccess),
INDEX index_loginidsite (login, idsite)
- ) ENGINE=$engine DEFAULT CHARSET=$charset
+ ) $tableOptions
",
'site' => "CREATE TABLE {$prefixTables}site (
@@ -119,7 +117,7 @@ public function getTablesCreateSql()
keep_url_fragment TINYINT NOT NULL DEFAULT 0,
creator_login VARCHAR(100) NULL,
PRIMARY KEY(idsite)
- ) ENGINE=$engine DEFAULT CHARSET=$charset
+ ) $tableOptions
",
'plugin_setting' => "CREATE TABLE {$prefixTables}plugin_setting (
@@ -131,7 +129,7 @@ public function getTablesCreateSql()
`idplugin_setting` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
PRIMARY KEY (idplugin_setting),
INDEX(plugin_name, user_login)
- ) ENGINE=$engine DEFAULT CHARSET=$charset
+ ) $tableOptions
",
'site_setting' => "CREATE TABLE {$prefixTables}site_setting (
@@ -143,14 +141,14 @@ public function getTablesCreateSql()
`idsite_setting` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
PRIMARY KEY (idsite_setting),
INDEX(idsite, plugin_name)
- ) ENGINE=$engine DEFAULT CHARSET=$charset
+ ) $tableOptions
",
'site_url' => "CREATE TABLE {$prefixTables}site_url (
idsite INTEGER(10) UNSIGNED NOT NULL,
url VARCHAR(190) NOT NULL,
PRIMARY KEY(idsite, url)
- ) ENGINE=$engine DEFAULT CHARSET=$charset
+ ) $tableOptions
",
'goal' => "CREATE TABLE `{$prefixTables}goal` (
@@ -167,7 +165,7 @@ public function getTablesCreateSql()
`deleted` tinyint(4) NOT NULL default '0',
`event_value_as_revenue` tinyint(4) NOT NULL default '0',
PRIMARY KEY (`idsite`,`idgoal`)
- ) ENGINE=$engine DEFAULT CHARSET=$charset
+ ) $tableOptions
",
'logger_message' => "CREATE TABLE {$prefixTables}logger_message (
@@ -177,7 +175,7 @@ public function getTablesCreateSql()
level VARCHAR(16) NULL,
message TEXT NULL,
PRIMARY KEY(idlogger_message)
- ) ENGINE=$engine DEFAULT CHARSET=$charset
+ ) $tableOptions
",
'log_action' => "CREATE TABLE {$prefixTables}log_action (
@@ -188,7 +186,7 @@ public function getTablesCreateSql()
url_prefix TINYINT(2) NULL,
PRIMARY KEY(idaction),
INDEX index_type_hash (type, hash)
- ) ENGINE=$engine DEFAULT CHARSET=$charset
+ ) $tableOptions
",
'log_visit' => "CREATE TABLE {$prefixTables}log_visit (
@@ -202,7 +200,7 @@ public function getTablesCreateSql()
INDEX index_idsite_config_datetime (idsite, config_id, visit_last_action_time),
INDEX index_idsite_datetime (idsite, visit_last_action_time),
INDEX index_idsite_idvisitor_time (idsite, idvisitor, visit_last_action_time DESC)
- ) ENGINE=$engine DEFAULT CHARSET=$charset
+ ) $tableOptions
",
'log_conversion_item' => "CREATE TABLE `{$prefixTables}log_conversion_item` (
@@ -223,7 +221,7 @@ public function getTablesCreateSql()
deleted TINYINT(1) UNSIGNED NOT NULL,
PRIMARY KEY(idvisit, idorder, idaction_sku),
INDEX index_idsite_servertime ( idsite, server_time )
- ) ENGINE=$engine DEFAULT CHARSET=$charset
+ ) $tableOptions
",
'log_conversion' => "CREATE TABLE `{$prefixTables}log_conversion` (
@@ -247,7 +245,7 @@ public function getTablesCreateSql()
PRIMARY KEY (idvisit, idgoal, buster),
UNIQUE KEY unique_idsite_idorder (idsite, idorder),
INDEX index_idsite_datetime ( idsite, server_time )
- ) ENGINE=$engine DEFAULT CHARSET=$charset
+ ) $tableOptions
",
'log_link_visit_action' => "CREATE TABLE {$prefixTables}log_link_visit_action (
@@ -261,7 +259,7 @@ public function getTablesCreateSql()
pageview_position MEDIUMINT UNSIGNED DEFAULT NULL,
PRIMARY KEY(idlink_va),
INDEX index_idvisit(idvisit)
- ) ENGINE=$engine DEFAULT CHARSET=$charset
+ ) $tableOptions
",
'log_profiling' => "CREATE TABLE {$prefixTables}log_profiling (
@@ -271,7 +269,7 @@ public function getTablesCreateSql()
idprofiling BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
PRIMARY KEY (idprofiling),
UNIQUE KEY query(query(100))
- ) ENGINE=$engine DEFAULT CHARSET=$charset
+ ) $tableOptions
",
'option' => "CREATE TABLE `{$prefixTables}option` (
@@ -280,7 +278,7 @@ public function getTablesCreateSql()
autoload TINYINT NOT NULL DEFAULT '1',
PRIMARY KEY ( option_name ),
INDEX autoload( autoload )
- ) ENGINE=$engine DEFAULT CHARSET=$charset
+ ) $tableOptions
",
'session' => "CREATE TABLE {$prefixTables}session (
@@ -289,7 +287,7 @@ public function getTablesCreateSql()
lifetime INTEGER,
data MEDIUMTEXT,
PRIMARY KEY ( id )
- ) ENGINE=$engine DEFAULT CHARSET=$charset
+ ) $tableOptions
",
'archive_numeric' => "CREATE TABLE {$prefixTables}archive_numeric (
@@ -304,7 +302,7 @@ public function getTablesCreateSql()
PRIMARY KEY(idarchive, name),
INDEX index_idsite_dates_period(idsite, date1, date2, period, name(6)),
INDEX index_period_archived(period, ts_archived)
- ) ENGINE=$engine DEFAULT CHARSET=$charset
+ ) $tableOptions
",
'archive_blob' => "CREATE TABLE {$prefixTables}archive_blob (
@@ -318,7 +316,7 @@ public function getTablesCreateSql()
value MEDIUMBLOB NULL,
PRIMARY KEY(idarchive, name),
INDEX index_period_archived(period, ts_archived)
- ) ENGINE=$engine DEFAULT CHARSET=$charset
+ ) $tableOptions
",
'archive_invalidations' => "CREATE TABLE `{$prefixTables}archive_invalidations` (
@@ -335,14 +333,14 @@ public function getTablesCreateSql()
`report` VARCHAR(255) NULL,
PRIMARY KEY(idinvalidation),
INDEX index_idsite_dates_period_name(idsite, date1, period)
- ) ENGINE=$engine DEFAULT CHARSET=$charset
+ ) $tableOptions
",
'sequence' => "CREATE TABLE {$prefixTables}sequence (
`name` VARCHAR(120) NOT NULL,
`value` BIGINT(20) UNSIGNED NOT NULL ,
PRIMARY KEY(`name`)
- ) ENGINE=$engine DEFAULT CHARSET=$charset
+ ) $tableOptions
",
'brute_force_log' => "CREATE TABLE {$prefixTables}brute_force_log (
@@ -352,7 +350,7 @@ public function getTablesCreateSql()
`login` VARCHAR(100) NULL,
INDEX index_ip_address(ip_address),
PRIMARY KEY(`id_brute_force_log`)
- ) ENGINE=$engine DEFAULT CHARSET=$charset
+ ) $tableOptions
",
'tracking_failure' => "CREATE TABLE {$prefixTables}tracking_failure (
@@ -361,14 +359,14 @@ public function getTablesCreateSql()
`date_first_occurred` DATETIME NOT NULL ,
`request_url` MEDIUMTEXT NOT NULL ,
PRIMARY KEY(`idsite`, `idfailure`)
- ) ENGINE=$engine DEFAULT CHARSET=$charset
+ ) $tableOptions
",
'locks' => "CREATE TABLE `{$prefixTables}locks` (
`key` VARCHAR(" . Lock::MAX_KEY_LEN . ") NOT NULL,
`value` VARCHAR(255) NULL DEFAULT NULL,
`expiry_time` BIGINT UNSIGNED DEFAULT 9999999999,
PRIMARY KEY (`key`)
- ) ENGINE=$engine DEFAULT CHARSET=$charset
+ ) $tableOptions
",
'changes' => "CREATE TABLE `{$prefixTables}changes` (
`idchange` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
@@ -381,7 +379,7 @@ public function getTablesCreateSql()
`link` VARCHAR(255) NULL,
PRIMARY KEY(`idchange`),
UNIQUE KEY unique_plugin_version_title (`plugin_name`, `version`, `title`(100))
- ) ENGINE=$engine DEFAULT CHARSET=$charset
+ ) $tableOptions
",
);
@@ -522,10 +520,10 @@ public function createDatabase($dbName = null)
$dbName = $this->getDbName();
}
+ $createOptions = $this->getDatabaseCreateOptions();
$dbName = str_replace('`', '', $dbName);
- $charset = DbHelper::getDefaultCharset();
- Db::exec("CREATE DATABASE IF NOT EXISTS `" . $dbName . "` DEFAULT CHARACTER SET " . $charset);
+ Db::exec("CREATE DATABASE IF NOT EXISTS `$dbName` $createOptions");
}
/**
@@ -538,16 +536,11 @@ public function createDatabase($dbName = null)
*/
public function createTable($nameWithoutPrefix, $createDefinition)
{
- $dbSettings = new Db\Settings();
- $charset = $dbSettings->getUsedCharset();
-
$statement = sprintf(
- "CREATE TABLE IF NOT EXISTS `%s` ( %s ) ENGINE=%s DEFAULT CHARSET=%s %s;",
+ "CREATE TABLE IF NOT EXISTS `%s` ( %s ) %s;",
Common::prefixTable($nameWithoutPrefix),
$createDefinition,
- $this->getTableEngine(),
- $charset,
- $dbSettings->getRowFormat()
+ $this->getTableCreateOptions()
);
try {
@@ -675,21 +668,89 @@ public function supportsComplexColumnUpdates(): bool
return true;
}
+ /**
+ * Returns the default collation for a charset.
+ *
+ * @param string $charset
+ *
+ * @return string
+ * @throws Exception
+ */
+ public function getDefaultCollationForCharset(string $charset): string
+ {
+ $result = $this->getDb()->fetchRow(
+ 'SHOW COLLATION WHERE `Default` = "Yes" AND `Charset` = ?',
+ [$charset]
+ );
+
+ if (!isset($result['Collation'])) {
+ throw new Exception(sprintf(
+ 'Failed to detect default collation for character set "%s"',
+ $charset
+ ));
+ }
+
+ return $result['Collation'];
+ }
+
public function getDefaultPort(): int
{
return 3306;
}
- private function getTablePrefix()
+ public function getTableCreateOptions(): string
{
- return $this->getDbSettings()->getTablePrefix();
+ $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";
+ }
+
+ return $options;
+ }
+
+ protected function getDatabaseCreateOptions(): string
+ {
+ $charset = DbHelper::getDefaultCharset();
+ $collation = $this->getDefaultCollationForCharset($charset);
+
+ return "DEFAULT CHARACTER SET $charset COLLATE $collation";
}
- private function getTableEngine()
+ protected function getTableEngine()
{
return $this->getDbSettings()->getEngine();
}
+ protected function getTableRowFormat(): string
+ {
+ return $this->getDbSettings()->getRowFormat();
+ }
+
+ protected function getUsedCharset(): string
+ {
+ return $this->getDbSettings()->getUsedCharset();
+ }
+
+ protected function getUsedCollation(): string
+ {
+ return $this->getDbSettings()->getUsedCollation();
+ }
+
+ private function getTablePrefix()
+ {
+ return $this->getDbSettings()->getTablePrefix();
+ }
+
private function getDb()
{
return Db::get();
diff --git a/core/Db/Schema/Tidb.php b/core/Db/Schema/Tidb.php
index 6a1f1e29a27..6d1ba32270e 100644
--- a/core/Db/Schema/Tidb.php
+++ b/core/Db/Schema/Tidb.php
@@ -25,8 +25,44 @@ public function supportsComplexColumnUpdates(): bool
return false;
}
+ public function getDefaultCollationForCharset(string $charset): string
+ {
+ $collation = parent::getDefaultCollationForCharset($charset);
+
+ if ('utf8mb4' === $charset && 'utf8mb4_bin' === $collation) {
+ // replace the TiDB default "utf8mb4_bin" with a better default
+ return 'utf8mb4_0900_ai_ci';
+ }
+
+ return $collation;
+ }
+
public function getDefaultPort(): int
{
return 4000;
}
+
+ 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 ('' !== $collation) {
+ $options .= " COLLATE=$collation";
+ }
+
+ if ('' !== $rowFormat) {
+ $options .= " $rowFormat";
+ }
+
+ return $options;
+ }
}
diff --git a/core/Db/SchemaInterface.php b/core/Db/SchemaInterface.php
index 6405c1260d2..cdbf194c0f4 100644
--- a/core/Db/SchemaInterface.php
+++ b/core/Db/SchemaInterface.php
@@ -125,10 +125,26 @@ public function addMaxExecutionTimeHintToQuery(string $sql, float $limit): strin
*/
public function supportsComplexColumnUpdates(): bool;
+ /**
+ * Returns the default collation for a charset used by this database engine.
+ *
+ * @param string $charset
+ *
+ * @return string
+ */
+ public function getDefaultCollationForCharset(string $charset): string;
+
/**
* Return the default port used by this database engine
*
* @return int
*/
public function getDefaultPort(): int;
+
+ /**
+ * Return the table options to use for a CREATE TABLE statement.
+ *
+ * @return string
+ */
+ public function getTableCreateOptions(): string;
}
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/core/DbHelper.php b/core/DbHelper.php
index 25aecfa99c6..fad8a0af1a5 100644
--- a/core/DbHelper.php
+++ b/core/DbHelper.php
@@ -210,7 +210,7 @@ public static function tableHasIndex($table, $indexName)
* @return string
* @throws Tracker\Db\DbException
*/
- public static function getDefaultCharset()
+ public static function getDefaultCharset(): string
{
$result = Db::get()->fetchRow("SHOW CHARACTER SET LIKE 'utf8mb4'");
@@ -233,6 +233,19 @@ public static function getDefaultCharset()
return 'utf8mb4';
}
+ /**
+ * Returns the default collation for a charset.
+ *
+ * @param string $charset
+ *
+ * @return string
+ * @throws Exception
+ */
+ public static function getDefaultCollationForCharset(string $charset): string
+ {
+ return Schema::getInstance()->getDefaultCollationForCharset($charset);
+ }
+
/**
* Returns sql queries to convert all installed tables to utf8mb4
*
diff --git a/core/Tracker/Db/Mysqli.php b/core/Tracker/Db/Mysqli.php
index b5c769d9864..05ac1843f36 100644
--- a/core/Tracker/Db/Mysqli.php
+++ b/core/Tracker/Db/Mysqli.php
@@ -26,6 +26,7 @@ class Mysqli extends Db
protected $username;
protected $password;
protected $charset;
+ protected $collation;
protected $activeTransaction = false;
protected $enable_ssl;
@@ -57,11 +58,12 @@ public function __construct($dbInfo, $driverName = 'mysql')
$this->port = (int)$dbInfo['port'];
$this->socket = null;
}
+
$this->dbname = $dbInfo['dbname'];
$this->username = $dbInfo['username'];
$this->password = $dbInfo['password'];
- $this->charset = isset($dbInfo['charset']) ? $dbInfo['charset'] : null;
-
+ $this->charset = $dbInfo['charset'] ?? null;
+ $this->collation = $dbInfo['collation'] ?? null;
if (!empty($dbInfo['enable_ssl'])) {
$this->enable_ssl = $dbInfo['enable_ssl'];
@@ -133,8 +135,17 @@ public function connect()
throw new DbException("Connect failed: " . mysqli_connect_error());
}
- if ($this->charset && !mysqli_set_charset($this->connection, $this->charset)) {
- throw new DbException("Set Charset failed: " . mysqli_error($this->connection));
+ if ($this->charset && $this->collation) {
+ // mysqli_set_charset does not support setting a collation
+ $query = "SET NAMES '" . $this->charset . "' COLLATE '" . $this->collation . "'";
+
+ if (!mysqli_query($this->connection, $query)) {
+ throw new DbException("Set charset/connection collation failed: " . mysqli_error($this->connection));
+ }
+ } elseif ($this->charset) {
+ if (!mysqli_set_charset($this->connection, $this->charset)) {
+ throw new DbException("Set Charset failed: " . mysqli_error($this->connection));
+ }
}
$this->password = '';
diff --git a/core/Tracker/Db/Pdo/Mysql.php b/core/Tracker/Db/Pdo/Mysql.php
index 3d9a75f2a70..d4e0714c791 100644
--- a/core/Tracker/Db/Pdo/Mysql.php
+++ b/core/Tracker/Db/Pdo/Mysql.php
@@ -26,13 +26,33 @@ class Mysql extends Db
* @var PDO
*/
protected $connection = null;
+
+ /**
+ * @var string
+ */
protected $dsn;
+
+ /**
+ * @var string
+ */
private $username;
+
+ /**
+ * @var string
+ */
private $password;
+
+ /**
+ * @var string|null
+ */
protected $charset;
- protected $mysqlOptions = array();
+ /**
+ * @var string|null
+ */
+ private $collation;
+ protected $mysqlOptions = [];
protected $activeTransaction = false;
@@ -58,8 +78,11 @@ public function __construct($dbInfo, $driverName = 'mysql')
if (isset($dbInfo['charset'])) {
$this->charset = $dbInfo['charset'];
$this->dsn .= ';charset=' . $this->charset;
- }
+ if (!empty($dbInfo['collation'])) {
+ $this->collation = $dbInfo['collation'];
+ }
+ }
if (isset($dbInfo['enable_ssl']) && $dbInfo['enable_ssl']) {
if (!empty($dbInfo['ssl_key'])) {
@@ -409,6 +432,11 @@ private function establishConnection(): void
*/
if (!empty($this->charset)) {
$sql = "SET NAMES '" . $this->charset . "'";
+
+ if (!empty($this->collation)) {
+ $sql .= " COLLATE '" . $this->collation . "'";
+ }
+
$this->connection->exec($sql);
}
}
diff --git a/core/Updater/Migration/Db/CreateTable.php b/core/Updater/Migration/Db/CreateTable.php
index 10f211352fe..3d39c4c3919 100644
--- a/core/Updater/Migration/Db/CreateTable.php
+++ b/core/Updater/Migration/Db/CreateTable.php
@@ -9,7 +9,7 @@
namespace Piwik\Updater\Migration\Db;
-use Piwik\Db;
+use Piwik\Db\Schema;
/**
* @see Factory::createTable()
@@ -19,12 +19,11 @@ class CreateTable extends Sql
{
/**
* Constructor.
- * @param Db\Settings $dbSettings
* @param string $table Prefixed table name
* @param string|string[] $columnNames array(columnName => columnValue)
* @param string|string[] $primaryKey one or multiple columns that define the primary key
*/
- public function __construct(Db\Settings $dbSettings, $table, $columnNames, $primaryKey)
+ public function __construct($table, $columnNames, $primaryKey)
{
$columns = array();
foreach ($columnNames as $column => $type) {
@@ -35,15 +34,12 @@ public function __construct(Db\Settings $dbSettings, $table, $columnNames, $prim
$columns[] = sprintf('PRIMARY KEY ( `%s` )', implode('`, `', $primaryKey));
}
-
- $sql = rtrim(sprintf(
- 'CREATE TABLE `%s` (%s) ENGINE=%s DEFAULT CHARSET=%s %s',
+ $sql = sprintf(
+ 'CREATE TABLE `%s` (%s) %s',
$table,
implode(', ', $columns),
- $dbSettings->getEngine(),
- $dbSettings->getUsedCharset(),
- $dbSettings->getRowFormat()
- ));
+ Schema::getInstance()->getTableCreateOptions()
+ );
parent::__construct($sql, static::ERROR_CODE_TABLE_EXISTS);
}
diff --git a/core/Updates/5.1.2-rc1.php b/core/Updates/5.1.2-rc1.php
new file mode 100644
index 00000000000..406cd87e07e
--- /dev/null
+++ b/core/Updates/5.1.2-rc1.php
@@ -0,0 +1,108 @@
+migration = $factory;
+ }
+
+ public function getMigrations(Updater $updater)
+ {
+ $migrations = [];
+
+ $config = Config::getInstance();
+ $dbConfig = $config->database;
+
+ // only run migration if config is not set
+ if (empty($dbConfig['collation'])) {
+ $collation = $this->detectCollationForMigration();
+
+ if (null !== $collation) {
+ $migrations[] = $this->migration->config->set(
+ 'database',
+ 'collation',
+ $collation
+ );
+ }
+ }
+
+ return $migrations;
+ }
+
+ public function doUpdate(Updater $updater)
+ {
+ $updater->executeMigrations(__FILE__, $this->getMigrations($updater));
+ }
+
+ private function detectCollationForMigration(): ?string
+ {
+ try {
+ $db = Db::get();
+ $userTable = Common::prefixTable('user');
+ $userTableStatus = $db->fetchRow('SHOW TABLE STATUS WHERE Name = ?', [$userTable]);
+
+ if (empty($userTableStatus['Collation'])) {
+ // if there is no user table, or no collation for it, abort detection
+ // this table should always exist and something must be wrong in this case
+ return null;
+ }
+
+ $userTableCollation = $userTableStatus['Collation'];
+ $connectionCollation = $db->fetchOne('SELECT @@collation_connection');
+
+ if ($userTableCollation === $connectionCollation) {
+ // if the connection is matching the user table
+ // we should be safe to assume we have already found a config value
+ return $userTableCollation;
+ }
+
+ $archiveTables = ArchiveTableCreator::getTablesArchivesInstalled(ArchiveTableCreator::NUMERIC_TABLE);
+
+ if (0 === count($archiveTables)) {
+ // skip if there is no archive table (yet)
+ return null;
+ }
+
+ // sort tables so we have them in order of their date
+ rsort($archiveTables);
+
+ $archiveTableStatus = $db->fetchRow('SHOW TABLE STATUS WHERE Name = ?', [$archiveTables[0]]);
+
+ if (
+ !empty($archiveTableStatus['Collation'])
+ && $archiveTableStatus['Collation'] === $userTableCollation
+ ) {
+ // the most recent numeric archive table is matching the collation
+ // of the users table, should be a good config value to choose
+ return $userTableCollation;
+ }
+ } catch (\Exception $e) {
+ // rely on the system check if detection failed
+ }
+
+ return null;
+ }
+}
diff --git a/core/Version.php b/core/Version.php
index d3f2b6707af..19a8f4346d7 100644
--- a/core/Version.php
+++ b/core/Version.php
@@ -22,7 +22,7 @@ final class Version
* The current Matomo version.
* @var string
*/
- public const VERSION = '5.1.1-rc1';
+ public const VERSION = '5.1.2-rc1';
public const MAJOR_VERSION = 5;
diff --git a/libs/Zend/Db/Adapter/Mysqli.php b/libs/Zend/Db/Adapter/Mysqli.php
index 2dcdd67813f..50631bd4de0 100644
--- a/libs/Zend/Db/Adapter/Mysqli.php
+++ b/libs/Zend/Db/Adapter/Mysqli.php
@@ -375,7 +375,12 @@ protected function _connect()
throw new Zend_Db_Adapter_Mysqli_Exception(mysqli_connect_error());
}
- if (!empty($this->_config['charset'])) {
+ if (!empty($this->_config['charset']) && !empty($this->_config['collation'])) {
+ // mysqli_set_charset does not support setting a collation
+ $query = "SET NAMES '" . $this->_config['charset'] . "' COLLATE '" . $this->_config['collation'] . "'";
+
+ mysqli_query($this->_connection, $query);
+ } elseif (!empty($this->_config['charset'])) {
mysqli_set_charset($this->_connection, $this->_config['charset']);
}
}
diff --git a/libs/Zend/Db/Adapter/Pdo/Mysql.php b/libs/Zend/Db/Adapter/Pdo/Mysql.php
index 363196334fc..ff064e894e2 100644
--- a/libs/Zend/Db/Adapter/Pdo/Mysql.php
+++ b/libs/Zend/Db/Adapter/Pdo/Mysql.php
@@ -103,6 +103,11 @@ protected function _connect()
if (!empty($this->_config['charset'])) {
$initCommand = "SET NAMES '" . $this->_config['charset'] . "'";
+
+ if (!empty($this->_config['collation'])) {
+ $initCommand .= " COLLATE '" . $this->_config['collation'] . "'";
+ }
+
$this->_config['driver_options'][1002] = $initCommand; // 1002 = PDO::MYSQL_ATTR_INIT_COMMAND
}
diff --git a/plugins/Diagnostics/Diagnostic/DatabaseAbilitiesCheck.php b/plugins/Diagnostics/Diagnostic/DatabaseAbilitiesCheck.php
index a8ac6e7f640..a553c4dbf1b 100644
--- a/plugins/Diagnostics/Diagnostic/DatabaseAbilitiesCheck.php
+++ b/plugins/Diagnostics/Diagnostic/DatabaseAbilitiesCheck.php
@@ -42,6 +42,7 @@ public function execute()
$result = new DiagnosticResult($this->translator->translate('Installation_DatabaseAbilities'));
$result->addItem($this->checkUtf8mb4Charset());
+ $result->addItem($this->checkCollation());
if (Config::getInstance()->General['enable_load_data_infile']) {
$result->addItem($this->checkLoadDataInfile());
@@ -92,6 +93,29 @@ protected function checkUtf8mb4Charset()
);
}
+ protected function checkCollation(): DiagnosticResultItem
+ {
+ $dbSettings = new Db\Settings();
+ $collation = $dbSettings->getUsedCollation();
+
+ if ('' !== $collation) {
+ return new DiagnosticResultItem(DiagnosticResult::STATUS_OK, 'Connection collation');
+ }
+
+ $collationConnection = Db::get()->fetchOne('SELECT @@collation_connection');
+ $collationCharset = DbHelper::getDefaultCollationForCharset($dbSettings->getUsedCharset());
+
+ return new DiagnosticResultItem(
+ DiagnosticResult::STATUS_WARNING,
+ sprintf(
+ 'Connection collation
%s
%s
%s
',
+ $this->translator->translate('Diagnostics_DatabaseCollationNotConfigured'),
+ $this->translator->translate('Diagnostics_DatabaseCollationConnection', [$collationConnection]),
+ $this->translator->translate('Diagnostics_DatabaseCollationCharset', [$collationCharset])
+ )
+ );
+ }
+
protected function checkLoadDataInfile()
{
$optionTable = Common::prefixTable('option');
diff --git a/plugins/Diagnostics/Diagnostic/DatabaseInformational.php b/plugins/Diagnostics/Diagnostic/DatabaseInformational.php
index 1d2dfc81032..1dd4eb6c522 100644
--- a/plugins/Diagnostics/Diagnostic/DatabaseInformational.php
+++ b/plugins/Diagnostics/Diagnostic/DatabaseInformational.php
@@ -16,7 +16,7 @@
use Piwik\Translation\Translator;
/**
- * Informatation about the database.
+ * Information about the database.
*/
class DatabaseInformational implements Diagnostic
{
@@ -38,6 +38,7 @@ public function execute()
$dbConfig = Config::getInstance()->database;
$results[] = DiagnosticResult::informationalResult('DB Prefix', $dbConfig['tables_prefix']);
$results[] = DiagnosticResult::informationalResult('DB Charset', $dbConfig['charset']);
+ $results[] = DiagnosticResult::informationalResult('DB Collation', $dbConfig['collation']);
$results[] = DiagnosticResult::informationalResult('DB Adapter', $dbConfig['adapter']);
$results[] = DiagnosticResult::informationalResult('MySQL Version', $this->getServerVersion());
$results[] = DiagnosticResult::informationalResult('Num Tables', $this->getNumMatomoTables());
diff --git a/plugins/Diagnostics/lang/en.json b/plugins/Diagnostics/lang/en.json
index ee0a651ef8c..70855e531a4 100644
--- a/plugins/Diagnostics/lang/en.json
+++ b/plugins/Diagnostics/lang/en.json
@@ -12,6 +12,9 @@
"Sections": "Sections",
"BrowserAndAutoArchivingEnabledLabel": "Browser and Auto-archiving enabled",
"BrowserAndAutoArchivingEnabledComment": "It looks like both browser and auto archiving are enabled. Auto archiving last started %3$s ago. If %1$sauto archiving%2$s is enabled, you should disable browser archiving in \"General Settings\".",
+ "DatabaseCollationNotConfigured": "You database connection is configured without an explicit collation. Please update [database] collation = ''
in the \"config/config.ini.php\" file with the collation to be used, to ensure all database features work as expected.",
+ "DatabaseCollationConnection": "Your currently used connection collation is: %1$s
",
+ "DatabaseCollationCharset": "The default collation for your configured charset is: %1$s
",
"DatabaseReaderConnection": "Database Reader Connection",
"DatabaseUtf8Requirement": "This is required to be able to store 4-byte UTF8 characters. Unless utf8mb4 is available special characters, such as emojis, less common characters of asian languages, various historic scripts or mathematical symbols will be replaced with %1$s. You can read more details about this topic in %2$sthis FAQ%3$s.",
"DatabaseUtf8mb4CharsetRecommended": "Your database doesn't support utf8mb4 charset yet.",
diff --git a/plugins/Installation/Controller.php b/plugins/Installation/Controller.php
index d7d40e5f277..151494ccb91 100644
--- a/plugins/Installation/Controller.php
+++ b/plugins/Installation/Controller.php
@@ -609,6 +609,7 @@ private function createConfigFile($dbInfos)
$config->database = $dbInfos;
$config->database['charset'] = DbHelper::getDefaultCharset();
+ $config->database['collation'] = DbHelper::getDefaultCollationForCharset($config->database['charset']);
$config->forceSave();
diff --git a/tests/PHPUnit/Integration/Db/Schema/MariadbTest.php b/tests/PHPUnit/Integration/Db/Schema/MariadbTest.php
new file mode 100644
index 00000000000..5841299adc8
--- /dev/null
+++ b/tests/PHPUnit/Integration/Db/Schema/MariadbTest.php
@@ -0,0 +1,58 @@
+ $value) {
+ DatabaseConfig::setConfigValue($name, $value);
+ }
+
+ $schema = Db\Schema::getInstance();
+
+ self::assertSame($expected, $schema->getTableCreateOptions());
+ }
+
+ public function getTableCreateOptionsTestData(): iterable
+ {
+ yield 'default charset, empty collation' => [
+ ['collation' => ''],
+ 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC'
+ ];
+
+ yield 'override charset, empty collation' => [
+ ['charset' => 'utf8mb3', 'collation' => ''],
+ 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb3'
+ ];
+
+ yield 'default charset, override collation' => [
+ ['collation' => 'utf8mb4_swedish_ci'],
+ 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_swedish_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
new file mode 100644
index 00000000000..b26cd0e3ddb
--- /dev/null
+++ b/tests/PHPUnit/Integration/Db/Schema/MysqlTest.php
@@ -0,0 +1,58 @@
+ $value) {
+ DatabaseConfig::setConfigValue($name, $value);
+ }
+
+ $schema = Db\Schema::getInstance();
+
+ self::assertSame($expected, $schema->getTableCreateOptions());
+ }
+
+ public function getTableCreateOptionsTestData(): iterable
+ {
+ yield 'default charset, empty collation' => [
+ ['collation' => ''],
+ 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC'
+ ];
+
+ yield 'override charset, empty collation' => [
+ ['charset' => 'utf8mb3', 'collation' => ''],
+ 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb3'
+ ];
+
+ yield 'default charset, override collation' => [
+ ['collation' => 'utf8mb4_swedish_ci'],
+ 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_swedish_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
new file mode 100644
index 00000000000..a8d055d4fd1
--- /dev/null
+++ b/tests/PHPUnit/Integration/Db/Schema/TidbTest.php
@@ -0,0 +1,72 @@
+getDefaultCollationForCharset('utf8mb4')
+ );
+ }
+
+ /**
+ * @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 'default charset, empty collation' => [
+ ['collation' => ''],
+ 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC'
+ ];
+
+ yield 'override charset, empty collation' => [
+ ['charset' => 'utf8mb3', 'collation' => ''],
+ 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb3'
+ ];
+
+ yield 'default charset, override collation' => [
+ ['collation' => 'utf8mb4_swedish_ci'],
+ 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_swedish_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/DbHelperTest.php b/tests/PHPUnit/Integration/DbHelperTest.php
index d3f88c76a53..de20af974db 100644
--- a/tests/PHPUnit/Integration/DbHelperTest.php
+++ b/tests/PHPUnit/Integration/DbHelperTest.php
@@ -119,6 +119,25 @@ public function testAddOriginHintToQuery()
self::assertEquals($expected, $result);
}
+ public function testGetDefaultCollationForCharset(): void
+ {
+ $charset = 'utf8mb4';
+ $collation = DbHelper::getDefaultCollationForCharset($charset);
+ $expectedPrefix = $charset . '_';
+
+ // exact collation depends on the database used
+ // but should always start with the charset
+ self::assertStringStartsWith($expectedPrefix, $collation);
+ }
+
+ public function testGetDefaultCollationForCharsetThrowsForInvalidCharset(): void
+ {
+ self::expectException(\Exception::class);
+ self::expectExceptionMessage('Failed to detect default collation for character set "invalid"');
+
+ DbHelper::getDefaultCollationForCharset('invalid');
+ }
+
private function assertDbExists($dbName)
{
$dbs = Db::fetchAll("SHOW DATABASES");
diff --git a/tests/PHPUnit/Integration/DbTest.php b/tests/PHPUnit/Integration/DbTest.php
index 600482b1f84..c6c3b8a7616 100644
--- a/tests/PHPUnit/Integration/DbTest.php
+++ b/tests/PHPUnit/Integration/DbTest.php
@@ -266,6 +266,43 @@ public function testGetRowCount($adapter, $expectedClass)
$this->assertEquals(1, $db->rowCount($result));
}
+ /**
+ * @dataProvider getDbAdapter
+ */
+ public function testConnectionCollationDefault(string $adapter, string $expectedClass): void
+ {
+ Db::destroyDatabaseObject();
+
+ $config = Config::getInstance();
+ $config->database['adapter'] = $adapter;
+ $config->database['collation'] = null;
+
+ $db = Db::get();
+ self::assertInstanceOf($expectedClass, $db);
+
+ // exact value depends on database used
+ $currentCollation = $db->fetchOne('SELECT @@collation_connection');
+ self::assertStringStartsWith('utf8', $currentCollation);
+ }
+
+ /**
+ * @dataProvider getDbAdapter
+ */
+ public function testConnectionCollationSetInConfig(string $adapter, string $expectedClass): void
+ {
+ Db::destroyDatabaseObject();
+
+ $config = Config::getInstance();
+ $config->database['adapter'] = $adapter;
+ $config->database['collation'] = $config->database['charset'] . '_swedish_ci';
+
+ $db = Db::get();
+ self::assertInstanceOf($expectedClass, $db);
+
+ $currentCollation = $db->fetchOne('SELECT @@collation_connection');
+ self::assertSame($config->database['collation'], $currentCollation);
+ }
+
public function getDbAdapter()
{
return array(
diff --git a/tests/PHPUnit/Integration/Tracker/Db/MysqliTest.php b/tests/PHPUnit/Integration/Tracker/Db/MysqliTest.php
new file mode 100644
index 00000000000..eed5f2baa68
--- /dev/null
+++ b/tests/PHPUnit/Integration/Tracker/Db/MysqliTest.php
@@ -0,0 +1,54 @@
+database['adapter'] = 'MYSQLI';
+ }
+
+ public function testConnectionThrowsOnInvalidCharset(): void
+ {
+ self::expectException(Tracker\Db\DbException::class);
+ self::expectExceptionMessageMatches('/Set Charset failed/');
+
+ $config = Config::getInstance();
+ $config->database['collation'] = null;
+ $config->database['charset'] = 'something really invalid';
+
+ Tracker\Db::connectPiwikTrackerDb();
+ }
+
+ public function testConnectionThrowsOnInvalidConnectionCollation(): void
+ {
+ self::expectException(Tracker\Db\DbException::class);
+ self::expectExceptionMessageMatches('/Set charset\/connection collation failed/');
+
+ $config = Config::getInstance();
+ $config->database['collation'] = 'something really invalid';
+
+ Tracker\Db::connectPiwikTrackerDb();
+ }
+}
diff --git a/tests/PHPUnit/Integration/Tracker/DbTest.php b/tests/PHPUnit/Integration/Tracker/DbTest.php
index df630f24d98..9db9837bb9a 100644
--- a/tests/PHPUnit/Integration/Tracker/DbTest.php
+++ b/tests/PHPUnit/Integration/Tracker/DbTest.php
@@ -192,6 +192,27 @@ public function testFetchAllNoMatch()
$this->assertEquals(array(), $val);
}
+ public function testConnectionCollationDefault(): void
+ {
+ $config = Config::getInstance();
+ $config->database['collation'] = null;
+ $db = Tracker\Db::connectPiwikTrackerDb();
+
+ // exact value depends on database used
+ $currentCollation = $db->fetchOne('SELECT @@collation_connection');
+ self::assertStringStartsWith('utf8', $currentCollation);
+ }
+
+ public function testConnectionCollationSetInConfig(): void
+ {
+ $config = Config::getInstance();
+ $config->database['collation'] = $config->database['charset'] . '_swedish_ci';
+ $db = Tracker\Db::connectPiwikTrackerDb();
+
+ $currentCollation = $db->fetchOne('SELECT @@collation_connection');
+ self::assertSame($config->database['collation'], $currentCollation);
+ }
+
private function insertRowId($value = '1')
{
$db = Tracker::getDatabase();
diff --git a/tests/PHPUnit/Integration/Updater/Migration/Db/FactoryTest.php b/tests/PHPUnit/Integration/Updater/Migration/Db/FactoryTest.php
index 4a210e384b1..583fbad0d27 100644
--- a/tests/PHPUnit/Integration/Updater/Migration/Db/FactoryTest.php
+++ b/tests/PHPUnit/Integration/Updater/Migration/Db/FactoryTest.php
@@ -10,6 +10,7 @@
namespace Piwik\Tests\Integration\Updater\Migration\Db;
use Piwik\Common;
+use Piwik\Db\Schema;
use Piwik\Tests\Framework\TestCase\IntegrationTestCase;
use Piwik\Updater\Migration\Db\AddColumn;
use Piwik\Updater\Migration\Db\AddColumns;
@@ -94,7 +95,9 @@ public function testCreateTableForwardsParameters()
$migration = $this->createTable();
$table = $this->testTablePrefixed;
- $this->assertSame("CREATE TABLE `$table` (`column` INT(10) DEFAULT 0, `column2` VARCHAR(255)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;", '' . $migration);
+ $createOptions = Schema::getInstance()->getTableCreateOptions();
+ self::assertStringContainsString('ROW_FORMAT=DYNAMIC', $createOptions);
+ $this->assertSame("CREATE TABLE `$table` (`column` INT(10) DEFAULT 0, `column2` VARCHAR(255)) $createOptions;", '' . $migration);
}
@@ -103,7 +106,9 @@ public function testCreateTableWithPrimaryKey()
$migration = $this->createTable('column2');
$table = $this->testTablePrefixed;
- $this->assertSame("CREATE TABLE `$table` (`column` INT(10) DEFAULT 0, `column2` VARCHAR(255), PRIMARY KEY ( `column2` )) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;", '' . $migration);
+ $createOptions = Schema::getInstance()->getTableCreateOptions();
+ self::assertStringContainsString('ROW_FORMAT=DYNAMIC', $createOptions);
+ $this->assertSame("CREATE TABLE `$table` (`column` INT(10) DEFAULT 0, `column2` VARCHAR(255), PRIMARY KEY ( `column2` )) $createOptions;", '' . $migration);
}
public function testDropTableReturnsDropTableInstance()
diff --git a/tests/resources/OmniFixture-dump.sql b/tests/resources/OmniFixture-dump.sql
index e0bcb6846e9..6e28bae6bb0 100644
--- a/tests/resources/OmniFixture-dump.sql
+++ b/tests/resources/OmniFixture-dump.sql
@@ -29,7 +29,7 @@ CREATE TABLE `access` (
`access` varchar(50) DEFAULT NULL,
PRIMARY KEY (`idaccess`),
KEY `index_loginidsite` (`login`,`idsite`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -161,7 +161,7 @@ CREATE TABLE `archive_invalidations` (
`report` varchar(255) DEFAULT NULL,
PRIMARY KEY (`idinvalidation`),
KEY `index_idsite_dates_period_name` (`idsite`,`date1`,`period`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -187,7 +187,7 @@ CREATE TABLE `brute_force_log` (
`login` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id_brute_force_log`),
KEY `index_ip_address` (`ip_address`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -217,7 +217,7 @@ CREATE TABLE `changes` (
`link` varchar(255) DEFAULT NULL,
PRIMARY KEY (`idchange`),
UNIQUE KEY `unique_plugin_version_title` (`plugin_name`,`version`,`title`(100))
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -281,7 +281,7 @@ CREATE TABLE `goal` (
`deleted` tinyint(4) NOT NULL DEFAULT '0',
`event_value_as_revenue` tinyint(4) NOT NULL DEFAULT '0',
PRIMARY KEY (`idsite`,`idgoal`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -306,7 +306,7 @@ CREATE TABLE `locks` (
`value` varchar(255) DEFAULT NULL,
`expiry_time` bigint(20) unsigned DEFAULT '9999999999',
PRIMARY KEY (`key`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -333,7 +333,7 @@ CREATE TABLE `log_action` (
`url_prefix` tinyint(2) DEFAULT NULL,
PRIMARY KEY (`idaction`),
KEY `index_type_hash` (`type`,`hash`)
-) ENGINE=InnoDB AUTO_INCREMENT=2764 DEFAULT CHARSET=utf8mb4;
+) ENGINE=InnoDB AUTO_INCREMENT=2764 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -406,7 +406,7 @@ CREATE TABLE `log_conversion` (
PRIMARY KEY (`idvisit`,`idgoal`,`buster`),
UNIQUE KEY `unique_idsite_idorder` (`idsite`,`idorder`),
KEY `index_idsite_datetime` (`idsite`,`server_time`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -444,7 +444,7 @@ CREATE TABLE `log_conversion_item` (
`deleted` tinyint(1) unsigned NOT NULL,
PRIMARY KEY (`idvisit`,`idorder`,`idaction_sku`),
KEY `index_idsite_servertime` (`idsite`,`server_time`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -520,7 +520,7 @@ CREATE TABLE `log_link_visit_action` (
PRIMARY KEY (`idlink_va`),
KEY `index_idvisit` (`idvisit`),
KEY `index_idsite_servertime` (`idsite`,`server_time`)
-) ENGINE=InnoDB AUTO_INCREMENT=2833 DEFAULT CHARSET=utf8mb4;
+) ENGINE=InnoDB AUTO_INCREMENT=2833 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -547,7 +547,7 @@ CREATE TABLE `log_profiling` (
`idprofiling` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`idprofiling`),
UNIQUE KEY `query` (`query`(100))
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -641,7 +641,7 @@ CREATE TABLE `log_visit` (
KEY `index_idsite_config_datetime` (`idsite`,`config_id`,`visit_last_action_time`),
KEY `index_idsite_datetime` (`idsite`,`visit_last_action_time`),
KEY `index_idsite_idvisitor_time` (`idsite`,`idvisitor`,`visit_last_action_time`)
-) ENGINE=InnoDB AUTO_INCREMENT=500 DEFAULT CHARSET=utf8mb4;
+) ENGINE=InnoDB AUTO_INCREMENT=500 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -668,7 +668,7 @@ CREATE TABLE `logger_message` (
`level` varchar(16) DEFAULT NULL,
`message` text,
PRIMARY KEY (`idlogger_message`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -693,7 +693,7 @@ CREATE TABLE `option` (
`autoload` tinyint(4) NOT NULL DEFAULT '1',
PRIMARY KEY (`option_name`),
KEY `autoload` (`autoload`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -722,7 +722,7 @@ CREATE TABLE `plugin_setting` (
`idplugin_setting` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`idplugin_setting`),
KEY `plugin_name` (`plugin_name`,`user_login`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -904,7 +904,7 @@ CREATE TABLE `sequence` (
`name` varchar(120) NOT NULL,
`value` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`name`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -929,7 +929,7 @@ CREATE TABLE `session` (
`lifetime` int(11) DEFAULT NULL,
`data` mediumtext,
PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -969,7 +969,7 @@ CREATE TABLE `site` (
`keep_url_fragment` tinyint(4) NOT NULL DEFAULT '0',
`creator_login` varchar(100) DEFAULT NULL,
PRIMARY KEY (`idsite`)
-) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
+) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -998,7 +998,7 @@ CREATE TABLE `site_setting` (
`idsite_setting` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`idsite_setting`),
KEY `idsite` (`idsite`,`plugin_name`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -1021,7 +1021,7 @@ CREATE TABLE `site_url` (
`idsite` int(10) unsigned NOT NULL,
`url` varchar(190) NOT NULL,
PRIMARY KEY (`idsite`,`url`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -1257,7 +1257,7 @@ CREATE TABLE `tracking_failure` (
`date_first_occurred` datetime NOT NULL,
`request_url` mediumtext NOT NULL,
PRIMARY KEY (`idsite`,`idfailure`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -1281,7 +1281,7 @@ CREATE TABLE `twofactor_recovery_code` (
`login` varchar(100) NOT NULL,
`recovery_code` varchar(40) NOT NULL,
PRIMARY KEY (`idrecoverycode`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -1345,7 +1345,7 @@ CREATE TABLE `user` (
`ts_changes_shown` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`login`),
UNIQUE KEY `uniq_email` (`email`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -1428,7 +1428,7 @@ CREATE TABLE `user_token_auth` (
`secure_only` tinyint(2) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`idusertokenauth`),
UNIQUE KEY `uniq_password` (`password`)
-) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8mb4;
+) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
/*!40101 SET character_set_client = @saved_cs_client */;
--