diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 470458185..3218e4e9d 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -111,15 +111,6 @@ define('LINKSET_EDITMODE_ADDREMOVE', 4); // The "linked" objects can be added/re */ abstract class AttributeDefinition { - /** - * SQL charset & collation declaration for text columns - * - * @see https://dev.mysql.com/doc/refman/5.7/en/charset-column.html - * @since 2.5 #1001 switch to utf8mb4 - */ - const SQL_TEXT_COLUMNS_CHARSET = ' CHARACTER SET '.DEFAULT_CHARACTER_SET.' COLLATE '.DEFAULT_COLLATION; - - public function GetType() { return Dict::S('Core:'.get_class($this)); @@ -1503,7 +1494,7 @@ class AttributeDBFieldVoid extends AttributeDefinition protected function GetSQLCol($bFullSpec = false) { return 'VARCHAR(255)' - .self::SQL_TEXT_COLUMNS_CHARSET + .CMDBSource::SQL_STRING_COLUMNS_CHARSET_DEFINITION .($bFullSpec ? $this->GetSQLColSpec() : ''); } protected function GetSQLColSpec() @@ -2118,7 +2109,7 @@ class AttributeString extends AttributeDBField protected function GetSQLCol($bFullSpec = false) { return 'VARCHAR(255)' - .self::SQL_TEXT_COLUMNS_CHARSET + .CMDBSource::SQL_STRING_COLUMNS_CHARSET_DEFINITION .($bFullSpec ? $this->GetSQLColSpec() : ''); } @@ -2510,7 +2501,7 @@ class AttributePassword extends AttributeString protected function GetSQLCol($bFullSpec = false) { return "VARCHAR(64)" - .self::SQL_TEXT_COLUMNS_CHARSET + .CMDBSource::SQL_STRING_COLUMNS_CHARSET_DEFINITION .($bFullSpec ? $this->GetSQLColSpec() : ''); } @@ -2641,7 +2632,7 @@ class AttributeText extends AttributeString protected function GetSQLCol($bFullSpec = false) { - return "TEXT".self::SQL_TEXT_COLUMNS_CHARSET; + return "TEXT".CMDBSource::SQL_STRING_COLUMNS_CHARSET_DEFINITION; } public function GetSQLColumns($bFullSpec = false) @@ -2651,7 +2642,7 @@ class AttributeText extends AttributeString if ($this->GetOptional('format', null) != null ) { // Add the extra column only if the property 'format' is specified for the attribute - $aColumns[$this->Get('sql').'_format'] = "ENUM('text','html')".self::SQL_TEXT_COLUMNS_CHARSET; + $aColumns[$this->Get('sql').'_format'] = "ENUM('text','html')".CMDBSource::SQL_STRING_COLUMNS_CHARSET_DEFINITION; if ($bFullSpec) { $aColumns[$this->Get('sql').'_format'].= " DEFAULT 'text'"; // default 'text' is for migrating old records @@ -2975,7 +2966,7 @@ class AttributeLongText extends AttributeText { protected function GetSQLCol($bFullSpec = false) { - return "LONGTEXT".self::SQL_TEXT_COLUMNS_CHARSET; + return "LONGTEXT".CMDBSource::SQL_STRING_COLUMNS_CHARSET_DEFINITION; } public function GetMaxSize() @@ -3155,7 +3146,7 @@ class AttributeCaseLog extends AttributeLongText { $aColumns = array(); $aColumns[$this->GetCode()] = 'LONGTEXT' // 2^32 (4 Gb) - .self::SQL_TEXT_COLUMNS_CHARSET; + .CMDBSource::SQL_STRING_COLUMNS_CHARSET_DEFINITION; $aColumns[$this->GetCode().'_index'] = 'BLOB'; return $aColumns; } @@ -3499,13 +3490,13 @@ class AttributeEnum extends AttributeString // make sure that this string will match the field type returned by the DB // (used to perform a comparison between the current DB format and the data model) return "ENUM(".implode(",", $aValues).")" - .self::SQL_TEXT_COLUMNS_CHARSET + .CMDBSource::SQL_STRING_COLUMNS_CHARSET_DEFINITION .($bFullSpec ? $this->GetSQLColSpec() : ''); } else { return "VARCHAR(255)" - .self::SQL_TEXT_COLUMNS_CHARSET + .CMDBSource::SQL_STRING_COLUMNS_CHARSET_DEFINITION .($bFullSpec ? " DEFAULT ''" : ""); // ENUM() is not an allowed syntax! } } @@ -5238,7 +5229,7 @@ class AttributeURL extends AttributeString protected function GetSQLCol($bFullSpec = false) { return "VARCHAR(2048)" - .self::SQL_TEXT_COLUMNS_CHARSET + .CMDBSource::SQL_STRING_COLUMNS_CHARSET_DEFINITION .($bFullSpec ? $this->GetSQLColSpec() : ''); } @@ -5413,8 +5404,8 @@ class AttributeBlob extends AttributeDefinition { $aColumns = array(); $aColumns[$this->GetCode().'_data'] = 'LONGBLOB'; // 2^32 (4 Gb) - $aColumns[$this->GetCode().'_mimetype'] = 'VARCHAR(255)'.self::SQL_TEXT_COLUMNS_CHARSET; - $aColumns[$this->GetCode().'_filename'] = 'VARCHAR(255)'.self::SQL_TEXT_COLUMNS_CHARSET; + $aColumns[$this->GetCode().'_mimetype'] = 'VARCHAR(255)'.CMDBSource::SQL_STRING_COLUMNS_CHARSET_DEFINITION; + $aColumns[$this->GetCode().'_filename'] = 'VARCHAR(255)'.CMDBSource::SQL_STRING_COLUMNS_CHARSET_DEFINITION; return $aColumns; } @@ -6551,7 +6542,7 @@ class AttributeOneWayPassword extends AttributeDefinition public function GetImportColumns() { $aColumns = array(); - $aColumns[$this->GetCode()] = 'TINYTEXT'.self::SQL_TEXT_COLUMNS_CHARSET; + $aColumns[$this->GetCode()] = 'TINYTEXT'.CMDBSource::SQL_STRING_COLUMNS_CHARSET_DEFINITION; return $aColumns; } @@ -6622,7 +6613,7 @@ class AttributeTable extends AttributeDBField protected function GetSQLCol($bFullSpec = false) { - return "LONGTEXT".self::SQL_TEXT_COLUMNS_CHARSET; + return "LONGTEXT".CMDBSource::SQL_STRING_COLUMNS_CHARSET_DEFINITION; } public function GetMaxSize() @@ -7025,7 +7016,7 @@ class AttributeRedundancySettings extends AttributeDBField protected function GetSQLCol($bFullSpec = false) { return "VARCHAR(20)" - .self::SQL_TEXT_COLUMNS_CHARSET + .CMDBSource::SQL_STRING_COLUMNS_CHARSET_DEFINITION .($bFullSpec ? $this->GetSQLColSpec() : ''); } diff --git a/core/cmdbsource.class.inc.php b/core/cmdbsource.class.inc.php index ac2423647..a7f8d5e00 100644 --- a/core/cmdbsource.class.inc.php +++ b/core/cmdbsource.class.inc.php @@ -108,6 +108,14 @@ class MySQLHasGoneAwayException extends MySQLException */ class CMDBSource { + /** + * SQL charset & collation declaration for text columns + * + * @see https://dev.mysql.com/doc/refman/5.7/en/charset-column.html + * @since 2.5 #1001 switch to utf8mb4 + */ + const SQL_STRING_COLUMNS_CHARSET_DEFINITION = ' CHARACTER SET '.DEFAULT_CHARACTER_SET.' COLLATE '.DEFAULT_COLLATION; + protected static $m_sDBHost; protected static $m_sDBUser; protected static $m_sDBPwd; @@ -906,11 +914,22 @@ class CMDBSource if (empty($aTableInfo)) return false; if (!array_key_exists($sField, $aTableInfo["Fields"])) return false; $aFieldData = $aTableInfo["Fields"][$sField]; + $sRet = $aFieldData["Type"]; + + $sColumnCharset = $aFieldData["Charset"]; + $sColumnCollation = $aFieldData["Collation"]; + if (!empty($sColumnCharset)) + { + $sRet .= ' CHARACTER SET '.$sColumnCharset; + $sRet .= ' COLLATE '.$sColumnCollation; + } + if ($aFieldData["Null"] == 'NO') { $sRet .= ' NOT NULL'; } + if (is_numeric($aFieldData["Default"])) { if (strtolower(substr($aFieldData["Type"], 0, 5)) == 'enum(') @@ -928,6 +947,7 @@ class CMDBSource { $sRet .= ' DEFAULT '.self::Quote($aFieldData["Default"]); } + return $sRet; } @@ -970,32 +990,34 @@ class CMDBSource private static function _TableInfoCacheInit($sTableName) { if (isset(self::$m_aTablesInfo[strtolower($sTableName)]) - && (self::$m_aTablesInfo[strtolower($sTableName)] != null)) return; - - try + && (self::$m_aTablesInfo[strtolower($sTableName)] != null)) { - // Check if the table exists - $aFields = self::QueryToArray("SHOW COLUMNS FROM `$sTableName`"); - // Note: without backticks, you get an error with some table names (e.g. "group") - foreach ($aFields as $aFieldData) - { - $sFieldName = $aFieldData["Field"]; - self::$m_aTablesInfo[strtolower($sTableName)]["Fields"][$sFieldName] = - array - ( - "Name"=>$aFieldData["Field"], - "Type"=>$aFieldData["Type"], - "Null"=>$aFieldData["Null"], - "Key"=>$aFieldData["Key"], - "Default"=>$aFieldData["Default"], - "Extra"=>$aFieldData["Extra"] - ); - } + return; } - catch(MySQLException $e) + + // Create array entry, if table does not exist / has no columns + self::$m_aTablesInfo[strtolower($sTableName)] = null; + + // Get table informations + // We were using SHOW COLUMNS FROM... but this don't return charset and collation info ! + // so since 2.5 and #1001 (switch to utf8mb4) we're using INFORMATION_SCHEMA ! + $aFields = self::QueryToArray('SELECT * FROM information_schema.`COLUMNS`' + .' WHERE table_schema = "'.self::$m_sDBName.'" AND table_name = "'.$sTableName.'";'); + foreach ($aFields as $aFieldData) { - // Table does not exist - self::$m_aTablesInfo[strtolower($sTableName)] = null; + $sFieldName = $aFieldData["COLUMN_NAME"]; + self::$m_aTablesInfo[strtolower($sTableName)]["Fields"][$sFieldName] = + array + ( + "Name" => $sFieldName, + "Type" => $aFieldData["COLUMN_TYPE"], + "Null" => $aFieldData["IS_NULLABLE"], + "Key" => $aFieldData["COLUMN_KEY"], + "Default" => $aFieldData["COLUMN_DEFAULT"], + "Extra" => $aFieldData["EXTRA"], + "Charset" => $aFieldData["CHARACTER_SET_NAME"], + "Collation" => $aFieldData["COLLATION_NAME"], + ); } if (!is_null(self::$m_aTablesInfo[strtolower($sTableName)])) @@ -1009,25 +1031,13 @@ class CMDBSource self::$m_aTablesInfo[strtolower($sTableName)]["Indexes"] = $aMyIndexes; } } - //public static function EnumTables() - //{ - // self::_TablesInfoCacheInit(); - // return array_keys(self::$m_aTablesInfo); - //} + public static function GetTableInfo($sTable) { self::_TableInfoCacheInit($sTable); // perform a case insensitive match because on Windows the table names become lowercase :-( - //foreach(self::$m_aTablesInfo as $sTableName => $aInfo) - //{ - // if (strtolower($sTableName) == strtolower($sTable)) - // { - // return $aInfo; - // } - //} return self::$m_aTablesInfo[strtolower($sTable)]; - //return null; } /** diff --git a/core/metamodel.class.php b/core/metamodel.class.php index b3aaaa90b..aa678bad2 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -4921,9 +4921,12 @@ abstract class MetaModel $aErrors = array(); $aSugFix = array(); + $sAlterDBMetaData = self::DBCheckCharsetAndCollation(); + // A new way of representing things to be done - quicker to execute ! $aCreateTable = array(); // array of