mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-13 07:24:13 +01:00
N°1888 - Filter search with another search
This commit is contained in:
@@ -873,6 +873,15 @@ class DBObjectSearch extends DBSearch
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DBObjectSearch $oFilter
|
||||
* @param string $sExtKeyAttCode
|
||||
* @param array $aClassAliases
|
||||
* @param array $aAliasTranslation
|
||||
* @param int $iOperatorCode
|
||||
*
|
||||
* @throws \CoreException
|
||||
*/
|
||||
protected function AddCondition_PointingTo_InNameSpace(DBObjectSearch $oFilter, $sExtKeyAttCode, &$aClassAliases, &$aAliasTranslation, $iOperatorCode)
|
||||
{
|
||||
// Find the node on which the new tree must be attached (most of the time it is "this")
|
||||
@@ -883,6 +892,7 @@ class DBObjectSearch extends DBSearch
|
||||
{
|
||||
foreach ($oReceivingFilter->m_aPointingTo[$sExtKeyAttCode][$iOperatorCode] as $oExisting)
|
||||
{
|
||||
/** @var DBObjectSearch $oExisting */
|
||||
if ($oExisting->GetClass() == $oFilter->GetClass())
|
||||
{
|
||||
$oExisting->MergeWith_InNamespace($oFilter, $oExisting->m_aClasses, $aAliasTranslation);
|
||||
@@ -953,7 +963,7 @@ class DBObjectSearch extends DBSearch
|
||||
$this->RecomputeClassList($this->m_aClasses);
|
||||
}
|
||||
|
||||
protected function AddCondition_ReferencedBy_InNameSpace(DBSearch $oFilter, $sForeignExtKeyAttCode, &$aClassAliases, &$aAliasTranslation, $iOperatorCode)
|
||||
protected function AddCondition_ReferencedBy_InNameSpace(DBObjectSearch $oFilter, $sForeignExtKeyAttCode, &$aClassAliases, &$aAliasTranslation, $iOperatorCode)
|
||||
{
|
||||
$sForeignClass = $oFilter->GetClass();
|
||||
|
||||
@@ -980,10 +990,115 @@ class DBObjectSearch extends DBSearch
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter this search with another search.
|
||||
* Initial search is unmodified.
|
||||
* The difference with Intersect, is that an alias can be provided,
|
||||
* the filtered class does not need to be the first joined class.
|
||||
*
|
||||
* @param string $sClassAlias class being filtered
|
||||
* @param DBSearch $oFilter Filter to apply
|
||||
*
|
||||
* @return DBSearch The filtered search
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public function Filter($sClassAlias, DBSearch $oFilter)
|
||||
{
|
||||
// If the conditions are the correct ones for Intersect
|
||||
if (($this->GetFirstJoinedClassAlias() == $sClassAlias))
|
||||
{
|
||||
return $this->Intersect($oFilter);
|
||||
}
|
||||
|
||||
/** @var \DBObjectSearch $oFilteredSearch */
|
||||
$oFilteredSearch = $this->DeepClone();
|
||||
$oFilterExpression = self::FilterSubClass($oFilteredSearch, $sClassAlias, $oFilter, $this->m_aClasses);
|
||||
if ($oFilterExpression === false)
|
||||
{
|
||||
throw new CoreException("Limitation: cannot filter search");
|
||||
}
|
||||
|
||||
$oFilteredSearch->AddConditionExpression($oFilterExpression);
|
||||
|
||||
return $oFilteredSearch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter "in place" the search (filtered part is replaced in the initial search)
|
||||
*
|
||||
* @param DBObjectSearch $oSearch Search to filter, modified with the given filter
|
||||
* @param string $sClassAlias class to filter
|
||||
* @param \DBSearch $oFilter Filter to apply
|
||||
*
|
||||
* @return \Expression|false
|
||||
* @throws \CoreException
|
||||
*/
|
||||
private static function FilterSubClass(DBObjectSearch &$oSearch, $sClassAlias, DBSearch $oFilter, $aRootClasses)
|
||||
{
|
||||
if (($oSearch->GetFirstJoinedClassAlias() == $sClassAlias))
|
||||
{
|
||||
$oSearch->ResetCondition();
|
||||
$oSearch = $oSearch->IntersectSubClass($oFilter, $aRootClasses);
|
||||
return $oSearch->GetCriteria();
|
||||
}
|
||||
|
||||
/** @var Expression $oFilterExpression */
|
||||
// Search in the filter tree where is the correct DBSearch
|
||||
foreach ($oSearch->m_aPointingTo as $sExtKey => $aPointingTo)
|
||||
{
|
||||
foreach ($aPointingTo as $iOperatorCode => $aFilters)
|
||||
{
|
||||
foreach ($aFilters as $index => $oExtFilter)
|
||||
{
|
||||
$oFilterExpression = self::FilterSubClass($oExtFilter, $sClassAlias, $oFilter, $aRootClasses);
|
||||
if ($oFilterExpression !== false)
|
||||
{
|
||||
$oSearch->m_aPointingTo[$sExtKey][$iOperatorCode][$index] = $oExtFilter;
|
||||
return $oFilterExpression;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach($oSearch->m_aReferencedBy as $sForeignClass => $aReferences)
|
||||
{
|
||||
foreach($aReferences as $sForeignExtKeyAttCode => $aFiltersByOperator)
|
||||
{
|
||||
foreach ($aFiltersByOperator as $iOperatorCode => $aFilters)
|
||||
{
|
||||
foreach ($aFilters as $index => $oForeignFilter)
|
||||
{
|
||||
$oFilterExpression = self::FilterSubClass($oForeignFilter, $sClassAlias, $oFilter, $aRootClasses);
|
||||
if ($oFilterExpression !== false)
|
||||
{
|
||||
$oSearch->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode][$iOperatorCode][$index] = $oForeignFilter;
|
||||
return $oFilterExpression;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public function Intersect(DBSearch $oFilter)
|
||||
{
|
||||
return $this->IntersectSubClass($oFilter, $this->m_aClasses);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DBSearch $oFilter
|
||||
* @param array $aRootClasses classes of the root search (for aliases)
|
||||
*
|
||||
* @return \DBUnionSearch|mixed
|
||||
* @throws \CoreException
|
||||
*/
|
||||
protected function IntersectSubClass(DBSearch $oFilter, $aRootClasses)
|
||||
{
|
||||
if ($oFilter instanceof DBUnionSearch)
|
||||
{
|
||||
@@ -999,15 +1114,12 @@ class DBObjectSearch extends DBSearch
|
||||
foreach ($aFilters as $oRightFilter)
|
||||
{
|
||||
// Limitation: the queried class must be the first declared class
|
||||
if ($this->GetFirstJoinedClassAlias() != $this->GetClassAlias())
|
||||
{
|
||||
throw new CoreException("Limitation: cannot merge two queries if the queried class ({$this->GetClass()} AS {$this->GetClassAlias()}) is not the first joined class ({$this->GetFirstJoinedClass()} AS {$this->GetFirstJoinedClassAlias()})");
|
||||
}
|
||||
if ($oRightFilter->GetFirstJoinedClassAlias() != $oRightFilter->GetClassAlias())
|
||||
{
|
||||
throw new CoreException("Limitation: cannot merge two queries if the queried class ({$oRightFilter->GetClass()} AS {$oRightFilter->GetClassAlias()}) is not the first joined class ({$oRightFilter->GetFirstJoinedClass()} AS {$oRightFilter->GetFirstJoinedClassAlias()})");
|
||||
}
|
||||
|
||||
/** @var \DBObjectSearch $oLeftFilter */
|
||||
$oLeftFilter = $this->DeepClone();
|
||||
$oRightFilter = $oRightFilter->DeepClone();
|
||||
|
||||
@@ -1017,14 +1129,14 @@ class DBObjectSearch extends DBSearch
|
||||
$oLeftFilter->AllowAllData();
|
||||
}
|
||||
|
||||
if ($oLeftFilter->GetClass() != $oRightFilter->GetClass())
|
||||
if ($oLeftFilter->GetFirstJoinedClass() != $oRightFilter->GetClass())
|
||||
{
|
||||
if (MetaModel::IsParentClass($oLeftFilter->GetClass(), $oRightFilter->GetClass()))
|
||||
if (MetaModel::IsParentClass($oLeftFilter->GetFirstJoinedClass(), $oRightFilter->GetClass()))
|
||||
{
|
||||
// Specialize $oLeftFilter
|
||||
$oLeftFilter->ChangeClass($oRightFilter->GetClass());
|
||||
$oLeftFilter->ChangeClass($oRightFilter->GetClass(), $oLeftFilter->GetFirstJoinedClassAlias());
|
||||
}
|
||||
elseif (MetaModel::IsParentClass($oRightFilter->GetClass(), $oLeftFilter->GetClass()))
|
||||
elseif (MetaModel::IsParentClass($oRightFilter->GetFirstJoinedClass(), $oLeftFilter->GetClass()))
|
||||
{
|
||||
// Specialize $oRightFilter
|
||||
$oRightFilter->ChangeClass($oLeftFilter->GetClass());
|
||||
@@ -1036,7 +1148,7 @@ class DBObjectSearch extends DBSearch
|
||||
}
|
||||
|
||||
$aAliasTranslation = array();
|
||||
$oLeftFilter->MergeWith_InNamespace($oRightFilter, $oLeftFilter->m_aClasses, $aAliasTranslation);
|
||||
$oLeftFilter->MergeWith_InNamespace($oRightFilter, $aRootClasses, $aAliasTranslation);
|
||||
$oLeftFilter->TransferConditionExpression($oRightFilter, $aAliasTranslation);
|
||||
$aSearches[] = $oLeftFilter;
|
||||
}
|
||||
@@ -1051,15 +1163,22 @@ class DBObjectSearch extends DBSearch
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DBObjectSearch $oFilter
|
||||
* @param array $aClassAliases
|
||||
* @param array $aAliasTranslation
|
||||
*
|
||||
* @throws CoreException
|
||||
*/
|
||||
protected function MergeWith_InNamespace($oFilter, &$aClassAliases, &$aAliasTranslation)
|
||||
{
|
||||
if ($this->GetClass() != $oFilter->GetClass())
|
||||
if ($this->GetFirstJoinedClass() != $oFilter->GetClass())
|
||||
{
|
||||
throw new CoreException("Attempting to merge a filter of class '{$this->GetClass()}' with a filter of class '{$oFilter->GetClass()}'");
|
||||
throw new CoreException("Attempting to merge a filter of class '{$this->GetFirstJoinedClass()}' with a filter of class '{$oFilter->GetClass()}'");
|
||||
}
|
||||
|
||||
// Translate search condition into our aliasing scheme
|
||||
$aAliasTranslation[$oFilter->GetClassAlias()]['*'] = $this->GetClassAlias();
|
||||
$aAliasTranslation[$oFilter->GetClassAlias()]['*'] = $this->GetFirstJoinedClassAlias();
|
||||
|
||||
foreach($oFilter->m_aPointingTo as $sExtKeyAttCode=>$aPointingTo)
|
||||
{
|
||||
|
||||
@@ -433,11 +433,27 @@ abstract class DBSearch
|
||||
*/
|
||||
abstract public function AddCondition_ReferencedBy(DBObjectSearch $oFilter, $sForeignExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS, &$aRealiasingMap = null);
|
||||
|
||||
/**
|
||||
/**
|
||||
* Filter this search with another search.
|
||||
* Initial search is unmodified.
|
||||
* The difference with Intersect, is that an alias can be provided,
|
||||
* the filtered class does not need to be the first joined class,
|
||||
* it can be any class of the search.
|
||||
*
|
||||
* @param string $sClassAlias class being filtered
|
||||
* @param DBSearch $oFilter Filter to apply
|
||||
*
|
||||
* @return DBSearch The filtered search
|
||||
* @throws \CoreException
|
||||
*/
|
||||
abstract public function Filter($sClassAlias, DBSearch $oFilter);
|
||||
|
||||
/**
|
||||
* Filter the result
|
||||
*
|
||||
* The filter is performed by returning only the values in common with the given $oFilter
|
||||
* The impact on the resulting query performance/viability can be significant.
|
||||
* Only the first joined class can be filtered.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
@@ -1087,7 +1103,7 @@ abstract class DBSearch
|
||||
$oSearch = $this;
|
||||
if (!$this->IsAllDataAllowed() && !$this->IsDataFiltered())
|
||||
{
|
||||
foreach ($this->GetSelectedClasses() as $sClass)
|
||||
foreach ($this->GetSelectedClasses() as $sClassAlias => $sClass)
|
||||
{
|
||||
$oVisibleObjects = UserRights::GetSelectFilter($sClass, $this->GetModifierProperties('UserRightsGetSelectFilter'));
|
||||
if ($oVisibleObjects === false)
|
||||
@@ -1098,7 +1114,7 @@ abstract class DBSearch
|
||||
if (is_object($oVisibleObjects))
|
||||
{
|
||||
$oVisibleObjects->AllowAllData();
|
||||
$oSearch = $this->Intersect($oVisibleObjects);
|
||||
$oSearch = $this->Filter($sClassAlias, $oVisibleObjects);
|
||||
/** @var DBSearch $oSearch */
|
||||
$oSearch->SetDataFiltered();
|
||||
}
|
||||
|
||||
@@ -383,6 +383,16 @@ class DBUnionSearch extends DBSearch
|
||||
}
|
||||
}
|
||||
|
||||
public function Filter($sClassAlias, DBSearch $oFilter)
|
||||
{
|
||||
$aSearches = array();
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$aSearches[] = $oSearch->Filter($sClassAlias, $oFilter);
|
||||
}
|
||||
return new DBUnionSearch($aSearches);
|
||||
}
|
||||
|
||||
public function Intersect(DBSearch $oFilter)
|
||||
{
|
||||
$aSearches = array();
|
||||
|
||||
@@ -955,7 +955,7 @@ class DBObjectSearch extends DBSearch
|
||||
$this->RecomputeClassList($this->m_aClasses);
|
||||
}
|
||||
|
||||
protected function AddCondition_ReferencedBy_InNameSpace(DBSearch $oFilter, $sForeignExtKeyAttCode, &$aClassAliases, &$aAliasTranslation, $iOperatorCode)
|
||||
protected function AddCondition_ReferencedBy_InNameSpace(DBObjectSearch $oFilter, $sForeignExtKeyAttCode, &$aClassAliases, &$aAliasTranslation, $iOperatorCode)
|
||||
{
|
||||
$sForeignClass = $oFilter->GetClass();
|
||||
|
||||
@@ -982,10 +982,115 @@ class DBObjectSearch extends DBSearch
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter this search with another search.
|
||||
* Initial search is unmodified.
|
||||
* The difference with Intersect, is that an alias can be provided,
|
||||
* the filtered class does not need to be the first joined class.
|
||||
*
|
||||
* @param string $sClassAlias class being filtered
|
||||
* @param DBSearch $oFilter Filter to apply
|
||||
*
|
||||
* @return DBSearch The filtered search
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public function Filter($sClassAlias, DBSearch $oFilter)
|
||||
{
|
||||
// If the conditions are the correct ones for Intersect
|
||||
if (($this->GetFirstJoinedClassAlias() == $sClassAlias))
|
||||
{
|
||||
return $this->Intersect($oFilter);
|
||||
}
|
||||
|
||||
/** @var \DBObjectSearch $oFilteredSearch */
|
||||
$oFilteredSearch = $this->DeepClone();
|
||||
$oFilterExpression = self::FilterSubClass($oFilteredSearch, $sClassAlias, $oFilter, $this->m_aClasses);
|
||||
if ($oFilterExpression === false)
|
||||
{
|
||||
throw new CoreException("Limitation: cannot filter search");
|
||||
}
|
||||
|
||||
$oFilteredSearch->AddConditionExpression($oFilterExpression);
|
||||
|
||||
return $oFilteredSearch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter "in place" the search (filtered part is replaced in the initial search)
|
||||
*
|
||||
* @param DBObjectSearch $oSearch Search to filter, modified with the given filter
|
||||
* @param string $sClassAlias class to filter
|
||||
* @param \DBSearch $oFilter Filter to apply
|
||||
*
|
||||
* @return \Expression|false
|
||||
* @throws \CoreException
|
||||
*/
|
||||
private static function FilterSubClass(DBObjectSearch &$oSearch, $sClassAlias, DBSearch $oFilter, $aRootClasses)
|
||||
{
|
||||
if (($oSearch->GetFirstJoinedClassAlias() == $sClassAlias))
|
||||
{
|
||||
$oSearch->ResetCondition();
|
||||
$oSearch = $oSearch->IntersectSubClass($oFilter, $aRootClasses);
|
||||
return $oSearch->GetCriteria();
|
||||
}
|
||||
|
||||
/** @var Expression $oFilterExpression */
|
||||
// Search in the filter tree where is the correct DBSearch
|
||||
foreach ($oSearch->m_aPointingTo as $sExtKey => $aPointingTo)
|
||||
{
|
||||
foreach ($aPointingTo as $iOperatorCode => $aFilters)
|
||||
{
|
||||
foreach ($aFilters as $index => $oExtFilter)
|
||||
{
|
||||
$oFilterExpression = self::FilterSubClass($oExtFilter, $sClassAlias, $oFilter, $aRootClasses);
|
||||
if ($oFilterExpression !== false)
|
||||
{
|
||||
$oSearch->m_aPointingTo[$sExtKey][$iOperatorCode][$index] = $oExtFilter;
|
||||
return $oFilterExpression;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach($oSearch->m_aReferencedBy as $sForeignClass => $aReferences)
|
||||
{
|
||||
foreach($aReferences as $sForeignExtKeyAttCode => $aFiltersByOperator)
|
||||
{
|
||||
foreach ($aFiltersByOperator as $iOperatorCode => $aFilters)
|
||||
{
|
||||
foreach ($aFilters as $index => $oForeignFilter)
|
||||
{
|
||||
$oFilterExpression = self::FilterSubClass($oForeignFilter, $sClassAlias, $oFilter, $aRootClasses);
|
||||
if ($oFilterExpression !== false)
|
||||
{
|
||||
$oSearch->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode][$iOperatorCode][$index] = $oForeignFilter;
|
||||
return $oFilterExpression;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public function Intersect(DBSearch $oFilter)
|
||||
{
|
||||
return $this->IntersectSubClass($oFilter, $this->m_aClasses);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DBSearch $oFilter
|
||||
* @param array $aRootClasses classes of the root search (for aliases)
|
||||
*
|
||||
* @return \DBUnionSearch|mixed
|
||||
* @throws \CoreException
|
||||
*/
|
||||
protected function IntersectSubClass(DBSearch $oFilter, $aRootClasses)
|
||||
{
|
||||
if ($oFilter instanceof DBUnionSearch)
|
||||
{
|
||||
@@ -1001,15 +1106,12 @@ class DBObjectSearch extends DBSearch
|
||||
foreach ($aFilters as $oRightFilter)
|
||||
{
|
||||
// Limitation: the queried class must be the first declared class
|
||||
if ($this->GetFirstJoinedClassAlias() != $this->GetClassAlias())
|
||||
{
|
||||
throw new CoreException("Limitation: cannot merge two queries if the queried class ({$this->GetClass()} AS {$this->GetClassAlias()}) is not the first joined class ({$this->GetFirstJoinedClass()} AS {$this->GetFirstJoinedClassAlias()})");
|
||||
}
|
||||
if ($oRightFilter->GetFirstJoinedClassAlias() != $oRightFilter->GetClassAlias())
|
||||
{
|
||||
throw new CoreException("Limitation: cannot merge two queries if the queried class ({$oRightFilter->GetClass()} AS {$oRightFilter->GetClassAlias()}) is not the first joined class ({$oRightFilter->GetFirstJoinedClass()} AS {$oRightFilter->GetFirstJoinedClassAlias()})");
|
||||
}
|
||||
|
||||
/** @var \DBObjectSearch $oLeftFilter */
|
||||
$oLeftFilter = $this->DeepClone();
|
||||
$oRightFilter = $oRightFilter->DeepClone();
|
||||
|
||||
@@ -1019,14 +1121,14 @@ class DBObjectSearch extends DBSearch
|
||||
$oLeftFilter->AllowAllData();
|
||||
}
|
||||
|
||||
if ($oLeftFilter->GetClass() != $oRightFilter->GetClass())
|
||||
if ($oLeftFilter->GetFirstJoinedClass() != $oRightFilter->GetClass())
|
||||
{
|
||||
if (MetaModel::IsParentClass($oLeftFilter->GetClass(), $oRightFilter->GetClass()))
|
||||
if (MetaModel::IsParentClass($oLeftFilter->GetFirstJoinedClass(), $oRightFilter->GetClass()))
|
||||
{
|
||||
// Specialize $oLeftFilter
|
||||
$oLeftFilter->ChangeClass($oRightFilter->GetClass());
|
||||
$oLeftFilter->ChangeClass($oRightFilter->GetClass(), $oLeftFilter->GetFirstJoinedClassAlias());
|
||||
}
|
||||
elseif (MetaModel::IsParentClass($oRightFilter->GetClass(), $oLeftFilter->GetClass()))
|
||||
elseif (MetaModel::IsParentClass($oRightFilter->GetFirstJoinedClass(), $oLeftFilter->GetClass()))
|
||||
{
|
||||
// Specialize $oRightFilter
|
||||
$oRightFilter->ChangeClass($oLeftFilter->GetClass());
|
||||
@@ -1038,7 +1140,7 @@ class DBObjectSearch extends DBSearch
|
||||
}
|
||||
|
||||
$aAliasTranslation = array();
|
||||
$oLeftFilter->MergeWith_InNamespace($oRightFilter, $oLeftFilter->m_aClasses, $aAliasTranslation);
|
||||
$oLeftFilter->MergeWith_InNamespace($oRightFilter, $aRootClasses, $aAliasTranslation);
|
||||
$oLeftFilter->TransferConditionExpression($oRightFilter, $aAliasTranslation);
|
||||
$aSearches[] = $oLeftFilter;
|
||||
}
|
||||
|
||||
414
test/core/DBSearchIntersectTest.php
Normal file
414
test/core/DBSearchIntersectTest.php
Normal file
@@ -0,0 +1,414 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Core;
|
||||
|
||||
use CMDBSource;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use DBSearch;
|
||||
|
||||
/**
|
||||
* Class DBSearchIntersectTest
|
||||
*
|
||||
* @package Combodo\iTop\Test\UnitTest\Core
|
||||
*
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
*/
|
||||
class DBSearchIntersectTest extends ItopDataTestCase
|
||||
{
|
||||
const CREATE_TEST_ORG = false;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
require_once(APPROOT.'application/startup.inc.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider FilterProvider
|
||||
*
|
||||
* @param $sLeftSelect
|
||||
* @param $sRightSelect
|
||||
* @param $sClassAlias
|
||||
* @param $sResult
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \MissingQueryArgument
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function testFilter($sLeftSelect, $sRightSelect, $sClassAlias, $sResult)
|
||||
{
|
||||
$oLeftSearch = DBSearch::FromOQL($sLeftSelect);
|
||||
$oRightSearch = DBSearch::FromOQL($sRightSelect);
|
||||
|
||||
$oResultSearch = $oLeftSearch->Filter($sClassAlias, $oRightSearch);
|
||||
CMDBSource::TestQuery($oResultSearch->MakeSelectQuery());
|
||||
$this->assertEquals($sResult, $oResultSearch->ToOQL());
|
||||
}
|
||||
|
||||
public function FilterProvider()
|
||||
{
|
||||
$aTests = array();
|
||||
|
||||
$aTests['Multiple selected classes inverted'] = array(
|
||||
'left' => "SELECT `L`, `P` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id WHERE 1",
|
||||
'right' => "SELECT Person WHERE org_id = 3",
|
||||
'alias' => "P",
|
||||
'result' => "SELECT `L`, `P` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id WHERE (`P`.`org_id` = 3)");
|
||||
|
||||
$aTests['Multiple selected classes inverted 1'] = array(
|
||||
'left' => "SELECT `L`, `P`, `D` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id JOIN PC AS D ON D.location_id = L.id JOIN Person AS P2 ON P.manager_id = P2.id WHERE 1",
|
||||
'right' => "SELECT Location WHERE org_id = 3",
|
||||
'alias' => "L",
|
||||
'result' => "SELECT `L`, `P`, `D` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id JOIN PC AS `D` ON `D`.location_id = `L`.id JOIN Person AS `P2` ON `P`.manager_id = `P2`.id WHERE (`L`.`org_id` = 3)");
|
||||
|
||||
$aTests['Multiple selected classes inverted 2'] = array(
|
||||
'left' => "SELECT `L`, `P`, `D` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id JOIN PC AS D ON D.location_id = L.id JOIN Person AS P2 ON P.manager_id = P2.id WHERE (`L`.`org_id` = 3)",
|
||||
'right' => "SELECT Person WHERE org_id = 3",
|
||||
'alias' => "P",
|
||||
'result' => "SELECT `L`, `P`, `D` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id JOIN PC AS `D` ON `D`.location_id = `L`.id JOIN Person AS `P2` ON `P`.manager_id = `P2`.id WHERE ((`L`.`org_id` = 3) AND (`P`.`org_id` = 3))");
|
||||
|
||||
$aTests['Same class'] = array(
|
||||
'left' => "SELECT Contact WHERE name = 'Christie'",
|
||||
'right' => "SELECT Contact WHERE org_id = 3",
|
||||
'alias' => "Contact",
|
||||
'result' => "SELECT `Contact` FROM Contact AS `Contact` WHERE ((`Contact`.`name` = 'Christie') AND (`Contact`.`org_id` = 3))");
|
||||
|
||||
$aTests['Different Alias'] = array(
|
||||
'left' => "SELECT Contact AS C WHERE C.name = 'Christie'",
|
||||
'right' => "SELECT Contact AS CC WHERE CC.org_id = 3",
|
||||
'alias' => "C",
|
||||
'result' => "SELECT `C` FROM Contact AS `C` WHERE ((`C`.`name` = 'Christie') AND (`C`.`org_id` = 3))");
|
||||
|
||||
$aTests['Multiple selected classes'] = array(
|
||||
'left' => "SELECT `L`, `P` FROM Location AS `L` JOIN Person AS `P` ON `P`.location_id = `L`.id WHERE 1",
|
||||
'right' => "SELECT Location WHERE org_id = 3",
|
||||
'alias' => "L",
|
||||
'result' => "SELECT `L`, `P` FROM Location AS `L` JOIN Person AS `P` ON `P`.location_id = `L`.id WHERE (`L`.`org_id` = 3)");
|
||||
|
||||
$aTests['Joined classes'] = array(
|
||||
'left' => "SELECT `L` FROM Location AS `L` JOIN Person AS `P` ON `P`.location_id = `L`.id WHERE 1",
|
||||
'right' => "SELECT Person WHERE org_id = 3",
|
||||
'alias' => "P",
|
||||
'result' => "SELECT `L` FROM Location AS `L` JOIN Person AS `P` ON `P`.location_id = `L`.id WHERE (`P`.`org_id` = 3)");
|
||||
|
||||
$aTests['Joined filter'] = array(
|
||||
'left' => "SELECT `P` FROM Person AS `P` WHERE 1",
|
||||
'right' => "SELECT `P` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id WHERE `L`.org_id = 3",
|
||||
'alias' => "P",
|
||||
'result' => "SELECT `P` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id WHERE (`L`.`org_id` = 3)");
|
||||
|
||||
$aTests['Joined filter on joined classes'] = array(
|
||||
'left' => "SELECT `L` FROM Location AS `L` JOIN Person AS `P` ON `P`.location_id = `L`.id WHERE 1",
|
||||
'right' => "SELECT Person FROM Person AS Person JOIN Location ON Person.location_id = Location.id WHERE Location.org_id = 3",
|
||||
'alias' => "P",
|
||||
'result' => "SELECT `L` FROM Location AS `L` JOIN Person AS `P` ON `P`.location_id = `L`.id JOIN Location AS `Location` ON `P`.location_id = `Location`.id WHERE (`Location`.`org_id` = 3)");
|
||||
|
||||
$aTests['Alias collision'] = array(
|
||||
'left' => "SELECT `L` FROM Location AS `L` JOIN Person AS `P` ON `P`.location_id = `L`.id WHERE 1",
|
||||
'right' => "SELECT Person FROM Person AS Person JOIN Location AS `L` ON Person.location_id = `L`.id WHERE `L`.org_id = 3",
|
||||
'alias' => "P",
|
||||
'result' => "SELECT `L` FROM Location AS `L` JOIN Person AS `P` ON `P`.location_id = `L`.id JOIN Location AS `L1` ON `P`.location_id = `L1`.id WHERE (`L1`.`org_id` = 3)");
|
||||
|
||||
return $aTests;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @dataProvider IntersectProvider
|
||||
*
|
||||
* @param $sLeftSelect
|
||||
* @param $sRightSelect
|
||||
* @param $sResult
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \MissingQueryArgument
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function testIntersect($sLeftSelect, $sRightSelect, $sResult)
|
||||
{
|
||||
$oLeftSearch = DBSearch::FromOQL($sLeftSelect);
|
||||
$oRightSearch = DBSearch::FromOQL($sRightSelect);
|
||||
|
||||
$oResultSearch = $oLeftSearch->Intersect($oRightSearch);
|
||||
CMDBSource::TestQuery($oResultSearch->MakeSelectQuery());
|
||||
$this->assertEquals($sResult, $oResultSearch->ToOQL());
|
||||
}
|
||||
|
||||
public function IntersectProvider()
|
||||
{
|
||||
$aTests = array();
|
||||
|
||||
$aTests['Multiple selected classes inverted'] = array(
|
||||
'left' => "SELECT `L`, `P` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id WHERE 1",
|
||||
'right' => "SELECT Person WHERE org_id = 3",
|
||||
'result' => "SELECT `L`, `P` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id WHERE (`P`.`org_id` = 3)");
|
||||
|
||||
$aTests['Multiple selected classes inverted 2'] = array(
|
||||
'left' => "SELECT `L`, `P`, `D` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id JOIN PC AS D ON D.location_id = L.id JOIN Person AS P2 ON P.manager_id = P2.id WHERE (`L`.`org_id` = 3)",
|
||||
'right' => "SELECT Person WHERE org_id = 3",
|
||||
'result' => "SELECT `L`, `P`, `D` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id JOIN PC AS `D` ON `D`.location_id = `L`.id JOIN Person AS `P2` ON `P`.manager_id = `P2`.id WHERE ((`L`.`org_id` = 3) AND (`P`.`org_id` = 3))");
|
||||
|
||||
$aTests['Same class'] = array(
|
||||
'left' => "SELECT Contact WHERE name = 'Christie'",
|
||||
'right' => "SELECT Contact WHERE org_id = 3",
|
||||
'result' => "SELECT `Contact` FROM Contact AS `Contact` WHERE ((`Contact`.`name` = 'Christie') AND (`Contact`.`org_id` = 3))");
|
||||
|
||||
$aTests['Different Alias'] = array(
|
||||
'left' => "SELECT Contact AS C WHERE C.name = 'Christie'",
|
||||
'right' => "SELECT Contact AS CC WHERE CC.org_id = 3",
|
||||
'result' => "SELECT `C` FROM Contact AS `C` WHERE ((`C`.`name` = 'Christie') AND (`C`.`org_id` = 3))");
|
||||
|
||||
$aTests['Multiple selected classes'] = array(
|
||||
'left' => "SELECT `L`, `P` FROM Location AS `L` JOIN Person AS `P` ON `P`.location_id = `L`.id WHERE 1",
|
||||
'right' => "SELECT Location WHERE org_id = 3",
|
||||
'result' => "SELECT `L`, `P` FROM Location AS `L` JOIN Person AS `P` ON `P`.location_id = `L`.id WHERE (`L`.`org_id` = 3)");
|
||||
|
||||
$aTests['Alias collision'] = array(
|
||||
'left' => "SELECT `P` FROM Person AS `P` WHERE 1",
|
||||
'right' => "SELECT `Person` FROM Person AS `Person` JOIN Person AS `P` ON `P`.manager_id = `Person`.id WHERE `P`.org_id = 3",
|
||||
'result' => "SELECT `P` FROM Person AS `P` JOIN Person AS `P1` ON `P1`.manager_id = `P`.id WHERE (`P1`.`org_id` = 3)");
|
||||
|
||||
return $aTests;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \CoreException
|
||||
* @throws \MissingQueryArgument
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function testIntersectNotOptimizedPointingTo()
|
||||
{
|
||||
$sBaseQuery = "SELECT l FROM lnkContactToFunctionalCI AS l JOIN Contact AS c ON l.contact_id = c.id";
|
||||
$sOQL = "SELECT l FROM lnkContactToFunctionalCI AS l JOIN Person AS p ON l.contact_id = p.id";
|
||||
$sResult = "SELECT `l` FROM lnkContactToFunctionalCI AS `l` JOIN Contact AS `c` ON `l`.contact_id = `c`.id JOIN Person AS `p` ON `l`.contact_id = `p`.id WHERE 1";
|
||||
|
||||
$oSearchA = DBSearch::FromOQL($sBaseQuery);
|
||||
$oSearchB = DBSearch::FromOQL($sOQL);
|
||||
$oIntersect = $oSearchA->Intersect($oSearchB);
|
||||
CMDBSource::TestQuery($oIntersect->MakeSelectQuery());
|
||||
$this->assertEquals($sResult, $oIntersect->ToOQL());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \CoreException
|
||||
* @throws \MissingQueryArgument
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function testIntersectNotOptimizedReferencedBy()
|
||||
{
|
||||
$sBaseQuery = "SELECT o FROM Organization AS o JOIN Contact AS c ON c.org_id = o.id WHERE c.id = 1";
|
||||
$sOQL = "SELECT o FROM Organization AS o JOIN Person AS p ON p.org_id = o.id WHERE p.id = 2";
|
||||
$sResult = "SELECT `o` FROM Organization AS `o` JOIN Contact AS `c` ON `c`.org_id = `o`.id JOIN Person AS `p` ON `p`.org_id = `o`.id WHERE ((`c`.`id` = 1) AND (`p`.`id` = 2))";
|
||||
|
||||
$oSearchA = DBSearch::FromOQL($sBaseQuery);
|
||||
$oSearchB = DBSearch::FromOQL($sOQL);
|
||||
$oIntersect = $oSearchA->Intersect($oSearchB);
|
||||
CMDBSource::TestQuery($oIntersect->MakeSelectQuery());
|
||||
$this->assertEquals($sResult, $oIntersect->ToOQL());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \CoreException
|
||||
* @throws \MissingQueryArgument
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function testIntersectOptimizedReferencedBy()
|
||||
{
|
||||
$sBaseQuery = "SELECT s FROM Service AS s JOIN Organization AS o ON s.org_id = o.id WHERE o.name = 'some name'";
|
||||
$sQueryA = "SELECT r FROM UserRequest AS r JOIN Service AS s ON r.service_id = s.id JOIN Organization AS o ON s.org_id = o.id WHERE r.agent_id = 456 AND s.servicefamily_id = 789 AND o.name = 'right_name'";
|
||||
$sResult = "SELECT `s` FROM Service AS `s` JOIN Organization AS `o` ON `s`.org_id = `o`.id JOIN UserRequest AS `r` ON `r`.service_id = `s`.id WHERE ((`o`.`name` = 'some name') AND (((`r`.`agent_id` = 456) AND (`s`.`servicefamily_id` = 789)) AND (`o`.`name` = 'right_name')))";
|
||||
|
||||
$oSearchA = DBSearch::FromOQL($sQueryA);
|
||||
$oSearchB = DBSearch::FromOQL($sBaseQuery);
|
||||
$oSearchB->AddCondition_ReferencedBy($oSearchA, 'service_id');
|
||||
CMDBSource::TestQuery($oSearchB->MakeSelectQuery());
|
||||
$this->assertEquals($sResult, $oSearchB->ToOQL());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \CoreException
|
||||
* @throws \CoreWarning
|
||||
* @throws \MissingQueryArgument
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function testIntersectNotOptimizedAddConditionPointingTo()
|
||||
{
|
||||
$sBaseQuery = "SELECT Person FROM Person AS Person";
|
||||
$sQueryA = "SELECT o FROM Organization AS o JOIN Contact AS c ON c.org_id = o.id";
|
||||
$sResult = "SELECT `Person` FROM Person AS `Person` JOIN Organization AS `o` ON `Person`.org_id = `o`.id JOIN Contact AS `c` ON `c`.org_id = `o`.id WHERE 1";
|
||||
|
||||
$oSearchA = DBSearch::FromOQL($sQueryA);
|
||||
$oSearchB = DBSearch::FromOQL($sBaseQuery);
|
||||
$oSearchB->AddCondition_PointingTo($oSearchA, 'org_id');
|
||||
CMDBSource::TestQuery($oSearchB->MakeSelectQuery());
|
||||
$this->assertEquals($sResult, $oSearchB->ToOQL());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \CoreException
|
||||
* @throws \CoreWarning
|
||||
* @throws \MissingQueryArgument
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function testIntersectOptimizedAddConditionPointingTo()
|
||||
{
|
||||
$sBaseQuery = "SELECT ur FROM UserRequest AS ur JOIN Person AS p ON ur.agent_id = p.id WHERE p.status != 'terminated'";
|
||||
$sQueryA = "SELECT o FROM 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'";
|
||||
$sResult = "SELECT `ur` FROM UserRequest AS `ur` JOIN Person AS `p` ON `ur`.agent_id = `p`.id JOIN Organization AS `o` ON `ur`.org_id = `o`.id JOIN Person AS `p11` ON `ur`.caller_id = `p11`.id WHERE ((`p`.`status` != 'terminated') AND (((`o`.`name` LIKE 'Company') AND (`ur`.`service_id` = 123)) AND (`p11`.`employee_number` LIKE '007')))";
|
||||
|
||||
$oSearchA = DBSearch::FromOQL($sQueryA);
|
||||
$oSearchB = DBSearch::FromOQL($sBaseQuery);
|
||||
$oSearchB->AddCondition_PointingTo($oSearchA, 'org_id');
|
||||
CMDBSource::TestQuery($oSearchB->MakeSelectQuery());
|
||||
$this->assertEquals($sResult, $oSearchB->ToOQL());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \CoreException
|
||||
* @throws \MissingQueryArgument
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function testIntersectNotOptimizedAddConditionReferencedBy()
|
||||
{
|
||||
$sBaseQuery = "SELECT Person FROM Person AS Person";
|
||||
$sQueryA = "SELECT l FROM lnkContactToFunctionalCI AS l JOIN Contact AS c ON l.contact_id = c.id";
|
||||
$sResult = "SELECT `Person` FROM Person AS `Person` JOIN lnkContactToFunctionalCI AS `l` ON `l`.contact_id = `Person`.id JOIN Contact AS `c` ON `l`.contact_id = `c`.id WHERE 1";
|
||||
|
||||
$oSearchA = DBSearch::FromOQL($sQueryA);
|
||||
$oSearchB = DBSearch::FromOQL($sBaseQuery);
|
||||
$oSearchB->AddCondition_ReferencedBy($oSearchA, 'contact_id');
|
||||
CMDBSource::TestQuery($oSearchA->MakeSelectQuery());
|
||||
$this->assertEquals($sResult, $oSearchB->ToOQL());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider IntersectOptimizationProvider
|
||||
* @doesNotPerformAssertions
|
||||
*
|
||||
* @param string $sOQL
|
||||
* @param string $sResult
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \MissingQueryArgument
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function testIntersectOptimization($sBaseQuery, $sOQL, $sResult)
|
||||
{
|
||||
$oSearchA = DBSearch::FromOQL($sBaseQuery);
|
||||
$oSearchB = DBSearch::FromOQL($sOQL);
|
||||
$oIntersect = $oSearchA->Intersect($oSearchB);
|
||||
CMDBSource::TestQuery($oIntersect->MakeSelectQuery());
|
||||
$this->assertEquals($sResult, $oIntersect->ToOQL());
|
||||
}
|
||||
|
||||
public function IntersectOptimizationProvider()
|
||||
{
|
||||
$aQueries = array(
|
||||
'Exact same query' => array(
|
||||
'Base Query' => 'SELECT s FROM Service AS s JOIN Organization AS o ON s.org_id = o.id WHERE o.name = "The World Company"',
|
||||
'Filter OQL' => 'SELECT s FROM Service AS s JOIN Organization AS o ON s.org_id = o.id WHERE o.name = "The World Company"',
|
||||
'Result ' => "SELECT `s` FROM Service AS `s` JOIN Organization AS `o` ON `s`.org_id = `o`.id WHERE ((`o`.`name` = 'The World Company') AND (`o`.`name` = 'The World Company'))",
|
||||
),
|
||||
'Same query, other aliases' => array(
|
||||
'Base Query' => 'SELECT s FROM Service AS s JOIN Organization AS o ON s.org_id = o.id WHERE o.name = "The World Company"',
|
||||
'Filter OQL' => 'SELECT s2 FROM Service AS s2 JOIN Organization AS o2 ON s2.org_id = o2.id WHERE o2.name = "The World Company"',
|
||||
'Result ' => "SELECT `s` FROM Service AS `s` JOIN Organization AS `o` ON `s`.org_id = `o`.id WHERE ((`o`.`name` = 'The World Company') AND (`o`.`name` = 'The World Company'))",
|
||||
),
|
||||
'Same aliases, different condition' => array(
|
||||
'Base Query' => 'SELECT s FROM Service AS s JOIN Organization AS o ON s.org_id = o.id WHERE o.name = "The World Company"',
|
||||
'Filter OQL' => 'SELECT s FROM Service AS s JOIN Organization AS o ON s.org_id = o.id WHERE o.parent_id = 0',
|
||||
'Result ' => "SELECT `s` FROM Service AS `s` JOIN Organization AS `o` ON `s`.org_id = `o`.id WHERE ((`o`.`name` = 'The World Company') AND (`o`.`parent_id` = 0))",
|
||||
),
|
||||
'Other aliases, different condition' => array(
|
||||
'Base Query' => 'SELECT s FROM Service AS s JOIN Organization AS o ON s.org_id = o.id WHERE o.name = "The World Company"',
|
||||
'Filter OQL' => 'SELECT s2 FROM Service AS s2 JOIN Organization AS o2 ON s2.org_id = o2.id WHERE o2.parent_id = 0',
|
||||
'Result ' => "SELECT `s` FROM Service AS `s` JOIN Organization AS `o` ON `s`.org_id = `o`.id WHERE ((`o`.`name` = 'The World Company') AND (`o`.`parent_id` = 0))",
|
||||
),
|
||||
'Same aliases, simpler query tree' => array(
|
||||
'Base Query' => 'SELECT s FROM Service AS s JOIN Organization AS o ON s.org_id = o.id WHERE o.name = "The World Company"',
|
||||
'Filter OQL' => 'SELECT s FROM Service AS s WHERE name LIKE "Save the World"',
|
||||
'Result ' => "SELECT `s` FROM Service AS `s` JOIN Organization AS `o` ON `s`.org_id = `o`.id WHERE ((`o`.`name` = 'The World Company') AND (`s`.`name` LIKE 'Save the World'))",
|
||||
),
|
||||
'Other aliases, simpler query tree' => array(
|
||||
'Base Query' => 'SELECT s FROM Service AS s JOIN Organization AS o ON s.org_id = o.id WHERE o.name = "The World Company"',
|
||||
'Filter OQL' => 'SELECT s2 FROM Service AS s2 WHERE name LIKE "Save the World"',
|
||||
'Result ' => "SELECT `s` FROM Service AS `s` JOIN Organization AS `o` ON `s`.org_id = `o`.id WHERE ((`o`.`name` = 'The World Company') AND (`s`.`name` LIKE 'Save the World'))",
|
||||
),
|
||||
'Same aliases, different query tree' => array(
|
||||
'Base Query' => 'SELECT s FROM Service AS s JOIN Organization AS o ON s.org_id = o.id WHERE o.name = "The World Company"',
|
||||
'Filter OQL' => 'SELECT s FROM Service AS s JOIN ServiceFamily AS f ON s.servicefamily_id = f.id WHERE s.org_id = 123 AND f.name = "Care"',
|
||||
'Result ' => "SELECT `s` FROM Service AS `s` JOIN Organization AS `o` ON `s`.org_id = `o`.id JOIN ServiceFamily AS `f` ON `s`.servicefamily_id = `f`.id WHERE ((`o`.`name` = 'The World Company') AND ((`s`.`org_id` = 123) AND (`f`.`name` = 'Care')))",
|
||||
),
|
||||
'Other aliases, different query tree' => array(
|
||||
'Base Query' => 'SELECT s FROM Service AS s JOIN Organization AS o ON s.org_id = o.id WHERE o.name = "The World Company"',
|
||||
'Filter OQL' => 'SELECT s2 FROM Service AS s2 JOIN ServiceFamily AS f ON s2.servicefamily_id = f.id WHERE s2.org_id = 123 AND f.name = "Care"',
|
||||
'Result ' => "SELECT `s` FROM Service AS `s` JOIN Organization AS `o` ON `s`.org_id = `o`.id JOIN ServiceFamily AS `f` ON `s`.servicefamily_id = `f`.id WHERE ((`o`.`name` = 'The World Company') AND ((`s`.`org_id` = 123) AND (`f`.`name` = 'Care')))",
|
||||
),
|
||||
|
||||
'2 - Exact same query' => array(
|
||||
'Base Query' => 'SELECT o FROM Organization AS o JOIN Service AS s ON s.org_id = o.id WHERE s.name = "Help"',
|
||||
'Filter OQL' => 'SELECT o FROM Organization AS o JOIN Service AS s ON s.org_id = o.id WHERE s.name = "Help"',
|
||||
'Result ' => "SELECT `o` FROM Organization AS `o` JOIN Service AS `s` ON `s`.org_id = `o`.id WHERE ((`s`.`name` = 'Help') AND (`s`.`name` = 'Help'))",
|
||||
),
|
||||
'2 - Same query, other aliases' => array(
|
||||
'Base Query' => 'SELECT o FROM Organization AS o JOIN Service AS s ON s.org_id = o.id WHERE s.name = "Help"',
|
||||
'Filter OQL' => 'SELECT o2 FROM Organization AS o2 JOIN Service AS s2 ON s2.org_id = o2.id WHERE s2.name = "Help"',
|
||||
'Result ' => "SELECT `o` FROM Organization AS `o` JOIN Service AS `s` ON `s`.org_id = `o`.id WHERE ((`s`.`name` = 'Help') AND (`s`.`name` = 'Help'))",
|
||||
),
|
||||
'2 - Same aliases, different condition' => array(
|
||||
'Base Query' => 'SELECT o FROM Organization AS o JOIN Service AS s ON s.org_id = o.id WHERE s.name = "Help"',
|
||||
'Filter OQL' => 'SELECT o FROM Organization AS o JOIN Service AS s ON s.org_id = o.id WHERE s.servicefamily_id = 321',
|
||||
'Result ' => "SELECT `o` FROM Organization AS `o` JOIN Service AS `s` ON `s`.org_id = `o`.id WHERE ((`s`.`name` = 'Help') AND (`s`.`servicefamily_id` = 321))",
|
||||
),
|
||||
'2 - Other aliases, different condition' => array(
|
||||
'Base Query' => 'SELECT o FROM Organization AS o JOIN Service AS s ON s.org_id = o.id WHERE s.name = "Help"',
|
||||
'Filter OQL' => 'SELECT o2 FROM Organization AS o2 JOIN Service AS s2 ON s2.org_id = o2.id WHERE s2.servicefamily_id = 321',
|
||||
'Result ' => "SELECT `o` FROM Organization AS `o` JOIN Service AS `s` ON `s`.org_id = `o`.id WHERE ((`s`.`name` = 'Help') AND (`s`.`servicefamily_id` = 321))",
|
||||
),
|
||||
'2 - Same aliases, simpler query tree' => array(
|
||||
'Base Query' => 'SELECT o FROM Organization AS o JOIN Service AS s ON s.org_id = o.id WHERE s.name = "Help"',
|
||||
'Filter OQL' => 'SELECT o FROM Organization AS o WHERE o.name = "Demo"',
|
||||
'Result ' => "SELECT `o` FROM Organization AS `o` JOIN Service AS `s` ON `s`.org_id = `o`.id WHERE ((`s`.`name` = 'Help') AND (`o`.`name` = 'Demo'))",
|
||||
),
|
||||
'2 - Other aliases, simpler query tree' => array(
|
||||
'Base Query' => 'SELECT o FROM Organization AS o JOIN Service AS s ON s.org_id = o.id WHERE s.name = "Help"',
|
||||
'Filter OQL' => 'SELECT o2 FROM Organization AS o2 WHERE o2.name = "Demo"',
|
||||
'Result ' => "SELECT `o` FROM Organization AS `o` JOIN Service AS `s` ON `s`.org_id = `o`.id WHERE ((`s`.`name` = 'Help') AND (`o`.`name` = 'Demo'))",
|
||||
),
|
||||
'2 - Same aliases, different query tree' => array(
|
||||
'Base Query' => 'SELECT o FROM Organization AS o JOIN Service AS s ON s.org_id = o.id WHERE s.name = "Help"',
|
||||
'Filter OQL' => 'SELECT o FROM Organization AS o JOIN Location AS l ON l.org_id = o.id WHERE l.name = "Paris"',
|
||||
'Result ' => "SELECT `o` FROM Organization AS `o` JOIN Service AS `s` ON `s`.org_id = `o`.id JOIN Location AS `l` ON `l`.org_id = `o`.id WHERE ((`s`.`name` = 'Help') AND (`l`.`name` = 'Paris'))",
|
||||
),
|
||||
'2 - Other aliases, different query tree' => array(
|
||||
'Base Query' => 'SELECT o FROM Organization AS o JOIN Service AS s ON s.org_id = o.id WHERE s.name = "Help"',
|
||||
'Filter OQL' => 'SELECT o2 FROM Organization AS o2 JOIN Location AS l ON l.org_id = o2.id WHERE l.name = "Paris"',
|
||||
'Result ' => "SELECT `o` FROM Organization AS `o` JOIN Service AS `s` ON `s`.org_id = `o`.id JOIN Location AS `l` ON `l`.org_id = `o`.id WHERE ((`s`.`name` = 'Help') AND (`l`.`name` = 'Paris'))",
|
||||
),
|
||||
|
||||
'Internal query optimizations 1' => array(
|
||||
'Base Query' => 'SELECT o FROM Organization AS o',
|
||||
'Filter OQL' => 'SELECT o FROM Organization AS o JOIN Location AS l ON l.org_id = o.id JOIN Organization AS p ON o.parent_id = p.id WHERE l.name = "Paris" AND p.code LIKE "toto"',
|
||||
'Result ' => "SELECT `o` FROM Organization AS `o` JOIN Organization AS `p` ON `o`.parent_id = `p`.id JOIN Location AS `l` ON `l`.org_id = `o`.id WHERE ((`l`.`name` = 'Paris') AND (`p`.`code` LIKE 'toto'))",
|
||||
),
|
||||
'Internal query optimizations 2' => array(
|
||||
'Base Query' => 'SELECT r FROM UserRequest AS r JOIN Service AS s ON r.service_id = s.id JOIN Organization AS o ON s.org_id = o.id WHERE o.name = "left_name"',
|
||||
'Filter OQL' => 'SELECT r FROM UserRequest AS r JOIN Service AS s ON r.service_id = s.id JOIN Organization AS o ON s.org_id = o.id WHERE o.name = "right_name"',
|
||||
'Result ' => "SELECT `r` FROM UserRequest AS `r` JOIN Service AS `s` ON `r`.service_id = `s`.id JOIN Organization AS `o` ON `s`.org_id = `o`.id WHERE ((`o`.`name` = 'left_name') AND (`o`.`name` = 'right_name'))",
|
||||
),
|
||||
);
|
||||
|
||||
return $aQueries;
|
||||
}
|
||||
}
|
||||
@@ -4712,199 +4712,6 @@ class TestExecActions extends TestBizModel
|
||||
}
|
||||
}
|
||||
|
||||
class TestIntersectOptimization extends TestBizModel
|
||||
{
|
||||
static public function GetName()
|
||||
{
|
||||
return 'Internal query optimizations (pointing to)';
|
||||
}
|
||||
|
||||
static public function GetDescription()
|
||||
{
|
||||
return 'Clever optimization required for the portal to work fine (expected improvement: query never finishing... to an almost instantaneous query!';
|
||||
}
|
||||
|
||||
protected function DoExecute()
|
||||
{
|
||||
$sBaseQuery = 'SELECT Service AS s JOIN Organization AS o ON s.org_id = o.id WHERE o.name = "The World Company"';
|
||||
$aQueries = array(
|
||||
// Exact same query
|
||||
'SELECT Service AS s JOIN Organization AS o ON s.org_id = o.id WHERE o.name = "The World Company"',
|
||||
// Same query, other aliases
|
||||
'SELECT Service AS s2 JOIN Organization AS o2 ON s2.org_id = o2.id WHERE o2.name = "The World Company"',
|
||||
// Same aliases, different condition
|
||||
'SELECT Service AS s JOIN Organization AS o ON s.org_id = o.id WHERE o.parent_id = 0',
|
||||
// Other aliases, different condition
|
||||
'SELECT Service AS s2 JOIN Organization AS o2 ON s2.org_id = o2.id WHERE o2.parent_id = 0',
|
||||
// Same aliases, simpler query tree
|
||||
'SELECT Service AS s WHERE name LIKE "Save the World"',
|
||||
// Other aliases, simpler query tree
|
||||
'SELECT Service AS s2 WHERE name LIKE "Save the World"',
|
||||
// Same aliases, different query tree
|
||||
'SELECT Service AS s JOIN ServiceFamily AS f ON s.servicefamily_id = f.id WHERE s.org_id = 123 AND f.name = "Care"',
|
||||
// Other aliases, different query tree
|
||||
'SELECT Service AS s2 JOIN ServiceFamily AS f ON s2.servicefamily_id = f.id WHERE s2.org_id = 123 AND f.name = "Care"',
|
||||
);
|
||||
echo "<h4>Base query: ".htmlentities($sBaseQuery, ENT_QUOTES, 'UTF-8')."</h4>\n";
|
||||
foreach ($aQueries as $sOQL)
|
||||
{
|
||||
echo "<h5>Checking: ".htmlentities($sOQL, ENT_QUOTES, 'UTF-8')."</h5>\n";
|
||||
$oSearchA = DBSearch::FromOQL($sBaseQuery);
|
||||
$oSearchB = DBSearch::FromOQL($sOQL);
|
||||
$oIntersect = $oSearchA->Intersect($oSearchB);
|
||||
echo "<p>Intersect: ".htmlentities($oIntersect->ToOQL(), ENT_QUOTES, 'UTF-8')."</p>\n";
|
||||
CMDBSource::TestQuery($oIntersect->MakeSelectQuery());
|
||||
echo "<p>Successfully tested the SQL query.</p>\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TestIntersectOptimization2 extends TestBizModel
|
||||
{
|
||||
static public function GetName()
|
||||
{
|
||||
return 'Internal query optimizations (referenced by)';
|
||||
}
|
||||
|
||||
static public function GetDescription()
|
||||
{
|
||||
return 'Clever optimization required for the portal to work fine (expected improvement: query never finishing... to an almost instantaneous query!';
|
||||
}
|
||||
|
||||
protected function DoExecute()
|
||||
{
|
||||
$sBaseQuery = 'SELECT Organization AS o JOIN Service AS s ON s.org_id = o.id WHERE s.name = "Help"';
|
||||
$aQueries = array(
|
||||
// Exact same query
|
||||
'SELECT Organization AS o JOIN Service AS s ON s.org_id = o.id WHERE s.name = "Help"',
|
||||
// Same query, other aliases
|
||||
'SELECT Organization AS o2 JOIN Service AS s2 ON s2.org_id = o2.id WHERE s2.name = "Help"',
|
||||
// Same aliases, different condition
|
||||
'SELECT Organization AS o JOIN Service AS s ON s.org_id = o.id WHERE s.servicefamily_id = 321',
|
||||
// Other aliases, different condition
|
||||
'SELECT Organization AS o2 JOIN Service AS s2 ON s2.org_id = o2.id WHERE s2.servicefamily_id = 321',
|
||||
// Same aliases, simpler query tree
|
||||
'SELECT Organization AS o WHERE o.name = "Demo"',
|
||||
// Other aliases, simpler query tree
|
||||
'SELECT Organization AS o2 WHERE o2.name = "Demo"',
|
||||
// Same aliases, different query tree
|
||||
'SELECT Organization AS o JOIN Location AS l ON l.org_id = o.id WHERE l.name = "Paris"',
|
||||
// Other aliases, different query tree
|
||||
'SELECT Organization AS o2 JOIN Location AS l ON l.org_id = o2.id WHERE l.name = "Paris"',
|
||||
);
|
||||
echo "<h4>Base query: ".htmlentities($sBaseQuery, ENT_QUOTES, 'UTF-8')."</h4>\n";
|
||||
foreach ($aQueries as $sOQL)
|
||||
{
|
||||
echo "<h5>Checking: ".htmlentities($sOQL, ENT_QUOTES, 'UTF-8')."</h5>\n";
|
||||
$oSearchA = DBSearch::FromOQL($sBaseQuery);
|
||||
$oSearchB = DBSearch::FromOQL($sOQL);
|
||||
$oIntersect = $oSearchA->Intersect($oSearchB);
|
||||
echo "<p>Intersect: ".htmlentities($oIntersect->ToOQL(), ENT_QUOTES, 'UTF-8')."</p>\n";
|
||||
CMDBSource::TestQuery($oIntersect->MakeSelectQuery());
|
||||
echo "<p>Successfully tested the SQL query.</p>\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TestIntersectOptimization3 extends TestBizModel
|
||||
{
|
||||
static public function GetName()
|
||||
{
|
||||
return 'Internal query optimizations (mix)';
|
||||
}
|
||||
|
||||
static public function GetDescription()
|
||||
{
|
||||
return 'Clever optimization required for the portal to work fine (expected improvement: query never finishing... to an almost instantaneous query!';
|
||||
}
|
||||
|
||||
protected function DoExecute()
|
||||
{
|
||||
$aQueries = array(
|
||||
array(
|
||||
'SELECT Organization AS o',
|
||||
'SELECT Organization AS o JOIN Location AS l ON l.org_id = o.id JOIN Organization AS p ON o.parent_id = p.id WHERE l.name = "Paris" AND p.code LIKE "toto"',
|
||||
),
|
||||
array(
|
||||
'SELECT UserRequest AS r JOIN Service AS s ON r.service_id = s.id JOIN Organization AS o ON s.org_id = o.id WHERE o.name = "left_name"',
|
||||
'SELECT UserRequest AS r JOIN Service AS s ON r.service_id = s.id JOIN Organization AS o ON s.org_id = o.id WHERE o.name = "right_name"',
|
||||
),
|
||||
);
|
||||
echo "<h4>Mixing....</h4>\n";
|
||||
foreach ($aQueries as $aQ)
|
||||
{
|
||||
$sBaseQuery = $aQ[0];
|
||||
$sOQL = $aQ[1];
|
||||
echo "<h5>Left: ".htmlentities($sBaseQuery, ENT_QUOTES, 'UTF-8')."</h5>\n";
|
||||
echo "<h5>Right: ".htmlentities($sOQL, ENT_QUOTES, 'UTF-8')."</h5>\n";
|
||||
$oSearchA = DBSearch::FromOQL($sBaseQuery);
|
||||
$oSearchB = DBSearch::FromOQL($sOQL);
|
||||
$oIntersect = $oSearchA->Intersect($oSearchB);
|
||||
echo "<p>Intersect: ".htmlentities($oIntersect->ToOQL(), ENT_QUOTES, 'UTF-8')."</p>\n";
|
||||
CMDBSource::TestQuery($oIntersect->MakeSelectQuery());
|
||||
echo "<p>Successfully tested the SQL query.</p>\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TestIntersectOptimization4 extends TestBizModel
|
||||
{
|
||||
static public function GetName()
|
||||
{
|
||||
return 'Internal query optimizations (Folding on Join/ReferencedBy)';
|
||||
}
|
||||
|
||||
static public function GetDescription()
|
||||
{
|
||||
return 'Clever optimization required for the portal to work fine (expected improvement: query never finishing... to an almost instantaneous query!';
|
||||
}
|
||||
|
||||
protected function DoExecute()
|
||||
{
|
||||
echo "<h4>Here we are (conluding a long series of tests)</h4>\n";
|
||||
$sQueryA = 'SELECT UserRequest AS r JOIN Service AS s ON r.service_id = s.id JOIN Organization AS o ON s.org_id = o.id WHERE r.agent_id = 456 AND s.servicefamily_id = 789 AND o.name = "right_name"';
|
||||
$sQueryB = 'SELECT Service AS s JOIN Organization AS o ON s.org_id = o.id WHERE o.name = "some name"';
|
||||
|
||||
echo "<h5>A: ".htmlentities($sQueryA, ENT_QUOTES, 'UTF-8')."</h5>\n";
|
||||
echo "<h5>B: ".htmlentities($sQueryB, ENT_QUOTES, 'UTF-8')."</h5>\n";
|
||||
$oSearchA = DBSearch::FromOQL($sQueryA);
|
||||
$oSearchB = DBSearch::FromOQL($sQueryB);
|
||||
$oSearchB->AddCondition_ReferencedBy($oSearchA, 'service_id');
|
||||
echo "<p>Referenced by...: ".htmlentities($oSearchB->ToOQL(), ENT_QUOTES, 'UTF-8')."</p>\n";
|
||||
CMDBSource::TestQuery($oSearchB->MakeSelectQuery());
|
||||
echo "<p>Successfully tested the SQL query.</p>\n";
|
||||
}
|
||||
}
|
||||
|
||||
class TestIntersectOptimization5 extends TestBizModel
|
||||
{
|
||||
static public function GetName()
|
||||
{
|
||||
return 'Internal query optimizations (Folding on Join/PointingTo)';
|
||||
}
|
||||
|
||||
static public function GetDescription()
|
||||
{
|
||||
return 'Clever optimization required for the portal to work fine (expected improvement: query never finishing... to an almost instantaneous query!';
|
||||
}
|
||||
|
||||
protected function DoExecute()
|
||||
{
|
||||
echo "<h4>Here we are (concluding a long series of tests)</h4>\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"';
|
||||
|
||||
echo "<h5>A: ".htmlentities($sQueryA, ENT_QUOTES, 'UTF-8')."</h5>\n";
|
||||
echo "<h5>B: ".htmlentities($sQueryB, ENT_QUOTES, 'UTF-8')."</h5>\n";
|
||||
$oSearchA = DBSearch::FromOQL($sQueryA);
|
||||
$oSearchB = DBSearch::FromOQL($sQueryB);
|
||||
$oSearchB->AddCondition_PointingTo($oSearchA, 'org_id');
|
||||
echo "<p>Pointing to...: ".htmlentities($oSearchB->ToOQL(), ENT_QUOTES, 'UTF-8')."</p>\n";
|
||||
CMDBSource::TestQuery($oSearchB->MakeSelectQuery());
|
||||
echo "<p>Successfully tested the SQL query.</p>\n";
|
||||
}
|
||||
}
|
||||
|
||||
class TestParsingOptimization extends TestBizModel
|
||||
{
|
||||
static public function GetName()
|
||||
@@ -5036,70 +4843,6 @@ 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 "<h4>Intersect NOT optimized on 'pointing to'</h4>\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 "<h5>Left: ".htmlentities($sBaseQuery, ENT_QUOTES, 'UTF-8')."</h5>\n";
|
||||
echo "<h5>Right: ".htmlentities($sOQL, ENT_QUOTES, 'UTF-8')."</h5>\n";
|
||||
$oSearchA = DBSearch::FromOQL($sBaseQuery);
|
||||
$oSearchB = DBSearch::FromOQL($sOQL);
|
||||
$oIntersect = $oSearchA->Intersect($oSearchB);
|
||||
echo "<p>Intersect: ".htmlentities($oIntersect->ToOQL(), ENT_QUOTES, 'UTF-8')."</p>\n";
|
||||
CMDBSource::TestQuery($oIntersect->MakeSelectQuery());
|
||||
echo "<p>Successfully tested the SQL query.</p>\n";
|
||||
|
||||
echo "<h4>Intersect NOT optimized on 'referenced by'</h4>\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 "<h5>Left: ".htmlentities($sBaseQuery, ENT_QUOTES, 'UTF-8')."</h5>\n";
|
||||
echo "<h5>Right: ".htmlentities($sOQL, ENT_QUOTES, 'UTF-8')."</h5>\n";
|
||||
$oSearchA = DBSearch::FromOQL($sBaseQuery);
|
||||
$oSearchB = DBSearch::FromOQL($sOQL);
|
||||
$oIntersect = $oSearchA->Intersect($oSearchB);
|
||||
echo "<p>Intersect: ".htmlentities($oIntersect->ToOQL(), ENT_QUOTES, 'UTF-8')."</p>\n";
|
||||
CMDBSource::TestQuery($oIntersect->MakeSelectQuery());
|
||||
echo "<p>Successfully tested the SQL query.</p>\n";
|
||||
|
||||
echo "<h4>NOT Folding on AddCondition_PointingTo</h4>\n";
|
||||
$sQueryA = 'SELECT Organization AS o JOIN Contact AS c ON c.org_id = o.id';
|
||||
$sQueryB = 'SELECT Person';
|
||||
echo "<h5>A: ".htmlentities($sQueryA, ENT_QUOTES, 'UTF-8')."</h5>\n";
|
||||
echo "<h5>B: ".htmlentities($sQueryB, ENT_QUOTES, 'UTF-8')."</h5>\n";
|
||||
$oSearchA = DBSearch::FromOQL($sQueryA);
|
||||
$oSearchB = DBSearch::FromOQL($sQueryB);
|
||||
$oSearchB->AddCondition_PointingTo($oSearchA, 'org_id');
|
||||
echo "<p>Pointing to...: ".htmlentities($oSearchB->ToOQL(), ENT_QUOTES, 'UTF-8')."</p>\n";
|
||||
CMDBSource::TestQuery($oSearchB->MakeSelectQuery());
|
||||
echo "<p>Successfully tested the SQL query.</p>\n";
|
||||
|
||||
echo "<h4>NOT Folding on AddCondition_ReferencedBy</h4>\n";
|
||||
$sQueryA = 'SELECT lnkContactToFunctionalCI AS l JOIN Contact AS c ON l.contact_id = c.id';
|
||||
$sQueryB = 'SELECT Person';
|
||||
echo "<h5>A: ".htmlentities($sQueryA, ENT_QUOTES, 'UTF-8')."</h5>\n";
|
||||
echo "<h5>B: ".htmlentities($sQueryB, ENT_QUOTES, 'UTF-8')."</h5>\n";
|
||||
$oSearchA = DBSearch::FromOQL($sQueryA);
|
||||
$oSearchB = DBSearch::FromOQL($sQueryB);
|
||||
$oSearchB->AddCondition_ReferencedBy($oSearchA, 'contact_id');
|
||||
echo "<p>Referenced by...: ".htmlentities($oSearchA->ToOQL(), ENT_QUOTES, 'UTF-8')."</p>\n";
|
||||
CMDBSource::TestQuery($oSearchA->MakeSelectQuery());
|
||||
echo "<p>Successfully tested the SQL query.</p>\n";
|
||||
}
|
||||
}
|
||||
|
||||
class TestBug609 extends TestBizModel
|
||||
{
|
||||
static public function GetName()
|
||||
|
||||
Reference in New Issue
Block a user