From b7fc2e40122096890edcbfb6cea7eb620ce7b8f9 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 22 Mar 2017 20:53:40 +0000 Subject: [PATCH] N.718 Audit failing with message "Attempting to merge a filter of class A with a filter of class B" (regression introduced in branch 2.3). There are circumstances for which the queries cannot (yet) be optimized (fallback to the original algorithm) SVN:trunk[4611] --- core/dbobjectsearch.class.php | 70 ++++++++++++++++++++++++----------- test/testlist.inc.php | 68 +++++++++++++++++++++++++++++++++- 2 files changed, 114 insertions(+), 24 deletions(-) diff --git a/core/dbobjectsearch.class.php b/core/dbobjectsearch.class.php index 26acfa5af..7a3d4294a 100644 --- a/core/dbobjectsearch.class.php +++ b/core/dbobjectsearch.class.php @@ -1,5 +1,5 @@ m_aReferencedBy[$this->GetClass()][$sExtKeyAttCode][$iOperatorCode])) { - // Optimization - fold sibling query - $oRemoteFilter = $oFilter->m_aReferencedBy[$this->GetClass()][$sExtKeyAttCode][$iOperatorCode][0]; - $aAliasTranslation = array(); - $this->MergeWith_InNamespace($oRemoteFilter, $this->m_aClasses, $aAliasTranslation); - unset($oFilter->m_aReferencedBy[$this->GetClass()][$sExtKeyAttCode][$iOperatorCode]); - $this->m_oSearchCondition = $this->m_oSearchCondition->Translate($aAliasTranslation, false, false); - $this->UpdateRealiasingMap($aRealiasingMap, $aAliasTranslation); + foreach ($oFilter->m_aReferencedBy[$this->GetClass()][$sExtKeyAttCode][$iOperatorCode] as $oRemoteFilter) + { + if ($this->GetClass() == $oRemoteFilter->GetClass()) + { + // Optimization - fold sibling query + $aAliasTranslation = array(); + $this->MergeWith_InNamespace($oRemoteFilter, $this->m_aClasses, $aAliasTranslation); + unset($oFilter->m_aReferencedBy[$this->GetClass()][$sExtKeyAttCode][$iOperatorCode]); + $this->m_oSearchCondition = $this->m_oSearchCondition->Translate($aAliasTranslation, false, false); + $this->UpdateRealiasingMap($aRealiasingMap, $aAliasTranslation); + } + } } } $this->RecomputeClassList($this->m_aClasses); @@ -767,12 +772,20 @@ class DBObjectSearch extends DBSearch // Find the node on which the new tree must be attached (most of the time it is "this") $oReceivingFilter = $this->GetNode($this->GetClassAlias()); + $bMerged = false; if (ENABLE_OPT && isset($oReceivingFilter->m_aPointingTo[$sExtKeyAttCode][$iOperatorCode])) { - $oExisting = $oReceivingFilter->m_aPointingTo[$sExtKeyAttCode][$iOperatorCode][0]; - $oExisting->MergeWith_InNamespace($oFilter, $oExisting->m_aClasses, $aAliasTranslation); + foreach ($oReceivingFilter->m_aPointingTo[$sExtKeyAttCode][$iOperatorCode] as $oExisting) + { + if ($oExisting->GetClass() == $oFilter->GetClass()) + { + $oExisting->MergeWith_InNamespace($oFilter, $oExisting->m_aClasses, $aAliasTranslation); + $bMerged = true; + break; + } + } } - else + if (!$bMerged) { $oFilter->AddToNamespace($aClassAliases, $aAliasTranslation); $oReceivingFilter->m_aPointingTo[$sExtKeyAttCode][$iOperatorCode][] = $oFilter; @@ -814,13 +827,18 @@ class DBObjectSearch extends DBSearch { if (isset($oFilter->m_aPointingTo[$sForeignExtKeyAttCode][$iOperatorCode])) { - // Optimization - fold sibling query - $oRemoteFilter = $oFilter->m_aPointingTo[$sForeignExtKeyAttCode][$iOperatorCode][0]; - $aAliasTranslation = array(); - $this->MergeWith_InNamespace($oRemoteFilter, $this->m_aClasses, $aAliasTranslation); - unset($oFilter->m_aPointingTo[$sForeignExtKeyAttCode][$iOperatorCode]); - $this->m_oSearchCondition = $this->m_oSearchCondition->Translate($aAliasTranslation, false, false); - $this->UpdateRealiasingMap($aRealiasingMap, $aAliasTranslation); + foreach ($oFilter->m_aPointingTo[$sForeignExtKeyAttCode][$iOperatorCode] as $oRemoteFilter) + { + if ($this->GetClass() == $oRemoteFilter->GetClass()) + { + // Optimization - fold sibling query + $aAliasTranslation = array(); + $this->MergeWith_InNamespace($oRemoteFilter, $this->m_aClasses, $aAliasTranslation); + unset($oFilter->m_aPointingTo[$sForeignExtKeyAttCode][$iOperatorCode]); + $this->m_oSearchCondition = $this->m_oSearchCondition->Translate($aAliasTranslation, false, false); + $this->UpdateRealiasingMap($aRealiasingMap, $aAliasTranslation); + } + } } } $this->RecomputeClassList($this->m_aClasses); @@ -834,12 +852,20 @@ class DBObjectSearch extends DBSearch // Find the node on which the new tree must be attached (most of the time it is "this") $oReceivingFilter = $this->GetNode($this->GetClassAlias()); + $bMerged = false; if (ENABLE_OPT && isset($oReceivingFilter->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode][$iOperatorCode])) { - $oExisting = $oReceivingFilter->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode][$iOperatorCode][0]; - $oExisting->MergeWith_InNamespace($oFilter, $oExisting->m_aClasses, $aAliasTranslation); + foreach ($oReceivingFilter->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode][$iOperatorCode] as $oExisting) + { + if ($oExisting->GetClass() == $oFilter->GetClass()) + { + $oExisting->MergeWith_InNamespace($oFilter, $oExisting->m_aClasses, $aAliasTranslation); + $bMerged = true; + break; + } + } } - else + if (!$bMerged) { $oFilter->AddToNamespace($aClassAliases, $aAliasTranslation); $oReceivingFilter->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode][$iOperatorCode][] = $oFilter; diff --git a/test/testlist.inc.php b/test/testlist.inc.php index 8fa08ca55..20694f37c 100644 --- a/test/testlist.inc.php +++ b/test/testlist.inc.php @@ -5309,7 +5309,7 @@ class TestIntersectOptimization5 extends TestBizModel protected function DoExecute() { - echo "

Here we are (conluding a long series of tests)

\n"; + echo "

Here we are (concluding a long series of tests)

\n"; $sQueryA = 'SELECT Organization AS o JOIN UserRequest AS r ON r.org_id = o.id JOIN Person AS p ON r.caller_id = p.id WHERE o.name LIKE "Company" AND r.service_id = 123 AND p.employee_number LIKE "007"'; $sQueryB = 'SELECT UserRequest AS ur JOIN Person AS p ON ur.agent_id = p.id WHERE p.status != "terminated"'; @@ -5318,7 +5318,7 @@ class TestIntersectOptimization5 extends TestBizModel $oSearchA = DBSearch::FromOQL($sQueryA); $oSearchB = DBSearch::FromOQL($sQueryB); $oSearchB->AddCondition_PointingTo($oSearchA, 'org_id'); - echo "

Referenced by...: ".htmlentities($oSearchB->ToOQL(), ENT_QUOTES, 'UTF-8')."

\n"; + echo "

Pointing to...: ".htmlentities($oSearchB->ToOQL(), ENT_QUOTES, 'UTF-8')."

\n"; CMDBSource::TestQuery($oSearchB->MakeSelectQuery()); echo "

Successfully tested the SQL query.

\n"; } @@ -5454,3 +5454,67 @@ class TestImplicitAlias extends TestBizModel } } } + +class TestIntersectNotOptimized extends TestBizModel +{ + static public function GetName() + { + return 'Internal query NOT optimized'; + } + + static public function GetDescription() + { + return '(N.718) Sometimes, the optimization CANNOT be performed because merging two different classes (same branch) is not implemented'; + } + + protected function DoExecute() + { + echo "

Intersect NOT optimized on 'pointing to'

\n"; + $sBaseQuery = 'SELECT lnkContactToFunctionalCI AS l JOIN Contact AS c ON l.contact_id = c.id'; + $sOQL = 'SELECT lnkContactToFunctionalCI AS l JOIN Person AS p ON l.contact_id = p.id'; + echo "
Left: ".htmlentities($sBaseQuery, ENT_QUOTES, 'UTF-8')."
\n"; + echo "
Right: ".htmlentities($sOQL, ENT_QUOTES, 'UTF-8')."
\n"; + $oSearchA = DBSearch::FromOQL($sBaseQuery); + $oSearchB = DBSearch::FromOQL($sOQL); + $oIntersect = $oSearchA->Intersect($oSearchB); + echo "

Intersect: ".htmlentities($oIntersect->ToOQL(), ENT_QUOTES, 'UTF-8')."

\n"; + CMDBSource::TestQuery($oIntersect->MakeSelectQuery()); + echo "

Successfully tested the SQL query.

\n"; + + echo "

Intersect NOT optimized on 'referenced by'

\n"; + $sBaseQuery = 'SELECT Organization AS o JOIN Contact AS c ON c.org_id = o.id WHERE c.id = 1'; + $sOQL = 'SELECT Organization AS o JOIN Person AS p ON p.org_id = o.id WHERE p.id = 2'; + echo "
Left: ".htmlentities($sBaseQuery, ENT_QUOTES, 'UTF-8')."
\n"; + echo "
Right: ".htmlentities($sOQL, ENT_QUOTES, 'UTF-8')."
\n"; + $oSearchA = DBSearch::FromOQL($sBaseQuery); + $oSearchB = DBSearch::FromOQL($sOQL); + $oIntersect = $oSearchA->Intersect($oSearchB); + echo "

Intersect: ".htmlentities($oIntersect->ToOQL(), ENT_QUOTES, 'UTF-8')."

\n"; + CMDBSource::TestQuery($oIntersect->MakeSelectQuery()); + echo "

Successfully tested the SQL query.

\n"; + + echo "

NOT Folding on AddCondition_PointingTo

\n"; + $sQueryA = 'SELECT Organization AS o JOIN Contact AS c ON c.org_id = o.id'; + $sQueryB = 'SELECT Person'; + echo "
A: ".htmlentities($sQueryA, ENT_QUOTES, 'UTF-8')."
\n"; + echo "
B: ".htmlentities($sQueryB, ENT_QUOTES, 'UTF-8')."
\n"; + $oSearchA = DBSearch::FromOQL($sQueryA); + $oSearchB = DBSearch::FromOQL($sQueryB); + $oSearchB->AddCondition_PointingTo($oSearchA, 'org_id'); + echo "

Pointing to...: ".htmlentities($oSearchB->ToOQL(), ENT_QUOTES, 'UTF-8')."

\n"; + CMDBSource::TestQuery($oSearchB->MakeSelectQuery()); + echo "

Successfully tested the SQL query.

\n"; + + echo "

NOT Folding on AddCondition_ReferencedBy

\n"; + $sQueryA = 'SELECT lnkContactToFunctionalCI AS l JOIN Contact AS c ON l.contact_id = c.id'; + $sQueryB = 'SELECT Person'; + echo "
A: ".htmlentities($sQueryA, ENT_QUOTES, 'UTF-8')."
\n"; + echo "
B: ".htmlentities($sQueryB, ENT_QUOTES, 'UTF-8')."
\n"; + $oSearchA = DBSearch::FromOQL($sQueryA); + $oSearchB = DBSearch::FromOQL($sQueryB); + $oSearchB->AddCondition_ReferencedBy($oSearchA, 'contact_id'); + echo "

Referenced by...: ".htmlentities($oSearchA->ToOQL(), ENT_QUOTES, 'UTF-8')."

\n"; + CMDBSource::TestQuery($oSearchA->MakeSelectQuery()); + echo "

Successfully tested the SQL query.

\n"; + } +}