From 2bd8700c909559e7dd22e5c3596c5c4451b1c4f2 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 15 Aug 2011 14:01:51 +0000 Subject: [PATCH] Implementing Trac #419: provide means for the toolkit to fix synchro data sources SVN:trunk[1452] --- core/metamodel.class.php | 76 +++++++-- synchro/synchrodatasource.class.inc.php | 201 ++++++++++++++++++++---- 2 files changed, 233 insertions(+), 44 deletions(-) diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 9224cd3d9..41b2b7e77 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -2609,10 +2609,13 @@ if (!array_key_exists($sAttCode, self::$m_aAttribDefs[$sClass])) /** * Check (and updates if needed) the hierarchical keys + * @param $bDiagnosticsOnly boolean If true only a diagnostic pass will be run, returning true or false + * @param $bVerbose boolean Displays some information about what is done/what needs to be done * @param $bForceComputation boolean If true, the _left and _right parameters will be recomputed even if some values already exist in the DB */ - public static function CheckHKeys($bForceComputation = false) + public static function CheckHKeys($bDiagnosticsOnly = false, $bVerbose = false, $bForceComputation = false) { + $bChangeNeeded = false; foreach (self::GetClasses() as $sClass) { if (!self::HasTable($sClass)) continue; @@ -2622,10 +2625,20 @@ if (!array_key_exists($sAttCode, self::$m_aAttribDefs[$sClass])) // Check (once) all the attributes that are hierarchical keys if((self::GetAttributeOrigin($sClass, $sAttCode) == $sClass) && $oAttDef->IsHierarchicalKey()) { - self::HKInit($sClass, $sAttCode, $bForceComputation); + if ($bVerbose) + { + echo "The attribute $sAttCode from $sClass is a hierarchical key.\n"; + } + $bResult = self::HKInit($sClass, $sAttCode, $bDiagnosticsOnly, $bVerbose, $bForceComputation); + $bChangeNeeded |= $bResult; + if ($bVerbose && !$bResult) + { + echo "Ok, the attribute $sAttCode from class $sClass seems up to date.\n"; + } } } } + return $bChangeNeeded; } /** @@ -2634,9 +2647,12 @@ if (!array_key_exists($sAttCode, self::$m_aAttribDefs[$sClass])) * to correspond to the existing hierarchy in the database * @param $sClass string Name of the class to process * @param $sAttCode string Code of the attribute to process - * @param $bForceComputation boolean If true, the _left and _right parameters will be recomputed even if some values already exist in the DB + * @param $bDiagnosticsOnly boolean If true only a diagnostic pass will be run, returning true or false + * @param $bVerbose boolean Displays some information about what is done/what needs to be done + * @param $bForceComputation boolean If true, the _left and _right parameters will be recomputed even if some values already exist in the DB + * @return true if an update is needed (diagnostics only) / was performed */ - public static function HKInit($sClass, $sAttCode, $bForceComputation = false) + public static function HKInit($sClass, $sAttCode, $bDiagnosticsOnly = false, $bVerbose = false, $bForceComputation = false) { $idx = 1; $bUpdateNeeded = $bForceComputation; @@ -2644,24 +2660,35 @@ if (!array_key_exists($sAttCode, self::$m_aAttribDefs[$sClass])) $sTable = self::DBGetTable($sClass, $sAttCode); if ($oAttDef->IsHierarchicalKey()) { - if (!$bForceComputation) + // Check if some values already exist in the table for the _right value, if so, do nothing + $sRight = $oAttDef->GetSQLRight(); + $sSQL = "SELECT MAX(`$sRight`) AS MaxRight FROM `$sTable`"; + $iMaxRight = CMDBSource::QueryToScalar($sSQL); + $sSQL = "SELECT COUNT(`$sRight`) AS Count FROM `$sTable`"; + $iCount = CMDBSource::QueryToScalar($sSQL); + if (!$bForceComputation && ($iCount != 0) && ($iMaxRight == 0)) { - // Check if some values already exist in the table for the _right value, if so, do nothing - $sRight = $oAttDef->GetSQLRight(); - $sSQL = "SELECT MAX(`$sRight`) AS MaxRight FROM `$sTable`"; - $iMaxRight = CMDBSource::QueryToScalar($sSQL); - if ($iMaxRight == 0) + $bUpdateNeeded = true; + if ($bVerbose) { - $bUpdateNeeded = true; + echo "The table '$sTable' must be updated to compute the fields $sRight and ".$oAttDef->GetSQLLeft()."\n"; } } - if ($bUpdateNeeded) + if ($bForceComputation && !$bDiagnosticsOnly) + { + echo "Rebuilding the fields $sRight and ".$oAttDef->GetSQLLeft()." from table '$sTable'...\n"; + } + if ($bUpdateNeeded && !$bDiagnosticsOnly) { try { CMDBSource::Query('START TRANSACTION'); self::HKInitChildren($sTable, $sAttCode, $oAttDef, 0, $idx); CMDBSource::Query('COMMIT'); + if ($bVerbose) + { + echo "Ok, table '$sTable' successfully updated.\n"; + } } catch(Exception $e) { @@ -2670,6 +2697,7 @@ if (!array_key_exists($sAttCode, self::$m_aAttribDefs[$sClass])) } } } + return $bUpdateNeeded; } /** @@ -2693,6 +2721,30 @@ if (!array_key_exists($sAttCode, self::$m_aAttribDefs[$sClass])) } } + public static function CheckDataSources($bDiagnostics, $bVerbose) + { + $sOQL = 'SELECT SynchroDataSource'; + $oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL)); + $bFixNeeded = false; + if ($bVerbose && $oSet->Count() == 0) + { + echo "There are no Data Sources in the database.\n"; + } + while($oSource = $oSet->Fetch()) + { + if ($bVerbose) + { + echo "Checking Data Source '".$oSource->GetName()."'...\n"; + $bFixNeeded = $bFixNeeded | $oSource->CheckDBConsistency($bDiagnostics, $bVerbose); + } + } + if (!$bFixNeeded && $bVerbose) + { + echo "Ok.\n"; + } + return $bFixNeeded; + } + public static function GenerateUniqueAlias(&$aAliases, $sNewName, $sRealName) { if (!array_key_exists($sNewName, $aAliases)) diff --git a/synchro/synchrodatasource.class.inc.php b/synchro/synchrodatasource.class.inc.php index 92e51c0c1..ee1c0510a 100644 --- a/synchro/synchrodatasource.class.inc.php +++ b/synchro/synchrodatasource.class.inc.php @@ -476,27 +476,7 @@ EOF { if(!isset($aAttributes[$sAttCode])) { - $oAttDef = MetaModel::GetAttributeDef($this->GetTargetClass(), $sAttCode); - if ($oAttDef->IsExternalKey()) - { - $oAttribute = new SynchroAttExtKey(); - $oAttribute->Set('reconciliation_attcode', ''); // Blank means by pkey - } - elseif ($oAttDef->IsLinkSet() && $oAttDef->IsIndirect()) - { - $oAttribute = new SynchroAttLinkSet(); - // Todo - set those value from the form - $oAttribute->Set('row_separator', MetaModel::GetConfig()->Get('link_set_item_separator')); - $oAttribute->Set('attribute_separator', MetaModel::GetConfig()->Get('link_set_attribute_separator')); - $oAttribute->Set('value_separator', MetaModel::GetConfig()->Get('link_set_value_separator')); - $oAttribute->Set('attribute_qualifier', MetaModel::GetConfig()->Get('link_set_attribute_qualifier')); - } - else - { - $oAttribute = new SynchroAttribute(); - } - $oAttribute->Set('sync_source_id', $this->GetKey()); - $oAttribute->Set('attcode', $sAttCode); + $oAttribute = $this->CreateSynchroAtt($sAttCode); } else { @@ -527,9 +507,40 @@ EOF $this->Set('attribute_list', $oAttributeSet); } - /* - * Overload the standard behavior - */ + /** + * Creates a new SynchroAttXXX object in memory with the default values + */ + protected function CreateSynchroAtt($sAttCode) + { + $oAttDef = MetaModel::GetAttributeDef($this->GetTargetClass(), $sAttCode); + if ($oAttDef->IsExternalKey()) + { + $oAttribute = new SynchroAttExtKey(); + $oAttribute->Set('reconciliation_attcode', ''); // Blank means by pkey + } + elseif ($oAttDef->IsLinkSet() && $oAttDef->IsIndirect()) + { + $oAttribute = new SynchroAttLinkSet(); + // Todo - set those value from the form + $oAttribute->Set('row_separator', MetaModel::GetConfig()->Get('link_set_item_separator')); + $oAttribute->Set('attribute_separator', MetaModel::GetConfig()->Get('link_set_attribute_separator')); + $oAttribute->Set('value_separator', MetaModel::GetConfig()->Get('link_set_value_separator')); + $oAttribute->Set('attribute_qualifier', MetaModel::GetConfig()->Get('link_set_attribute_qualifier')); + } + else + { + $oAttribute = new SynchroAttribute(); + } + $oAttribute->Set('sync_source_id', $this->GetKey()); + $oAttribute->Set('attcode', $sAttCode); + $oAttribute->Set('reconcile', 0); + $oAttribute->Set('update', 0); + $oAttribute->Set('update_policy', 'master_locked'); + return $oAttribute; + } + /** + * Overload the standard behavior + */ public function ComputeValues() { parent::ComputeValues(); @@ -667,13 +678,31 @@ EOF $sCreateTable = "CREATE TABLE `$sTable` ($sFieldDefs) ENGINE = innodb;"; CMDBSource::Query($sCreateTable); + $aTriggers = $this->GetTriggersDefinition(); + foreach($aTriggers as $key => $sTriggerSQL) + { + CMDBSource::Query($sTriggerSQL); + } + } + + /** + * Gets the definitions of the 3 triggers: before insert, before update and after delete + * @return array An array with 3 entries, one for each of the SQL queries + */ + protected function GetTriggersDefinition() + { + $sTable = $this->GetDataTable(); + $sReplicaTable = MetaModel::DBGetTable('SynchroReplica'); + $aColumns = $this->GetSQLColumns(); + $aResult = array(); + $sTriggerInsert = "CREATE TRIGGER `{$sTable}_bi` BEFORE INSERT ON $sTable"; $sTriggerInsert .= " FOR EACH ROW"; $sTriggerInsert .= " BEGIN"; $sTriggerInsert .= " INSERT INTO {$sReplicaTable} (sync_source_id, status_last_seen, `status`) VALUES ({$this->GetKey()}, NOW(), 'new');"; $sTriggerInsert .= " SET NEW.id = LAST_INSERT_ID();"; $sTriggerInsert .= " END;"; - CMDBSource::Query($sTriggerInsert); + $aResult['bi'] = $sTriggerInsert; $aModified = array(); foreach($aColumns as $sColumn => $ColSpec) @@ -696,14 +725,15 @@ EOF $sTriggerUpdate .= " SET NEW.id = OLD.id;"; // make sure this id won't change $sTriggerUpdate .= " END IF;"; $sTriggerUpdate .= " END;"; - CMDBSource::Query($sTriggerUpdate); + $aResult['bu'] = $sTriggerUpdate; - $sTriggerInsert = "CREATE TRIGGER `{$sTable}_ad` AFTER DELETE ON $sTable"; - $sTriggerInsert .= " FOR EACH ROW"; - $sTriggerInsert .= " BEGIN"; - $sTriggerInsert .= " DELETE FROM {$sReplicaTable} WHERE id = OLD.id;"; - $sTriggerInsert .= " END;"; - CMDBSource::Query($sTriggerInsert); + $sTriggerDelete = "CREATE TRIGGER `{$sTable}_ad` AFTER DELETE ON $sTable"; + $sTriggerDelete .= " FOR EACH ROW"; + $sTriggerDelete .= " BEGIN"; + $sTriggerDelete .= " DELETE FROM {$sReplicaTable} WHERE id = OLD.id;"; + $sTriggerDelete .= " END;"; + $aResult['ad'] = $sTriggerDelete; + return $aResult; } protected function AfterDelete() @@ -717,6 +747,113 @@ EOF // TO DO - check that triggers get dropped with the table } + /** + * Checks if the data source definition is consistent with the schema of the target class + * @param $bDiagnostics boolean True to only diagnose the consistency, false to actually apply some changes + * @param $bVerbose boolean True to get some information in the std output (echo) + * @return bool Whether or not the database needs fixing for this data source + */ + public function CheckDBConsistency($bDiagnostics, $bVerbose, $oChange = null) + { + $bFixNeeded = false; + $aMissingFields = array(); + $oAttributeSet = $this->Get('attribute_list'); + $aAttributes = array(); + + while($oAttribute = $oAttributeSet->Fetch()) + { + $aAttributes[$oAttribute->Get('attcode')] = $oAttribute; + } + + foreach(MetaModel::ListAttributeDefs($this->GetTargetClass()) as $sAttCode=>$oAttDef) + { + if ($oAttDef->IsWritable()) + { + if (!isset($aAttributes[$sAttCode])) + { + $bFixNeeded = true; + $aMissingFields[] = $sAttCode; + // New field missing... + if ($bDiagnostics) + { + // Report the issue + if ($bVerbose) + { + echo "Missing field description for the field '$sAttCode', for the data synchro task ".$this->GetName()." (".$this->GetKey()."), will be created with default values.\n"; + } + } + else + { + if ($oChange == null) + { + $oChange = MetaModel::NewObject("CMDBChange"); + $oChange->Set("date", time()); + $sUserString = CMDBChange::GetCurrentUserName(); + $oChange->Set("userinfo", $sUserString); + $oChange->DBInsert(); + } + // Fix the issue + $oAttribute = $this->CreateSynchroAtt($sAttCode); + $oAttribute->DBInsertTracked($oChange); + } + } + } + } + $sTable = $this->GetDataTable(); + if ($bFixNeeded) + { + // The structure of the table needs adjusting + $aColumns = $this->GetSQLColumns($aMissingFields); + $aFieldDefs = array(); + foreach($aColumns as $sAttCode => $sColumnDef) + { + $aFieldDefs[] = "$sAttCode $sColumnDef"; + } + $sAlterTable = "ALTER TABLE `$sTable` ADD (".implode(',', $aFieldDefs).");"; + + // The triggers as well must be adjusted + $aTriggers = array(); + $aTriggersDefs = $this->GetTriggersDefinition(); + $aTriggers[] = "DROP TRIGGER IF EXISTS {$sTable}_bi;"; + $aTriggers[] = $aTriggersDefs['bi']; + $aTriggers[] = "DROP TRIGGER IF EXISTS {$sTable}_bu;"; + $aTriggers[] = $aTriggersDefs['bu']; + $aTriggers[] = "DROP TRIGGER IF EXISTS {$sTable}_ad;"; + $aTriggers[] = $aTriggersDefs['ad']; + + if ($bDiagnostics) + { + if ($bVerbose) + { + // Report the issue + echo "The structure of the table $sTable for the data synchro task ".$this->GetName()." (".$this->GetKey().") must be altered (missing fields: ".implode(',', $aMissingFields).").\n"; + echo "$sAlterTable\n"; + echo "The trigger {$sTable}_bi, {$sTable}_bu, {$sTable}_ad for the data synchro task ".$this->GetName()." (".$this->GetKey().") must be re-created.\n"; + echo implode("\n", $aTriggers)."\n"; + } + } + else + { + // Fix the issue + CMDBSource::Query($sAlterTable); + if ($bVerbose) + { + echo "$sAlterTable\n"; + } + + foreach($aTriggers as $sSQL) + { + CMDBSource::Query($sSQL); + if ($bVerbose) + { + echo "$sSQL\n"; + } + } + } + } + + return $bFixNeeded; + } protected function SendNotification($sSubject, $sBody) { $iContact = $this->Get('notify_contact_id');