From fc2901a5d309028689b55c0cb4830835a61da849 Mon Sep 17 00:00:00 2001 From: odain Date: Wed, 31 Dec 2025 07:59:21 +0100 Subject: [PATCH] cache returned objects for next round + add jeffrey test --- core/dbobjectset.class.php | 51 +++++++++++----- .../core/DBObject/DBObjectSetTest.php | 58 +++++++++++++++++++ 2 files changed, 94 insertions(+), 15 deletions(-) diff --git a/core/dbobjectset.class.php b/core/dbobjectset.class.php index e34f54425..3c3ea6ff1 100644 --- a/core/dbobjectset.class.php +++ b/core/dbobjectset.class.php @@ -122,9 +122,7 @@ class DBObjectSet implements iDBObjectSetIterator */ public function __destruct() { - if (is_object($this->m_oSQLResult)) { - $this->m_oSQLResult->free(); - } + $this->Free(); } /** @@ -711,11 +709,8 @@ class DBObjectSet implements iDBObjectSetIterator $sSQL = $this->_makeSelectQuery($this->m_aAttToLoad); - if (is_object($this->m_oSQLResult)) { - // Free previous resultset if any - $this->m_oSQLResult->free(); - $this->m_oSQLResult = null; - } + // Free previous resultset if any + $this->Free(); try { $oKPI = new ExecutionKPI(); @@ -919,6 +914,14 @@ class DBObjectSet implements iDBObjectSetIterator return $this->m_iNumLoadedDBRows + count($this->m_aAddedObjects); } + private function Free() + { + if (is_object($this->m_oSQLResult)) { + CMDBSource::FreeResult($this->m_oSQLResult); + $this->m_oSQLResult = null; + } + } + /** * Fetch an object (with the given class alias) at the current position in the set and move the cursor to the next position. * @@ -939,6 +942,7 @@ class DBObjectSet implements iDBObjectSetIterator } if ($this->m_iCurrRow >= $this->CountLoaded()) { + $this->Free(); return null; } @@ -946,7 +950,9 @@ class DBObjectSet implements iDBObjectSetIterator $sRequestedClassAlias = $this->m_oFilter->GetClassAlias(); } - if ($this->m_iCurrRow < $this->m_iNumLoadedDBRows) { + if ($this->m_iCurrRow < count($this->m_aCacheObj)) { + $oRetObj = $this->m_aCacheObj[$this->m_iCurrRow][$sRequestedClassAlias]; + } else if ($this->m_iCurrRow < $this->m_iNumLoadedDBRows) { // Pick the row from the database $aRow = CMDBSource::FetchArray($this->m_oSQLResult); foreach ($this->m_oFilter->GetSelectedClasses() as $sClassAlias => $sClass) { @@ -956,6 +962,7 @@ class DBObjectSet implements iDBObjectSetIterator } else { try { $oRetObj = MetaModel::GetObjectByRow($sClass, $aRow, $sClassAlias, $this->m_aAttToLoad, $this->m_aExtendedDataSpec); + $this->m_aCacheObj[$this->m_iCurrRow] = [$sRequestedClassAlias => $oRetObj]; } catch (CoreException $e) { $this->m_iCurrRow++; $oRetObj = $this->Fetch($sRequestedClassAlias); @@ -972,6 +979,8 @@ class DBObjectSet implements iDBObjectSetIterator return $oRetObj; } + private $m_aCacheObj = []; + /** * Fetch the whole row of objects (if several classes have been specified in the query) and move the cursor to the next position * @@ -990,21 +999,29 @@ class DBObjectSet implements iDBObjectSetIterator } if ($this->m_iCurrRow >= $this->CountLoaded()) { + $this->Free(); return null; } - if ($this->m_iCurrRow < $this->m_iNumLoadedDBRows) { + if ($this->m_iCurrRow < count($this->m_aCacheObj)) { + $aRetObjects = $this->m_aCacheObj[$this->m_iCurrRow]; + } else if ($this->m_iCurrRow < $this->m_iNumLoadedDBRows) { // Pick the row from the database $aRow = CMDBSource::FetchArray($this->m_oSQLResult); $aRetObjects = []; foreach ($this->m_oFilter->GetSelectedClasses() as $sClassAlias => $sClass) { - if (is_null($aRow[$sClassAlias.'id'])) { - $oObj = null; - } else { - $oObj = MetaModel::GetObjectByRow($sClass, $aRow, $sClassAlias, $this->m_aAttToLoad, $this->m_aExtendedDataSpec); + $oObj = null; + if (!is_null($aRow[$sClassAlias.'id'])) { + try { + $oObj = MetaModel::GetObjectByRow($sClass, $aRow, $sClassAlias, $this->m_aAttToLoad, $this->m_aExtendedDataSpec); + } + catch (CoreException $e) { + } } $aRetObjects[$sClassAlias] = $oObj; } + + $this->m_aCacheObj[$this->m_iCurrRow] = $aRetObjects; } else { // Pick the row from the objects added *in memory* $aRetObjects = []; @@ -1047,6 +1064,10 @@ class DBObjectSet implements iDBObjectSetIterator $this->Load(); } + if (is_null($this->m_oSQLResult)) { + return; + } + $this->m_iCurrRow = min($iRow, $this->Count()); if ($this->m_iCurrRow < $this->m_iNumLoadedDBRows) { $this->m_oSQLResult->data_seek($this->m_iCurrRow); @@ -1220,7 +1241,7 @@ class DBObjectSet implements iDBObjectSetIterator * * @throws \CoreException */ - public function HasSameContents(DBObjectSet $oObjectSet, $aExcludeColumns = []) + public function HasSameContents(DBObjectSet $oObjectSet, $aExcludeColumns = []): bool { $oComparator = new DBObjectSetComparator($this, $oObjectSet, $aExcludeColumns); return $oComparator->SetsAreEquivalent(); diff --git a/tests/php-unit-tests/unitary-tests/core/DBObject/DBObjectSetTest.php b/tests/php-unit-tests/unitary-tests/core/DBObject/DBObjectSetTest.php index 42e77f5bb..0c7929f9c 100644 --- a/tests/php-unit-tests/unitary-tests/core/DBObject/DBObjectSetTest.php +++ b/tests/php-unit-tests/unitary-tests/core/DBObject/DBObjectSetTest.php @@ -60,6 +60,15 @@ class DBObjectSetTest extends ItopDataTestCase } public function testDBObjectSetComparator() + { + $oSearch = DBObjectSearch::FromOQL_AllData("SELECT UserRequest"); + $DBObjectSet1 = new DBObjectSet($oSearch); + $DBObjectSet3 = new DBObjectSet($oSearch); + $oDBObjectSetComparator = new DBObjectSetComparator($DBObjectSet1, $DBObjectSet3); + $this->assertTrue($oDBObjectSetComparator->SetsAreEquivalent()); + } + + public function testDBObjectSetComparator_CheckCache() { $oSearch = DBObjectSearch::FromOQL_AllData("SELECT UserRequest"); $DBObjectSet1 = new DBObjectSet($oSearch); @@ -77,4 +86,53 @@ class DBObjectSetTest extends ItopDataTestCase $this->expectExceptionMessage($sMsg, "should call DB again this time"); $this->assertTrue($oDBObjectSetComparator->SetsAreEquivalent()); } + + public static function JeffreyProvider() + { + return [ + 'basic' => [false], + 'opt trick' => [true], + ]; + } + + /** + * @dataProvider JeffreyProvider + */ + public function testJeffrey(bool $bTrick) + { + echo '

-------

'; + + $this->doesNotPerformAssertions(); + $iMax = 100; + + + $oFilter = DBObjectSearch::FromOQL_AllData('SELECT UserRequest'); + $oSet = new DBObjectSet($oFilter); + $oSet->OptimizeColumnLoad([ + 'UserRequest' => ['ref', 'status', 'title'], + ]); + + if ($bTrick) { + $oNewSet = DBObjectSet::FromScratch($oSet->GetClass()); + while ($oObj = $oSet->Fetch()) { + $oNewSet->AddObject($oObj); + } + $oSet = $oNewSet; + } + + echo '

Start: '.date('Y-m-d H:i:s').'

'; + $i = 0; + while ($i < $iMax) { + $oSet->Rewind(); + while ($oObj = $oSet->Fetch()) { + // Do nothing + $s = $oObj->Get('title'); + } + + $i += 1; + } + + $peak = memory_get_peak_usage(true) / 1000000; + echo '

End: '.date('Y-m-d H:i:s').'

Peak memory: '.$peak.'

\n'; + } }