Merge remote-tracking branch 'origin/support/3.0' into develop

This commit is contained in:
Eric Espie
2023-04-05 18:00:46 +02:00
2 changed files with 133 additions and 73 deletions

View File

@@ -1101,22 +1101,39 @@ class DBObjectSearch extends DBSearch
public function Filter($sClassAlias, DBSearch $oFilter) public function Filter($sClassAlias, DBSearch $oFilter)
{ {
// If the conditions are the correct ones for Intersect // If the conditions are the correct ones for Intersect
if (MetaModel::IsParentClass($oFilter->GetFirstJoinedClass(),$this->GetFirstJoinedClass())) if (MetaModel::IsParentClass($oFilter->GetFirstJoinedClass(), $this->GetFirstJoinedClass())) {
{
return $this->Intersect($oFilter); return $this->Intersect($oFilter);
} }
/** @var \DBObjectSearch $oFilteredSearch */ if ($oFilter instanceof DBUnionSearch) {
$oFilteredSearch = $this->DeepClone(); $aFilters = $oFilter->GetSearches();
$oFilterExpression = self::FilterSubClass($oFilteredSearch, $sClassAlias, $oFilter, $this->m_aClasses); } else {
if ($oFilterExpression === false) $aFilters = [$oFilter];
{
throw new CoreException("Limitation: cannot filter search");
} }
$oFilteredSearch->AddConditionExpression($oFilterExpression); $aSearches = [];
foreach ($aFilters as $oRightFilter) {
/** @var \DBObjectSearch $oFilteredSearch */
$oFilteredSearch = $this->DeepClone();
$oFilterExpression = self::FilterSubClass($oFilteredSearch, $sClassAlias, $oRightFilter, $this->m_aClasses);
if ($oFilterExpression === false) {
throw new CoreException("Limitation: cannot filter search");
}
return $oFilteredSearch; $oFilteredSearch->AddConditionExpression($oFilterExpression);
$aSearches[] = $oFilteredSearch;
}
if (count($aSearches) == 0) {
throw new CoreException('Filtering '.$this->ToOQL().' by '.$oFilter->ToOQL().' failed');
}
if (count($aSearches) == 1) {
// return a DBObjectSearch
return $aSearches[0];
}
return new DBUnionSearch($aSearches);
} }
/** /**
@@ -1182,22 +1199,10 @@ class DBObjectSearch extends DBSearch
* @throws \CoreException * @throws \CoreException
*/ */
public function Intersect(DBSearch $oFilter) 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) if ($oFilter instanceof DBUnionSearch)
{ {
// Develop! // Develop!
$aFilters = $oFilter->GetSearches(); $aFilters = $oFilter->GetSearches();
} }
else else
@@ -1208,56 +1213,61 @@ class DBObjectSearch extends DBSearch
$aSearches = array(); $aSearches = array();
foreach ($aFilters as $oRightFilter) foreach ($aFilters as $oRightFilter)
{ {
// Limitation: the queried class must be the first declared class $aSearches[] = $this->IntersectSubClass($oRightFilter, $this->m_aClasses);
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();
$bAllowAllData = ($oLeftFilter->IsAllDataAllowed() && $oRightFilter->IsAllDataAllowed());
if ($bAllowAllData)
{
$oLeftFilter->AllowAllData();
}
if ($oLeftFilter->GetFirstJoinedClass() != $oRightFilter->GetClass())
{
if (MetaModel::IsParentClass($oLeftFilter->GetFirstJoinedClass(), $oRightFilter->GetClass()))
{
// Specialize $oLeftFilter
$oLeftFilter->ChangeClass($oRightFilter->GetClass(), $oLeftFilter->GetFirstJoinedClassAlias());
}
elseif (MetaModel::IsParentClass($oRightFilter->GetFirstJoinedClass(), $oLeftFilter->GetClass()))
{
// Specialize $oRightFilter
$oRightFilter->ChangeClass($oLeftFilter->GetFirstJoinedClass());
}
else
{
throw new CoreException("Attempting to merge a filter of class '{$oLeftFilter->GetClass()}' with a filter of class '{$oRightFilter->GetClass()}'");
}
}
$aAliasTranslation = array();
$oLeftFilter->RenameNestedQueriesAliasesInNameSpace($aRootClasses, $aAliasTranslation);
$oLeftFilter->MergeWith_InNamespace($oRightFilter, $aRootClasses, $aAliasTranslation);
$oRightFilter->RenameNestedQueriesAliasesInNameSpace($aRootClasses, $aAliasTranslation);
$oLeftFilter->TransferConditionExpression($oRightFilter, $aAliasTranslation);
$aSearches[] = $oLeftFilter;
} }
if (count($aSearches) == 1) if (count($aSearches) == 1)
{ {
// return a DBObjectSearch // return a DBObjectSearch
return $aSearches[0]; return $aSearches[0];
} }
else
{ return new DBUnionSearch($aSearches);
return new DBUnionSearch($aSearches); }
/**
* @param \DBObjectSearch $oRightFilter
* @param array $aRootClasses classes of the root search (for aliases)
*
* @return \DBObjectSearch
* @throws \CoreException
*/
protected function IntersectSubClass(DBObjectSearch $oRightFilter, array $aRootClasses): DBObjectSearch
{
// Limitation: the queried class must be the first declared class
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();
/** @var DBObjectSearch $oRightFilter */
$oRightFilter = $oRightFilter->DeepClone();
$bAllowAllData = ($oLeftFilter->IsAllDataAllowed() && $oRightFilter->IsAllDataAllowed());
if ($bAllowAllData) {
$oLeftFilter->AllowAllData();
}
if ($oLeftFilter->GetFirstJoinedClass() != $oRightFilter->GetClass()) {
if (MetaModel::IsParentClass($oLeftFilter->GetFirstJoinedClass(), $oRightFilter->GetClass())) {
// Specialize $oLeftFilter
$oLeftFilter->ChangeClass($oRightFilter->GetClass(), $oLeftFilter->GetFirstJoinedClassAlias());
} elseif (MetaModel::IsParentClass($oRightFilter->GetFirstJoinedClass(), $oLeftFilter->GetClass())) {
// Specialize $oRightFilter
$oRightFilter->ChangeClass($oLeftFilter->GetFirstJoinedClass());
} else {
throw new CoreException("Attempting to merge a filter of class '{$oLeftFilter->GetClass()}' with a filter of class '{$oRightFilter->GetClass()}'");
}
}
$aAliasTranslation = array();
$oLeftFilter->RenameNestedQueriesAliasesInNameSpace($aRootClasses, $aAliasTranslation);
$oLeftFilter->MergeWith_InNamespace($oRightFilter, $aRootClasses, $aAliasTranslation);
$oRightFilter->RenameNestedQueriesAliasesInNameSpace($aRootClasses, $aAliasTranslation);
$oLeftFilter->TransferConditionExpression($oRightFilter, $aAliasTranslation);
return $oLeftFilter;
} }
/** /**

View File

@@ -17,10 +17,10 @@ class DBUnionSearchTest extends ItopDataTestCase
public function testFilterOnFirstSelectedClass() public function testFilterOnFirstSelectedClass()
{ {
$sSourceOQL = 'SELECT `Person`, `Location` FROM Person AS `Person` JOIN Location AS `Location` ON `Person`.location_id = `Location`.id WHERE 1'; $sSourceOQL = 'SELECT `Person`, `Location` FROM Person AS `Person` JOIN Location AS `Location` ON `Person`.location_id = `Location`.id WHERE (`Location`.`id` = 1)';
$oSearch = DBSearch::FromOQL($sSourceOQL); $oSearch = DBSearch::FromOQL($sSourceOQL);
$sFilterOQL = 'SELECT `Person` FROM Person AS `Person` WHERE (`Person`.`id` = 1) UNION SELECT `Person` FROM Person AS `Person` WHERE (`Person`.`id` = 1)'; $sFilterOQL = 'SELECT `Person` FROM Person AS `Person` WHERE (`Person`.`id` = 2) UNION SELECT `Person` FROM Person AS `Person` WHERE (`Person`.`id` = 3)';
$oVisibleObjects = DBSearch::FromOQL($sFilterOQL); $oVisibleObjects = DBSearch::FromOQL($sFilterOQL);
$sClassAlias = 'Person'; $sClassAlias = 'Person';
$oVisibleObjects->AllowAllData(); $oVisibleObjects->AllowAllData();
@@ -38,6 +38,7 @@ class DBUnionSearchTest extends ItopDataTestCase
/** /**
* Ignored test (provokes PHP Error) * Ignored test (provokes PHP Error)
* *
* @dataProvider FilterOnSecondSelectedClassProvider
* @return void * @return void
* @throws \CoreException * @throws \CoreException
* @throws \MissingQueryArgument * @throws \MissingQueryArgument
@@ -45,22 +46,71 @@ class DBUnionSearchTest extends ItopDataTestCase
* @throws \MySQLHasGoneAwayException * @throws \MySQLHasGoneAwayException
* @throws \OQLException * @throws \OQLException
*/ */
public function FilterOnSecondSelectedClass() public function testFilterOnSecondSelectedClass($sSourceOQL, $sClassAlias, $sFilterOQL, $sExpected)
{ {
$sSourceOQL = 'SELECT `Person`, `Location` FROM Person JOIN Location ON `Person`.location_id = `Location`.id';
$oSearch = DBSearch::FromOQL($sSourceOQL); $oSearch = DBSearch::FromOQL($sSourceOQL);
$sFilterOQL = 'SELECT Location UNION SELECT Location';
$oVisibleObjects = DBSearch::FromOQL($sFilterOQL); $oVisibleObjects = DBSearch::FromOQL($sFilterOQL);
$sClassAlias = 'Location';
$oVisibleObjects->AllowAllData(); $oVisibleObjects->AllowAllData();
$oSearch = $oSearch->Filter($sClassAlias, $oVisibleObjects); $oSearch = $oSearch->Filter($sClassAlias, $oVisibleObjects);
// $oSearch->ToOQL(); $sResult = $oSearch->ToOQL();
$this->debug($sResult);
$this->assertEquals($sExpected, $sResult);
$oSet = new DBObjectSet($oSearch); $oSet = new DBObjectSet($oSearch);
$oSet->CountWithLimit(1); $oSet->CountWithLimit(1);
$this->assertTrue(true); $this->assertTrue(true);
} }
public function FilterOnSecondSelectedClassProvider()
{
return [
[
'sSourceOQL' => "SELECT P1, L1 FROM Person AS P1 JOIN Location AS L1 ON P1.location_id = L1.id WHERE L1.id = 1",
'sClassAlias' => "L1",
'sFilterOQL' => "SELECT Location AS L2 WHERE L2.id = 2 UNION SELECT Location AS L3 WHERE L3.id = 3",
'sExpected' => "SELECT `P1`, `L1` FROM Person AS `P1` JOIN Location AS `L1` ON `P1`.location_id = `L1`.id WHERE ((`L1`.`id` = 1) AND (`L1`.`id` = 2)) UNION SELECT `P1`, `L1` FROM Person AS `P1` JOIN Location AS `L1` ON `P1`.location_id = `L1`.id WHERE ((`L1`.`id` = 1) AND (`L1`.`id` = 3))",
],
[
'sSourceOQL' => 'SELECT L1, P1 FROM Person AS P1 JOIN Location AS L1 ON P1.location_id = L1.id WHERE L1.id = 1',
'sClassAlias' => 'L1',
'sFilterOQL' => 'SELECT Location AS L2 WHERE L2.id = 2 UNION SELECT Location AS L3 WHERE L3.id = 3',
'sExpected' => 'SELECT `L1`, `P1` FROM Person AS `P1` JOIN Location AS `L1` ON `P1`.location_id = `L1`.id WHERE ((`L1`.`id` = 1) AND (`L1`.`id` = 2)) UNION SELECT `L1`, `P1` FROM Person AS `P1` JOIN Location AS `L1` ON `P1`.location_id = `L1`.id WHERE ((`L1`.`id` = 1) AND (`L1`.`id` = 3))',
],
[
'sSourceOQL' => "SELECT L1,O1 FROM Location AS L1 JOIN Organization AS O1 ON L1.org_id=O1.id JOIN Organization AS O2 ON O1.parent_id = O2.id WHERE L1.name != 'l1-name' AND O1.name != 'o1-name' AND ISNULL(O2.name) != 0",
'sClassAlias' => "O1",
'sFilterOQL' => "SELECT Organization AS O1 WHERE O1.id = 2 UNION SELECT Organization AS O2 WHERE O2.id = 3",
'sExpected' => "SELECT `L1`, `O1` FROM Location AS `L1` JOIN Organization AS `O1` ON `L1`.org_id = `O1`.id JOIN Organization AS `O2` ON `O1`.parent_id = `O2`.id WHERE ((((`L1`.`name` != 'l1-name') AND (`O1`.`name` != 'o1-name')) AND (ISNULL(`O2`.`name`) != 0)) AND (`O1`.`id` = 2)) UNION SELECT `L1`, `O1` FROM Location AS `L1` JOIN Organization AS `O1` ON `L1`.org_id = `O1`.id JOIN Organization AS `O2` ON `O1`.parent_id = `O2`.id WHERE ((((`L1`.`name` != 'l1-name') AND (`O1`.`name` != 'o1-name')) AND (ISNULL(`O2`.`name`) != 0)) AND (`O1`.`id` = 3))",
],
[
'sSourceOQL' => "SELECT L1,O2 FROM Location AS L1 JOIN Organization AS O1 ON L1.org_id=O1.id JOIN Organization AS O2 ON O1.parent_id = O2.id WHERE L1.name != 'l1-name' AND O1.name != 'o1-name' AND ISNULL(O2.name) != 0",
'sClassAlias' => 'O2',
'sFilterOQL' => 'SELECT Organization AS O1 WHERE O1.id = 2 UNION SELECT Organization AS O2 WHERE O2.id = 3',
'sExpected' => "SELECT `L1`, `O2` FROM Location AS `L1` JOIN Organization AS `O1` ON `L1`.org_id = `O1`.id JOIN Organization AS `O2` ON `O1`.parent_id = `O2`.id WHERE ((((`L1`.`name` != 'l1-name') AND (`O1`.`name` != 'o1-name')) AND (ISNULL(`O2`.`name`) != 0)) AND (`O2`.`id` = 2)) UNION SELECT `L1`, `O2` FROM Location AS `L1` JOIN Organization AS `O1` ON `L1`.org_id = `O1`.id JOIN Organization AS `O2` ON `O1`.parent_id = `O2`.id WHERE ((((`L1`.`name` != 'l1-name') AND (`O1`.`name` != 'o1-name')) AND (ISNULL(`O2`.`name`) != 0)) AND (`O2`.`id` = 3))",
],
[
'sSourceOQL' => "SELECT L1,O1 FROM Location AS L1 JOIN Organization AS O1 ON L1.org_id=O1.id JOIN Organization AS O2 ON O1.parent_id = O2.id WHERE L1.name != 'l1-name' AND O1.name != 'o1-name' AND ISNULL(O2.name) != 0",
'sClassAlias' => 'O2',
'sFilterOQL' => 'SELECT Organization AS O1 WHERE O1.id = 2 UNION SELECT Organization AS O2 WHERE O2.id = 3',
// This is another problem, we should not be able to filter on not selected classes
'sExpected' => "SELECT `L1`, `O1` FROM Location AS `L1` JOIN Organization AS `O1` ON `L1`.org_id = `O1`.id JOIN Organization AS `O2` ON `O1`.parent_id = `O2`.id WHERE ((((`L1`.`name` != 'l1-name') AND (`O1`.`name` != 'o1-name')) AND (ISNULL(`O2`.`name`) != 0)) AND (`O2`.`id` = 2)) UNION SELECT `L1`, `O1` FROM Location AS `L1` JOIN Organization AS `O1` ON `L1`.org_id = `O1`.id JOIN Organization AS `O2` ON `O1`.parent_id = `O2`.id WHERE ((((`L1`.`name` != 'l1-name') AND (`O1`.`name` != 'o1-name')) AND (ISNULL(`O2`.`name`) != 0)) AND (`O2`.`id` = 3))",
],
[
'sSourceOQL' => "SELECT P1, O1 FROM Person AS P1 JOIN Organization AS O1 ON P1.org_id = O1.id JOIN Location AS L1 ON P1.location_id = L1.id JOIN Organization AS O2 ON L1.org_id = O2.id WHERE L1.name != '' AND P1.name != '' AND O1.name != '' AND O2.name != ''",
'sClassAlias' => 'O1',
'sFilterOQL' => 'SELECT Organization AS O1 WHERE O1.id = 2 UNION SELECT Organization AS O2 WHERE O2.id = 3',
'sExpected' => "SELECT `P1`, `O1` FROM Person AS `P1` JOIN Organization AS `O1` ON `P1`.org_id = `O1`.id JOIN Location AS `L1` ON `P1`.location_id = `L1`.id JOIN Organization AS `O2` ON `L1`.org_id = `O2`.id WHERE (((((`L1`.`name` != '') AND (`P1`.`name` != '')) AND (`O1`.`name` != '')) AND (`O2`.`name` != '')) AND (`O1`.`id` = 2)) UNION SELECT `P1`, `O1` FROM Person AS `P1` JOIN Organization AS `O1` ON `P1`.org_id = `O1`.id JOIN Location AS `L1` ON `P1`.location_id = `L1`.id JOIN Organization AS `O2` ON `L1`.org_id = `O2`.id WHERE (((((`L1`.`name` != '') AND (`P1`.`name` != '')) AND (`O1`.`name` != '')) AND (`O2`.`name` != '')) AND (`O1`.`id` = 3))",
],
[
'sSourceOQL' => "SELECT P1, O2 FROM Person AS P1 JOIN Organization AS O1 ON P1.org_id = O1.id JOIN Location AS L1 ON P1.location_id = L1.id JOIN Organization AS O2 ON L1.org_id = O2.id WHERE L1.name != '' AND P1.name != '' AND O1.name != '' AND O2.name != ''",
'sClassAlias' => 'O2',
'sFilterOQL' => 'SELECT Organization AS O1 WHERE O1.id = 2 UNION SELECT Organization AS O2 WHERE O2.id = 3',
'sExpected' => "SELECT `P1`, `O2` FROM Person AS `P1` JOIN Organization AS `O1` ON `P1`.org_id = `O1`.id JOIN Location AS `L1` ON `P1`.location_id = `L1`.id JOIN Organization AS `O2` ON `L1`.org_id = `O2`.id WHERE (((((`L1`.`name` != '') AND (`P1`.`name` != '')) AND (`O1`.`name` != '')) AND (`O2`.`name` != '')) AND (`O2`.`id` = 2)) UNION SELECT `P1`, `O2` FROM Person AS `P1` JOIN Organization AS `O1` ON `P1`.org_id = `O1`.id JOIN Location AS `L1` ON `P1`.location_id = `L1`.id JOIN Organization AS `O2` ON `L1`.org_id = `O2`.id WHERE (((((`L1`.`name` != '') AND (`P1`.`name` != '')) AND (`O1`.`name` != '')) AND (`O2`.`name` != '')) AND (`O2`.`id` = 3))",
],
];
}
} }