mirror of
https://github.com/Combodo/iTop.git
synced 2026-03-01 07:04:16 +01:00
N°6182 - Fix crash with UNION OQL queries when classes / aliases are different
This commit is contained in:
@@ -1898,8 +1898,8 @@ class MenuBlock extends DisplayBlock
|
||||
}
|
||||
|
||||
// Any style actions
|
||||
// - Bulk actions on objects set (except unions...)
|
||||
if ($iSetCount > 1 && !$this->GetFilter() instanceof DBUnionSearch) {
|
||||
// - Bulk actions on objects set
|
||||
if ($iSetCount > 1) {
|
||||
// Bulk actions for each selected classes (eg. "link" and "remote" on n:n relations)
|
||||
foreach ($aSelectedClasses as $sSelectedAlias => $sSelectedClass) {
|
||||
$sSelectedClassName = MetaModel::GetName($sSelectedClass);
|
||||
|
||||
@@ -25,7 +25,7 @@ class DBUnionSearch extends DBSearch
|
||||
{
|
||||
protected $aSearches; // source queries
|
||||
protected $aSelectedClasses; // alias => classes (lowest common ancestors) computed at construction
|
||||
|
||||
protected $aColumnToAliases;
|
||||
/**
|
||||
* DBUnionSearch constructor.
|
||||
*
|
||||
@@ -94,11 +94,14 @@ class DBUnionSearch extends DBSearch
|
||||
|
||||
/**
|
||||
* Find the lowest common ancestor for each of the selected class
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function ComputeSelectedClasses()
|
||||
{
|
||||
// 1 - Collect all the column/classes
|
||||
$aColumnToClasses = array();
|
||||
$aColumnToClasses = [];
|
||||
$this->aColumnToAliases = [];
|
||||
foreach ($this->aSearches as $iPos => $oSearch)
|
||||
{
|
||||
$aSelected = array_values($oSearch->GetSelectedClasses());
|
||||
@@ -117,7 +120,14 @@ class DBUnionSearch extends DBSearch
|
||||
|
||||
foreach ($aSelected as $iColumn => $sClass)
|
||||
{
|
||||
$aColumnToClasses[$iColumn][] = $sClass;
|
||||
$aColumnToClasses[$iColumn][$iPos] = $sClass;
|
||||
}
|
||||
|
||||
// Store the aliases by column to map them later (the first query impose the aliases)
|
||||
$aAliases = array_keys($oSearch->GetSelectedClasses());
|
||||
foreach ($aAliases as $iColumn => $sAlias)
|
||||
{
|
||||
$this->aColumnToAliases[$iColumn][$iPos] = $sAlias;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +136,7 @@ class DBUnionSearch extends DBSearch
|
||||
$aColumnToAlias = array_keys($oFirstSearch->GetSelectedClasses());
|
||||
|
||||
// 3 - Compute alias => lowest common ancestor
|
||||
$this->aSelectedClasses = array();
|
||||
$this->aSelectedClasses = [];
|
||||
foreach ($aColumnToClasses as $iColumn => $aClasses)
|
||||
{
|
||||
$sAlias = $aColumnToAlias[$iColumn];
|
||||
@@ -215,15 +225,32 @@ class DBUnionSearch extends DBSearch
|
||||
|
||||
/**
|
||||
* @param array $aSelectedClasses array of aliases
|
||||
* @throws CoreException
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function SetSelectedClasses($aSelectedClasses)
|
||||
{
|
||||
// Get the columns corresponding the given aliases
|
||||
$aSelectedColumns = [];
|
||||
$oFirstSearch = $this->aSearches[0];
|
||||
$aAliasesToColumn = array_flip(array_keys($oFirstSearch->GetSelectedClasses()));
|
||||
foreach ($aSelectedClasses as $sSelectedAlias) {
|
||||
if (!isset($aAliasesToColumn[$sSelectedAlias])) {
|
||||
throw new CoreException("SetSelectedClasses: Invalid class alias $sSelectedAlias");
|
||||
}
|
||||
$aSelectedColumns[] = $aAliasesToColumn[$sSelectedAlias];
|
||||
}
|
||||
|
||||
// 1 - change for each search
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
foreach ($this->aSearches as $iPos => $oSearch)
|
||||
{
|
||||
$aCurrentSelectedAliases = [];
|
||||
foreach ($aSelectedColumns as $iColumn) {
|
||||
$aCurrentSelectedAliases[] = $this->aColumnToAliases[$iColumn][$iPos];
|
||||
}
|
||||
|
||||
// Throws an exception if not valid
|
||||
$oSearch->SetSelectedClasses($aSelectedClasses);
|
||||
$oSearch->SetSelectedClasses($aCurrentSelectedAliases);
|
||||
}
|
||||
// 2 - update the lowest common ancestors
|
||||
$this->ComputeSelectedClasses();
|
||||
|
||||
@@ -58,12 +58,11 @@ class DBSearchIntersectTest extends ItopTestCase
|
||||
'alias' => "ApplicationSolution",
|
||||
'result' => "SELECT `ApplicationSolution` FROM ApplicationSolution AS `ApplicationSolution` WHERE (`ApplicationSolution`.`org_id` = 3) UNION SELECT `BusinessProcess` FROM BusinessProcess AS `BusinessProcess` WHERE (`BusinessProcess`.`org_id` = 3)");
|
||||
|
||||
// Bug to fix
|
||||
// $aTests['Test union #2902'] = array(
|
||||
// 'left' => "SELECT `L-1` FROM ServiceFamily AS `L-1` WHERE 1",
|
||||
// 'right' => "SELECT `sf` FROM ServiceFamily AS `sf` JOIN Service AS `s` ON `s`.servicefamily_id = `sf`.id JOIN lnkCustomerContractToService AS `l1` ON `l1`.service_id = `s`.id JOIN CustomerContract AS `cc` ON `l1`.customercontract_id = `cc`.id WHERE (`cc`.`org_id` = 3) UNION SELECT `sf` FROM ServiceFamily AS `sf` JOIN Service AS `s` ON `s`.servicefamily_id = `sf`.id JOIN lnkCustomerContractToService AS `l1` ON `l1`.service_id = `s`.id JOIN CustomerContract AS `cc` ON `l1`.customercontract_id = `cc`.id JOIN Organization AS `child` ON `cc`.org_id = `child`.id JOIN Organization AS `root` ON `child`.parent_id BELOW `root`.id WHERE (`root`.`id` = 3)",
|
||||
// 'alias' => "L-1",
|
||||
// 'result' => "SELECT `L-1` FROM ServiceFamily AS `L-1` JOIN Service AS `s` ON `s`.servicefamily_id = `L-1`.id JOIN lnkCustomerContractToService AS `l1` ON `l1`.service_id = `s`.id JOIN CustomerContract AS `cc` ON `l1`.customercontract_id = `cc`.id WHERE (`cc`.`org_id` = 3) UNION SELECT `L-1` FROM ServiceFamily AS `L-1` JOIN Service AS `s` ON `s`.servicefamily_id = `L-1`.id JOIN lnkCustomerContractToService AS `l1` ON `l1`.service_id = `s`.id JOIN CustomerContract AS `cc` ON `l1`.customercontract_id = `cc`.id JOIN Organization AS `child` ON `cc`.org_id = `child`.id JOIN Organization AS `root` ON `child`.parent_id BELOW `root`.id WHERE (`root`.`id` = 3)");
|
||||
$aTests['Test union #2902'] = array(
|
||||
'left' => "SELECT `L-1` FROM ServiceFamily AS `L-1` WHERE 1",
|
||||
'right' => "SELECT `sf` FROM ServiceFamily AS `sf` JOIN Service AS `s` ON `s`.servicefamily_id = `sf`.id JOIN lnkCustomerContractToService AS `l1` ON `l1`.service_id = `s`.id JOIN CustomerContract AS `cc` ON `l1`.customercontract_id = `cc`.id WHERE (`cc`.`org_id` = 3) UNION SELECT `sf` FROM ServiceFamily AS `sf` JOIN Service AS `s` ON `s`.servicefamily_id = `sf`.id JOIN lnkCustomerContractToService AS `l1` ON `l1`.service_id = `s`.id JOIN CustomerContract AS `cc` ON `l1`.customercontract_id = `cc`.id JOIN Organization AS `child` ON `cc`.org_id = `child`.id JOIN Organization AS `root` ON `child`.parent_id BELOW `root`.id WHERE (`root`.`id` = 3)",
|
||||
'alias' => "L-1",
|
||||
'result' => "SELECT `L-1` FROM ServiceFamily AS `L-1` JOIN Service AS `s` ON `s`.servicefamily_id = `L-1`.id JOIN lnkCustomerContractToService AS `l1` ON `l1`.service_id = `s`.id JOIN CustomerContract AS `cc` ON `l1`.customercontract_id = `cc`.id WHERE (`cc`.`org_id` = 3) UNION SELECT `L-1` FROM ServiceFamily AS `L-1` JOIN Service AS `s` ON `s`.servicefamily_id = `L-1`.id JOIN lnkCustomerContractToService AS `l1` ON `l1`.service_id = `s`.id JOIN CustomerContract AS `cc` ON `l1`.customercontract_id = `cc`.id JOIN Organization AS `child` ON `cc`.org_id = `child`.id JOIN Organization AS `root` ON `child`.parent_id BELOW `root`.id WHERE (`root`.`id` = 3)");
|
||||
|
||||
$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",
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace Combodo\iTop\Test\UnitTest\Core;
|
||||
use CMDBSource;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use CoreOqlMultipleResultsForbiddenException;
|
||||
use DBObjectSet;
|
||||
use DBSearch;
|
||||
use Exception;
|
||||
use Expression;
|
||||
@@ -745,4 +746,30 @@ class DBSearchTest extends ItopDataTestCase
|
||||
$oSearch->MakeSelectQuery();
|
||||
self::assertTrue(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider QueriesProvider
|
||||
* @param $sOQL
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testQueries($sOQL)
|
||||
{
|
||||
$oSearch = DBSearch::FromOQL($sOQL);
|
||||
$oSet = new DBObjectSet($oSearch);
|
||||
if ($oSet->Count() > 0) {
|
||||
$aSelectedAliases = array_keys($oSearch->GetSelectedClasses());
|
||||
$aFirstRow = $oSet->FetchAssoc();
|
||||
$aAliases = array_keys($aFirstRow);
|
||||
$this->assertEquals($aSelectedAliases, $aAliases);
|
||||
}
|
||||
}
|
||||
|
||||
public function QueriesProvider()
|
||||
{
|
||||
return [
|
||||
['SELECT L,P FROM Person AS P JOIN Location AS L ON P.location_id=L.id'],
|
||||
['SELECT P,L FROM Person AS P JOIN Location AS L ON P.location_id=L.id'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,52 @@ use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
class DBUnionSearchTest extends ItopDataTestCase
|
||||
{
|
||||
|
||||
/**
|
||||
* @dataProvider UnionSearchProvider
|
||||
*
|
||||
* @param $sOQL
|
||||
*
|
||||
* @return void
|
||||
* @throws \CoreException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function testUnionSearch($sOQL)
|
||||
{
|
||||
$oSearch = DBSearch::FromOQL($sOQL);
|
||||
|
||||
$oSet = new DBObjectSet($oSearch);
|
||||
if ($oSet->Count() > 0) {
|
||||
$aSelectedAliases = array_keys($oSearch->GetSelectedClasses());
|
||||
$aFirstRow = $oSet->FetchAssoc();
|
||||
$aAliases = array_keys($aFirstRow);
|
||||
$this->assertEquals($aSelectedAliases, $aAliases);
|
||||
}
|
||||
|
||||
$aSelectedClasses = $oSearch->GetSelectedClasses();
|
||||
foreach ($aSelectedClasses as $sSelectedAlias => $sSelectedClass) {
|
||||
$oSearchTest = $oSearch->DeepClone();
|
||||
$oSearchTest->SetSelectedClasses([$sSelectedAlias]);
|
||||
$oSet = new DBObjectSet($oSearchTest);
|
||||
if ($oSet->Count() > 0) {
|
||||
$aSelectedAliases = array_keys($oSearchTest->GetSelectedClasses());
|
||||
$aFirstRow = $oSet->FetchAssoc();
|
||||
$aAliases = array_keys($aFirstRow);
|
||||
$this->assertEquals($aSelectedAliases, $aAliases);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function UnionSearchProvider()
|
||||
{
|
||||
return [
|
||||
'Same class' => ["SELECT Server UNION SELECT Server"],
|
||||
'different class same alias' => ['SELECT Server AS fci UNION SELECT VirtualMachine AS fci'],
|
||||
'different class no alias' => ['SELECT Server UNION SELECT VirtualMachine'],
|
||||
'multiple classes same alias' => ['SELECT `L`, `P` FROM Location AS `L` JOIN Person AS `P` ON `P`.location_id = `L`.id WHERE (`L`.`org_id` = 3) UNION SELECT `L`, `P` FROM Location AS `L` JOIN Person AS `P` ON `P`.location_id = `L`.id WHERE (`L`.`org_id` = 2)'],
|
||||
'multiple classes' => ['SELECT `L`, `P` FROM Location AS `L` JOIN Person AS `P` ON `P`.location_id = `L`.id WHERE (`L`.`org_id` = 3) UNION SELECT `L1`, `P1` FROM Person AS `P1` JOIN Location AS `L1` ON `P1`.location_id = `L1`.id WHERE (`P1`.`org_id` = 2)'],
|
||||
];
|
||||
}
|
||||
|
||||
public function testFilterOnFirstSelectedClass()
|
||||
{
|
||||
$sSourceOQL = 'SELECT `Person`, `Location` FROM Person AS `Person` JOIN Location AS `Location` ON `Person`.location_id = `Location`.id WHERE (`Location`.`id` = 1)';
|
||||
|
||||
Reference in New Issue
Block a user