diff --git a/lib/ddl/mysql_sql_generator.php b/lib/ddl/mysql_sql_generator.php index 1d90a88dc15..b981484ccaa 100644 --- a/lib/ddl/mysql_sql_generator.php +++ b/lib/ddl/mysql_sql_generator.php @@ -98,11 +98,18 @@ class mysql_sql_generator extends sql_generator { /** * Given one correct xmldb_table, returns the SQL statements - * to create it (inside one array) + * to create it (inside one array). + * + * @param xmldb_table $xmldb_table An xmldb_table instance. + * @return array An array of SQL statements, starting with the table creation SQL followed + * by any of its comments, indexes and sequence creation SQL statements. */ public function getCreateTableSQL($xmldb_table) { // First find out if want some special db engine. $engine = $this->mdb->get_dbengine(); + // Do we know collation? + $collation = $this->mdb->get_dbcollation(); + $sqlarr = parent::getCreateTableSQL($xmldb_table); // Let's inject the extra MySQL tweaks. @@ -111,6 +118,12 @@ class mysql_sql_generator extends sql_generator { if ($engine) { $sqlarr[$i] .= " ENGINE = $engine"; } + if ($collation) { + if (strpos($collation, 'utf8_') === 0) { + $sqlarr[$i] .= " DEFAULT CHARACTER SET utf8"; + } + $sqlarr[$i] .= " DEFAULT COLLATE = $collation"; + } } } @@ -119,12 +132,32 @@ class mysql_sql_generator extends sql_generator { /** * Given one correct xmldb_table, returns the SQL statements - * to create temporary table (inside one array) + * to create temporary table (inside one array). + * + * @param xmldb_table $xmldb_table The xmldb_table object instance. + * @return array of sql statements */ public function getCreateTempTableSQL($xmldb_table) { + // Do we know collation? + $collation = $this->mdb->get_dbcollation(); $this->temptables->add_temptable($xmldb_table->getName()); - $sqlarr = parent::getCreateTableSQL($xmldb_table); // we do not want the engine hack included in create table SQL - $sqlarr = preg_replace('/^CREATE TABLE (.*)/s', 'CREATE TEMPORARY TABLE $1', $sqlarr); + + $sqlarr = parent::getCreateTableSQL($xmldb_table); + + // Let's inject the extra MySQL tweaks. + foreach ($sqlarr as $i=>$sql) { + if (strpos($sql, 'CREATE TABLE ') === 0) { + // We do not want the engine hack included in create table SQL. + $sqlarr[$i] = preg_replace('/^CREATE TABLE (.*)/s', 'CREATE TEMPORARY TABLE $1', $sql); + if ($collation) { + if (strpos($collation, 'utf8_') === 0) { + $sqlarr[$i] .= " DEFAULT CHARACTER SET utf8"; + } + $sqlarr[$i] .= " DEFAULT COLLATE $collation"; + } + } + } + return $sqlarr; } @@ -195,6 +228,12 @@ class mysql_sql_generator extends sql_generator { $xmldb_length='255'; } $dbtype .= '(' . $xmldb_length . ')'; + if ($collation = $this->mdb->get_dbcollation()) { + if (strpos($collation, 'utf8_') === 0) { + $dbtype .= " CHARACTER SET utf8"; + } + $dbtype .= " COLLATE $collation"; + } break; case XMLDB_TYPE_TEXT: if (empty($xmldb_length)) { @@ -207,6 +246,12 @@ class mysql_sql_generator extends sql_generator { } else { $dbtype = 'LONGTEXT'; } + if ($collation = $this->mdb->get_dbcollation()) { + if (strpos($collation, 'utf8_') === 0) { + $dbtype .= " CHARACTER SET utf8"; + } + $dbtype .= " COLLATE $collation"; + } break; case XMLDB_TYPE_BINARY: if (empty($xmldb_length)) { diff --git a/lib/dml/mysqli_native_moodle_database.php b/lib/dml/mysqli_native_moodle_database.php index 2f86756d528..e5ca247c273 100644 --- a/lib/dml/mysqli_native_moodle_database.php +++ b/lib/dml/mysqli_native_moodle_database.php @@ -81,7 +81,13 @@ class mysqli_native_moodle_database extends moodle_database { throw new dml_connection_exception($dberr); } - $result = $conn->query("CREATE DATABASE $dbname DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci"); + if (isset($dboptions['dbcollation']) and strpos($dboptions['dbcollation'], 'utf8_') === 0) { + $collation = $dboptions['dbcollation']; + } else { + $collation = 'utf8_unicode_ci'; + } + + $result = $conn->query("CREATE DATABASE $dbname DEFAULT CHARACTER SET utf8 DEFAULT COLLATE ".$collation); $conn->close(); @@ -206,6 +212,72 @@ class mysqli_native_moodle_database extends moodle_database { return $engine; } + /** + * Returns the current MySQL db collation. + * + * This is an ugly workaround for MySQL default collation problems. + * + * @return string or null MySQL collation name + */ + public function get_dbcollation() { + if (isset($this->dboptions['dbcollation'])) { + return $this->dboptions['dbcollation']; + } + if ($this->external) { + return null; + } + + $collation = null; + + // Look for current collation of our config table (the first table that gets created), + // so that we create all tables with the same collation. + $sql = "SELECT collation_name + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_schema = DATABASE() AND table_name = '{$this->prefix}config' AND column_name = 'value'"; + $this->query_start($sql, NULL, SQL_QUERY_AUX); + $result = $this->mysqli->query($sql); + $this->query_end($result); + if ($rec = $result->fetch_assoc()) { + $collation = $rec['collation_name']; + } + $result->close(); + + if (!$collation) { + // Get the default database collation, but only if using UTF-8. + $sql = "SELECT @@collation_database"; + $this->query_start($sql, NULL, SQL_QUERY_AUX); + $result = $this->mysqli->query($sql); + $this->query_end($result); + if ($rec = $result->fetch_assoc()) { + if (strpos($rec['@@collation_database'], 'utf8_') === 0) { + $collation = $rec['@@collation_database']; + } + } + $result->close(); + } + + if (!$collation) { + // We want only utf8 compatible collations. + $collation = null; + $sql = "SHOW COLLATION WHERE Collation LIKE 'utf8\_%' AND Charset = 'utf8'"; + $this->query_start($sql, NULL, SQL_QUERY_AUX); + $result = $this->mysqli->query($sql); + $this->query_end($result); + while ($res = $result->fetch_assoc()) { + $collation = $res['Collation']; + if (strtoupper($res['Default']) === 'YES') { + $collation = $res['Collation']; + break; + } + } + $result->close(); + } + + // Cache the result to improve performance. + $this->dboptions['dbcollation'] = $collation; + return $collation; + } + /** * Returns localised database type name * Note: can be used before connect()