This commit is contained in:
odain
2026-03-13 11:57:45 +01:00
parent 04380802b6
commit 9b90ed3983
3 changed files with 157 additions and 31 deletions

View File

@@ -13,6 +13,8 @@ class DeletionPlanService
{
private static DeletionPlanService $oInstance;
public int $iExecutionCount = 0;
protected function __construct()
{
}
@@ -92,6 +94,7 @@ 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)
* @param int $iMaxExecutionCount : max execution count 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
@@ -99,7 +102,7 @@ class DeletionPlanService
* @throws \CoreUnexpectedValue
* @throws \MySQLException
*/
public function ExecuteDeletionPlan(array $aClasses, int $iUnixTimeLimit = 0): array
public function ExecuteDeletionPlan(array $aClasses, int $iUnixTimeLimit = 0, int $iMaxExecutionCount=0): array
{
$oDeletionPlan = $this->GetDeletionPlan($aClasses);
@@ -107,12 +110,14 @@ class DeletionPlanService
throw new DataFeatureRemovalException("Deletion Plan cannot be executed due to issues");
}
$this->iExecutionCount=0;
$aSummary = [];
foreach ($oDeletionPlan->ListUpdates() as $sClass => $aToUpdate) {
$oDeletionPlanSummaryEntity = $aSummary[$sClass] ?? new DeletionPlanSummaryEntity($sClass);
foreach ($aToUpdate as $aData) {
if ($this->IsTimeLimitExceeded($iUnixTimeLimit)) {
if ($this->IsTimeLimitExceeded($iUnixTimeLimit, $iMaxExecutionCount)) {
$aSummary[$sClass] = $oDeletionPlanSummaryEntity;
return $aSummary;
}
@@ -133,7 +138,7 @@ class DeletionPlanService
$oDeletionPlanSummaryEntity = $aSummary[$sClass] ?? new DeletionPlanSummaryEntity($sClass);
foreach ($aDeletes as $sId => $aDelete) {
if ($this->IsTimeLimitExceeded($iUnixTimeLimit)) {
if ($this->IsTimeLimitExceeded($iUnixTimeLimit, $iMaxExecutionCount)) {
$aSummary[$sClass] = $oDeletionPlanSummaryEntity;
return $aSummary;
}
@@ -192,8 +197,15 @@ class DeletionPlanService
return $oDeletionPlan;
}
public function IsTimeLimitExceeded(int $iUnixTimeLimit): bool
public function IsTimeLimitExceeded(int $iUnixTimeLimit, int $iMaxExecutionCount): bool
{
throw new \Exception("");
$this->iExecutionCount++;
echo json_encode([$this->iExecutionCount, $iMaxExecutionCount]);
if ($iMaxExecutionCount === $this->iExecutionCount){
return false;
}
if ($iUnixTimeLimit === 0) {
return false;
}

View File

@@ -277,10 +277,11 @@ class DeletionPlanServiceTest extends ItopCustomDatamodelTestCase
public function testExecuteDeletionPlan_DeleteAllWithoutLimit()
{
$iDFRToRemoveLeaf = $this->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]);
$this->GivenDFRTreeInDB(<<<EOF
DFRToRemoveLeaf_1 <- DFRToUpdate_1
DFRToRemoveLeaf_1 <- DFRRemovedCollateral_1
DFRRemovedCollateral_1 <- DFRRemovedCollateralCascade_1
EOF);
$aClasses = [ 'DFRToRemoveLeaf' ];
$aRes = DeletionPlanService::GetInstance()->ExecuteDeletionPlan($aClasses);
@@ -290,7 +291,46 @@ class DeletionPlanServiceTest extends ItopCustomDatamodelTestCase
['DFRRemovedCollateral', 0, 1 ],
['DFRRemovedCollateralCascade', 0, 1 ],
];
$this->AssertSummaryEquals($aExpected, $aRes, 'DeleteAllWithoutLimit');
$this->AssertSummaryEquals($aExpected, $aRes);
}
public function testExecuteDeletionPlan_DeleteManyObjPerClassWithoutLimit()
{
$this->GivenDFRTreeInDB(<<<EOF
DFRToRemoveLeaf_1 <- DFRToUpdate_1
DFRToRemoveLeaf_1 <- DFRRemovedCollateral_1
DFRRemovedCollateral_1 <- DFRRemovedCollateralCascade_1
DFRToRemoveLeaf_2 <- DFRToUpdate_2
DFRToRemoveLeaf_2 <- DFRRemovedCollateral_2
DFRRemovedCollateral_2 <- DFRRemovedCollateralCascade_2
DFRToRemoveLeaf_3 <- DFRToUpdate_3
DFRToRemoveLeaf_3 <- DFRRemovedCollateral_3
DFRRemovedCollateral_3 <- DFRRemovedCollateralCascade_3
EOF);
$aClasses = [ 'DFRToRemoveLeaf' ];
$aRes = DeletionPlanService::GetInstance()->ExecuteDeletionPlan($aClasses);
$aExpected = [
['DFRToUpdate', 3, 0 ],
['DFRToRemoveLeaf', 0, 3 ],
['DFRRemovedCollateral', 0, 3 ],
['DFRRemovedCollateralCascade', 0, 3 ],
];
$this->AssertSummaryEquals($aExpected, $aRes);
}
public function testExecuteDeletionPlan_ManualDeleteShouldFail()
{
$this->GivenDFRTreeInDB(<<<EOF
DFRToRemoveLeaf_1 <- DFRManual_1
EOF);
$aClasses = [ 'DFRToRemoveLeaf' ];
$this->expectException(DataFeatureRemovalException::class);
$this->expectExceptionMessage('Deletion Plan cannot be executed due to issues');
DeletionPlanService::GetInstance()->ExecuteDeletionPlan($aClasses);
}
private function AssertSummaryEquals(array $expected, $actual, $sMessage = '')
@@ -311,16 +351,90 @@ class DeletionPlanServiceTest extends ItopCustomDatamodelTestCase
public function testExecuteDeletionPlan_StopInUpdates()
{
self::markTestSkipped();
$this->GivenDFRTreeInDB(<<<EOF
DFRToRemoveLeaf_1 <- DFRToUpdate_1
DFRToRemoveLeaf_1 <- DFRRemovedCollateral_1
DFRRemovedCollateral_1 <- DFRRemovedCollateralCascade_1
DFRToRemoveLeaf_2 <- DFRToUpdate_2
DFRToRemoveLeaf_2 <- DFRRemovedCollateral_2
DFRRemovedCollateral_2 <- DFRRemovedCollateralCascade_2
DFRToRemoveLeaf_3 <- DFRToUpdate_3
DFRToRemoveLeaf_3 <- DFRRemovedCollateral_3
DFRRemovedCollateral_3 <- DFRRemovedCollateralCascade_3
EOF);
$aClasses = [ 'DFRToRemoveLeaf' ];
$aRes = DeletionPlanService::GetInstance()->ExecuteDeletionPlan($aClasses, 0, 2);
$aExpected = [
['DFRToUpdate', 2, 0 ],
['DFRToRemoveLeaf', 0, 0 ],
['DFRRemovedCollateral', 0, 0 ],
['DFRRemovedCollateralCascade', 0, 0 ],
];
$this->AssertSummaryEquals($aExpected, $aRes);
}
public function testExecuteDeletionPlan_StopInDeletes()
{
self::markTestSkipped();
$this->GivenDFRTreeInDB(<<<EOF
DFRToRemoveLeaf_1 <- DFRToUpdate_1
DFRToRemoveLeaf_1 <- DFRRemovedCollateral_1
DFRRemovedCollateral_1 <- DFRRemovedCollateralCascade_1
DFRToRemoveLeaf_2 <- DFRToUpdate_2
DFRToRemoveLeaf_2 <- DFRRemovedCollateral_2
DFRRemovedCollateral_2 <- DFRRemovedCollateralCascade_2
DFRToRemoveLeaf_3 <- DFRToUpdate_3
DFRToRemoveLeaf_3 <- DFRRemovedCollateral_3
DFRRemovedCollateral_3 <- DFRRemovedCollateralCascade_3
EOF);
$aClasses = [ 'DFRToRemoveLeaf' ];
$aRes = DeletionPlanService::GetInstance()->ExecuteDeletionPlan($aClasses, 0, 5);
$aExpected = [
['DFRToUpdate', 3, 0 ],
['DFRToRemoveLeaf', 2, 0 ],
['DFRRemovedCollateral', 0, 0 ],
['DFRRemovedCollateralCascade', 0, 0 ],
];
$this->AssertSummaryEquals($aExpected, $aRes);
}
public function GetDatamodelDeltaAbsPath(): string
{
return __DIR__.'/deletionplan_delta.xml';
}
private function GivenDFRTreeInDB(string $sTree)
{
$aTree = explode("\n", $sTree);
foreach ($aTree as $sLine) {
if (trim($sLine) === ""){
continue;
}
$this->GivenDFRTreeLineInDB($sLine);
}
}
private array $aIdByObjectName = [];
private function GivenDFRTreeLineInDB(string $sLine)
{
list($sLeft, $sRight) = explode('<-', $sLine);
$sLeft=trim($sLeft);
$iLeftId = $this->aIdByObjectName[$sLeft] ?? 0;
if ($iLeftId===0){
list($sChildClass, ) = explode('_', $sLeft, 2);
$iLeftId = $this->GivenObjectInDB($sChildClass, ['name' => $sLeft]);
$this->aIdByObjectName[$sLeft]=$iLeftId;
}
$sRight=trim($sRight);
list($sChildClass, ) = explode('_', $sRight, 2);
$iRightId = $this->GivenObjectInDB($sChildClass, ['name' => $sRight, 'extkey_id' => $iLeftId]);
$this->aIdByObjectName[$sRight]=$iRightId;
}
}

View File

@@ -62,8 +62,8 @@
<dependencies/>
<tracking_level>all</tracking_level>
</field>
<field id="dfrtoremove_id" xsi:type="AttributeExternalKey">
<sql>dfrtoremove_id</sql>
<field id="extkey_id" xsi:type="AttributeExternalKey">
<sql>extkey_id</sql>
<filter/>
<dependencies/>
<is_null_allowed>true</is_null_allowed>
@@ -85,7 +85,7 @@
<item id="name">
<rank>10</rank>
</item>
<item id="dfrtoremove_id">
<item id="extkey_id">
<rank>20</rank>
</item>
</items>
@@ -114,8 +114,8 @@
<dependencies/>
<tracking_level>all</tracking_level>
</field>
<field id="dfrtoremove_id" xsi:type="AttributeExternalKey">
<sql>dfrtoremove_id</sql>
<field id="extkey_id" xsi:type="AttributeExternalKey">
<sql>extkey_id</sql>
<filter/>
<dependencies/>
<is_null_allowed>false</is_null_allowed>
@@ -137,7 +137,7 @@
<item id="name">
<rank>10</rank>
</item>
<item id="dfrtoremove_id">
<item id="extkey_id">
<rank>20</rank>
</item>
</items>
@@ -209,8 +209,8 @@
<dependencies/>
<tracking_level>all</tracking_level>
</field>
<field id="dfrremovedcollateral_id" xsi:type="AttributeExternalKey">
<sql>dfrremovedcollateral_id</sql>
<field id="extkey_id" xsi:type="AttributeExternalKey">
<sql>extkey_id</sql>
<filter/>
<dependencies/>
<is_null_allowed>false</is_null_allowed>
@@ -232,7 +232,7 @@
<item id="name">
<rank>10</rank>
</item>
<item id="dfrremovedcollateral_id">
<item id="extkey_id">
<rank>20</rank>
</item>
</items>
@@ -261,8 +261,8 @@
<dependencies/>
<tracking_level>all</tracking_level>
</field>
<field id="dfrtoremove_id" xsi:type="AttributeExternalKey">
<sql>dfrtoremove_id</sql>
<field id="extkey_id" xsi:type="AttributeExternalKey">
<sql>extkey_id</sql>
<filter/>
<dependencies/>
<is_null_allowed>false</is_null_allowed>
@@ -284,7 +284,7 @@
<item id="name">
<rank>10</rank>
</item>
<item id="dfrtoremove_id">
<item id="extkey_id">
<rank>20</rank>
</item>
</items>
@@ -308,16 +308,16 @@
<entry id="Class:DFRToUpdate+" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRToUpdate/Attribute:name" _delta="define"><![CDATA[Name]]></entry>
<entry id="Class:DFRToUpdate/Attribute:name+" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRToUpdate/Attribute:dfrtoremove_id" _delta="define"><![CDATA[Dfrtoremove id]]></entry>
<entry id="Class:DFRToUpdate/Attribute:dfrtoremove_id+" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRToUpdate/Attribute:extkey_id" _delta="define"><![CDATA[Dfrtoremove id]]></entry>
<entry id="Class:DFRToUpdate/Attribute:extkey_id+" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRRemovedCollateral/Name" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRRemovedCollateral/ComplementaryName" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRRemovedCollateral" _delta="define"><![CDATA[DFRRemovedCollateral]]></entry>
<entry id="Class:DFRRemovedCollateral+" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRRemovedCollateral/Attribute:name" _delta="define"><![CDATA[Name]]></entry>
<entry id="Class:DFRRemovedCollateral/Attribute:name+" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRRemovedCollateral/Attribute:dfrtoremove_id" _delta="define"><![CDATA[Dfrtoremove id]]></entry>
<entry id="Class:DFRRemovedCollateral/Attribute:dfrtoremove_id+" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRRemovedCollateral/Attribute:extkey_id" _delta="define"><![CDATA[Dfrtoremove id]]></entry>
<entry id="Class:DFRRemovedCollateral/Attribute:extkey_id+" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRToRemoveLeaf/Name" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRToRemoveLeaf/ComplementaryName" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRToRemoveLeaf" _delta="define"><![CDATA[DFRToRemoveLeaf]]></entry>
@@ -330,16 +330,16 @@
<entry id="Class:DFRRemovedCollateralCascade+" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRRemovedCollateralCascade/Attribute:name" _delta="define"><![CDATA[Name]]></entry>
<entry id="Class:DFRRemovedCollateralCascade/Attribute:name+" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRRemovedCollateralCascade/Attribute:dfrremovedcollateral_id" _delta="define"><![CDATA[Dfrremovedcollateral id]]></entry>
<entry id="Class:DFRRemovedCollateralCascade/Attribute:dfrremovedcollateral_id+" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRRemovedCollateralCascade/Attribute:extkey_id" _delta="define"><![CDATA[Dfrremovedcollateral id]]></entry>
<entry id="Class:DFRRemovedCollateralCascade/Attribute:extkey_id+" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRManual/Name" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRManual/ComplementaryName" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRManual" _delta="define"><![CDATA[DFRManual]]></entry>
<entry id="Class:DFRManual+" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRManual/Attribute:name" _delta="define"><![CDATA[Name]]></entry>
<entry id="Class:DFRManual/Attribute:name+" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRManual/Attribute:dfrtoremove_id" _delta="define"><![CDATA[Dfrtoremove id]]></entry>
<entry id="Class:DFRManual/Attribute:dfrtoremove_id+" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRManual/Attribute:extkey_id" _delta="define"><![CDATA[Dfrtoremove id]]></entry>
<entry id="Class:DFRManual/Attribute:extkey_id+" _delta="define"><![CDATA[]]></entry>
</entries>
</dictionary>
</dictionaries>