diff --git a/datamodels/2.x/combodo-data-feature-removal/src/Service/DeletionPlanService.php b/datamodels/2.x/combodo-data-feature-removal/src/Service/DeletionPlanService.php index bce90b8bf..91fbdac01 100644 --- a/datamodels/2.x/combodo-data-feature-removal/src/Service/DeletionPlanService.php +++ b/datamodels/2.x/combodo-data-feature-removal/src/Service/DeletionPlanService.php @@ -89,7 +89,9 @@ class DeletionPlanService } /** + * @since 3.3.0 * @param array $aClasses + * @param int $iUnixTimeLimit : max execution time in seconds since Epoch before stopping deletion. by default: no limit (ie remove all without stop) * * @return array<\Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanSummaryEntity> * @throws \Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException @@ -97,7 +99,7 @@ class DeletionPlanService * @throws \CoreUnexpectedValue * @throws \MySQLException */ - public function ExecuteDeletionPlan(array $aClasses): array + public function ExecuteDeletionPlan(array $aClasses, int $iUnixTimeLimit = 0): array { $oDeletionPlan = $this->GetDeletionPlan($aClasses); @@ -110,6 +112,11 @@ class DeletionPlanService $oDeletionPlanSummaryEntity = $aSummary[$sClass] ?? new DeletionPlanSummaryEntity($sClass); foreach ($aToUpdate as $aData) { + if ($this->IsTimeLimitExceeded($iUnixTimeLimit)) { + $aSummary[$sClass] = $oDeletionPlanSummaryEntity; + return $aSummary; + } + $oToUpdate = $aData['to_reset']; /** @var \DBObject $oToUpdate */ foreach ($aData['attributes'] as $sRemoteExtKey => $aRemoteAttDef) { @@ -126,20 +133,33 @@ class DeletionPlanService $oDeletionPlanSummaryEntity = $aSummary[$sClass] ?? new DeletionPlanSummaryEntity($sClass); foreach ($aDeletes as $sId => $aDelete) { - // Delete any existing change tracking about the current object - $oFilter = new DBObjectSearch('CMDBChangeOp'); - $oFilter->AddCondition('objclass', $sClass, '='); - $oFilter->AddCondition('objkey', $sId, '='); - MetaModel::PurgeData($oFilter); - - // Delete the entry - $aClassesToRemove = array_merge(MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL), MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_EXCLUDELEAF, false)); - foreach ($aClassesToRemove as $sParentClass) { - $oFilter = DBObjectSearch::FromOQL_AllData("SELECT $sParentClass WHERE id=:id"); - $sQuery = $oFilter->MakeDeleteQuery(['id' => $sId]); - CMDBSource::DeleteFrom($sQuery); + if ($this->IsTimeLimitExceeded($iUnixTimeLimit)) { + $aSummary[$sClass] = $oDeletionPlanSummaryEntity; + return $aSummary; } + try { + CMDBSource::Query('START TRANSACTION'); + // Delete any existing change tracking about the current object + $oFilter = new DBObjectSearch('CMDBChangeOp'); + $oFilter->AddCondition('objclass', $sClass, '='); + $oFilter->AddCondition('objkey', $sId, '='); + MetaModel::PurgeData($oFilter); + + // Delete the entry + $aClassesToRemove = array_merge(MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL), MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_EXCLUDELEAF, false)); + foreach ($aClassesToRemove as $sParentClass) { + $oFilter = DBObjectSearch::FromOQL_AllData("SELECT $sParentClass WHERE id=:id"); + $sQuery = $oFilter->MakeDeleteQuery(['id' => $sId]); + CMDBSource::DeleteFrom($sQuery); + } + + CMDBSource::Query('COMMIT'); + } catch (\Exception $e) { + \IssueLog::Exception(__METHOD__.": Cleanup failed", $e); + CMDBSource::Query('ROLLBACK'); + throw $e; + } $oDeletionPlanSummaryEntity->iDeleteCount++; } @@ -171,4 +191,13 @@ class DeletionPlanService return $oDeletionPlan; } + + public function IsTimeLimitExceeded(int $iUnixTimeLimit): bool + { + if ($iUnixTimeLimit === 0) { + return false; + } + + return (time() <= $iUnixTimeLimit); + } } diff --git a/tests/php-unit-tests/unitary-tests/datamodels/2.x/combodo-data-feature-removal/DeletionPlanServiceTest.php b/tests/php-unit-tests/unitary-tests/datamodels/2.x/combodo-data-feature-removal/DeletionPlanServiceTest.php index 6d6296a70..42dbffc34 100644 --- a/tests/php-unit-tests/unitary-tests/datamodels/2.x/combodo-data-feature-removal/DeletionPlanServiceTest.php +++ b/tests/php-unit-tests/unitary-tests/datamodels/2.x/combodo-data-feature-removal/DeletionPlanServiceTest.php @@ -1,8 +1,59 @@ GivenObjectInDB('DFRToRemoveLeaf', ['name' => 'ga']); + $this->GivenObjectInDB('DFRToUpdate', ['name' => 'bu', 'dfrtoremove_id' => $iDFRToRemoveLeaf]); + $iDFRRemovedCollateral = $this->GivenObjectInDB('DFRRemovedCollateral', ['name' => 'zo', 'dfrtoremove_id' => $iDFRToRemoveLeaf]); + $this->GivenObjectInDB('DFRRemovedCollateralCascade', ['name' => 'meu', 'dfrremovedcollateral_id' => $iDFRRemovedCollateral]); + $aClasses = [ 'DFRToRemoveLeaf' ]; + $aRes = DeletionPlanService::GetInstance()->ExecuteDeletionPlan($aClasses); + $aExpected = [ + ['DFRToUpdate', 1, 0 ], + ['DFRToRemoveLeaf', 0, 1 ], + ['DFRRemovedCollateral', 0, 1 ], + ['DFRRemovedCollateralCascade', 0, 1 ], + ]; + $this->AssertSummaryEquals($aExpected, $aRes, 'DeleteAllWithoutLimit'); + } + + private function AssertSummaryEquals(array $expected, $actual, $sMessage = '') + { + $aExpected = []; + foreach ($expected as $line) { + $sClass = $line[0]; + $iUpdate = $line[1]; + $iDelete = $line[2]; + + $oDeletionPlanSummaryEntity = new DeletionPlanSummaryEntity($sClass); + $oDeletionPlanSummaryEntity->iUpdateCount = $iUpdate; + $oDeletionPlanSummaryEntity->iDeleteCount = $iDelete; + $aExpected[$sClass] = $oDeletionPlanSummaryEntity; + } + $this->assertEquals($aExpected, $actual, $sMessage); + } + + public function testExecuteDeletionPlan_StopInUpdates() + { + self::markTestSkipped(); + } + + public function testExecuteDeletionPlan_StopInDeletes() + { + self::markTestSkipped(); + } + + public function GetDatamodelDeltaAbsPath(): string + { + return __DIR__.'/deletionplan_delta.xml'; + } }