diff --git a/core/cmdbsource.class.inc.php b/core/cmdbsource.class.inc.php index d541b93ca..ec7371d1b 100644 --- a/core/cmdbsource.class.inc.php +++ b/core/cmdbsource.class.inc.php @@ -589,6 +589,24 @@ class CMDBSource return $value; } + /** + * MariaDB returns "'value'" for enum, while MySQL returns "value" (without the surrounding single quotes) + * + * @param string $sValue + * + * @return string without the surrounding quotes + * @since 2.7.0 N°2490 + */ + private static function RemoveSurroundingQuotes($sValue) + { + if (utils::StartsWith($sValue, '\'') && utils::EndsWith($sValue, '\'')) + { + $sValue = substr($sValue, 1, -1); + } + + return $sValue; + } + /** * @param string $sSQLQuery * @@ -1113,6 +1131,78 @@ class CMDBSource return false; } + /** + * There may have some differences between DB : for example in MySQL 5.7 we have "INT", while in MariaDB >= 10.2 you get "int DEFAULT 'NULL'" + * + * We still do a case sensitive comparison for enum values ! + * + * A better solution would be to generate SQL field definitions ({@link GetFieldSpec} method) based on the DB used... But for + * now (N°2490 / SF #1756 / PR #91) we did implement this simpler solution + * + * @param string $sItopGeneratedFieldType + * @param string $sDbFieldType + * + * @return bool true if same type and options (case sensitive comparison only for type options), false otherwise + * @since 2.7.0 N°2490 + */ + public static function IsSameFieldTypes($sItopGeneratedFieldType, $sDbFieldType) + { + list($sItopFieldDataType, $sItopFieldTypeOptions, $sItopFieldOtherOptions) = static::GetFieldDataTypeAndOptions($sItopGeneratedFieldType); + list($sDbFieldDataType, $sDbFieldTypeOptions, $sDbFieldOtherOptions) = static::GetFieldDataTypeAndOptions($sDbFieldType); + + if (strcasecmp($sItopFieldDataType, $sDbFieldDataType) !== 0) + { + return false; + } + + if (strcmp($sItopFieldTypeOptions, $sDbFieldTypeOptions) !== 0) + { + // case sensitive comp as we need to check case for enum possible values for example + return false; + } + + // remove the default value NULL added by MariadDB + $sMariaDbDefaultNull = ' DEFAULT \'NULL\''; + if (utils::EndsWith($sDbFieldOtherOptions, $sMariaDbDefaultNull)) + { + $sDbFieldOtherOptions = substr($sDbFieldOtherOptions, 0, -strlen($sMariaDbDefaultNull)); + } + // remove quotes around default values (always present in MariaDB) + if (preg_match('/ DEFAULT \'([^\']+)\'$/', $sDbFieldOtherOptions, $aMatches) === 1) + { + if (($sItopFieldDataType !== 'ENUM') && is_numeric($aMatches[1])) + { + $sDbFieldOtherOptions = substr($sDbFieldOtherOptions, 0, -(strlen($aMatches[1]) + 2)) + .$aMatches[1]; + } + } + if (strcasecmp($sItopFieldOtherOptions, $sDbFieldOtherOptions) !== 0) + { + return false; + } + + return true; + } + + /** + * @param string $sCompleteFieldType sql field type, for example 'VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT 0' + * + * @return string[] consisting of 3 items : + * 1. data type : for example 'VARCHAR' + * 2. type value : for example '255' + * 3. other options : for example ' CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT 0' + */ + private static function GetFieldDataTypeAndOptions($sCompleteFieldType) + { + preg_match('/^([a-zA-Z]+)(\(([^\)]+)\))?( .+)?$/', $sCompleteFieldType, $aMatches); + + $sDataType = $aMatches[1]; + $sTypeOptions = isset($aMatches[2]) ? $aMatches[3] : ''; + $sOtherOptions = isset($aMatches[4]) ? $aMatches[4] : ''; + + return array($sDataType, $sTypeOptions, $sOtherOptions); + } + /** * @param string $sTable * @param string $sField @@ -1164,7 +1254,8 @@ class CMDBSource } elseif (is_string($aFieldData["Default"]) == 'string') { - $sRet .= ' DEFAULT '.self::Quote($aFieldData["Default"]); + $sDefaultValue = static::RemoveSurroundingQuotes($aFieldData["Default"]); + $sRet .= ' DEFAULT '.self::Quote($sDefaultValue); } return $sRet; diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 4b21582c8..f0363f583 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -5514,7 +5514,7 @@ abstract class MetaModel // $bToBeChanged = false; $sActualFieldSpec = CMDBSource::GetFieldSpec($sTable, $sField); - if (strcasecmp($sDBFieldSpec, $sActualFieldSpec) != 0) + if (!CMDBSource::IsSameFieldTypes($sDBFieldSpec, $sActualFieldSpec)) { $bToBeChanged = true; $aErrors[$sClass][$sAttCode][] = "field '$sField' in table '$sTable' has a wrong type: found '$sActualFieldSpec' while expecting '$sDBFieldSpec'"; diff --git a/test/core/CMDBSourceTest.php b/test/core/CMDBSourceTest.php new file mode 100644 index 000000000..fea84d1db --- /dev/null +++ b/test/core/CMDBSourceTest.php @@ -0,0 +1,101 @@ +assertEquals($bResult, CMDBSource::IsSameFieldTypes($sItopFieldType, $sDbFieldType)); + } + + public function compareFieldTypesProvider() + { + return array( + 'same datetime types' => array(true, 'DATETIME', 'DATETIME'), + 'different types' => array(false, 'VARCHAR(255)', 'INT(11)'), + 'different types, same type options' => array(false, 'VARCHAR(11)', 'INT(11)'), + 'same int declaration, same case' => array(true, 'INT(11)', 'INT(11)'), + 'same int declaration, different case on data type' => array(true, 'INT(11)', 'int(11)'), + 'same enum declaration, same case' => array( + true, + 'ENUM(\'error\',\'idle\',\'planned\',\'running\')', + 'ENUM(\'error\',\'idle\',\'planned\',\'running\')', + ), + 'same enum declaration, different case on data type' => array( + true, + 'ENUM(\'error\',\'idle\',\'planned\',\'running\')', + 'enum(\'error\',\'idle\',\'planned\',\'running\')', + ), + 'same enum declaration, different case on type options' => array( + false, + 'ENUM(\'ERROR\',\'IDLE\',\'planned\',\'running\')', + 'ENUM(\'error\',\'idle\',\'planned\',\'running\')', + ), + 'same enum declaration, different case on both data type and type options' => array( + false, + 'ENUM(\'ERROR\',\'IDLE\',\'planned\',\'running\')', + 'enum(\'error\',\'idle\',\'planned\',\'running\')', + ), + 'MariaDB 10.2 nullable datetime' => array( + true, + 'DATETIME', + 'datetime DEFAULT \'NULL\'', + ), + 'MariaDB 10.2 nullable text' => array( + true, + 'TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci', + 'text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT \'NULL\'', + ), + 'MariaDB 10.2 nullable unsigned int' => array( + true, + 'INT(11) UNSIGNED', + 'int(11) unsigned DEFAULT \'NULL\'', + ), + 'MariaDB 10.2 varchar with default value' => array( + true, + 'VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT 0', + 'varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT \'0\'', + ), + 'MariaDB 10.2 Enum with string default value' => array( + true, + 'ENUM(\'error\',\'idle\',\'planned\',\'running\') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT \'planned\'', + 'enum(\'error\',\'idle\',\'planned\',\'running\') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT \'planned\'', + ), + 'MariaDB 10.2 Enum with numeric default value' => array( + true, + 'ENUM(\'1\',\'2\',\'3\') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT \'1\'', + 'enum(\'1\',\'2\',\'3\') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT \'1\'', + ), + ); + } +}