N°1161 - Add functions, order by and limits to DBSearch::MakeGroupByQuery()

SVN:trunk[5350]
This commit is contained in:
Eric Espié
2018-02-16 12:59:35 +00:00
parent 7ea9b5b2f3
commit 03f9a9fcac
10 changed files with 1044 additions and 45 deletions

View File

@@ -1434,7 +1434,7 @@ class DBObjectSearch extends DBSearch
return $sRet;
}
public function GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr = null, $aSelectedClasses = null)
public function GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr = null, $aSelectedClasses = null, $aSelectExpr = null)
{
// Hide objects that are not visible to the current user
//
@@ -1517,7 +1517,15 @@ class DBObjectSearch extends DBSearch
$sRawId .= 'g:'.$sAlias.'!'.$oExpr->Render();
}
}
if (!is_null($aSelectExpr))
{
foreach($aSelectExpr as $sAlias => $oExpr)
{
$sRawId .= 'se:'.$sAlias.'!'.$oExpr->Render();
}
}
$aContextData['aGroupByExpr'] = $aGroupByExpr;
$aContextData['aSelectExpr'] = $aSelectExpr;
$sRawId .= $bGetCount;
$aContextData['bGetCount'] = $bGetCount;
if (is_array($aSelectedClasses))
@@ -1542,6 +1550,7 @@ class DBObjectSearch extends DBSearch
// Query caching
//
$sOqlAPCCacheId = null;
if (self::$m_bQueryCacheEnabled)
{
// Warning: using directly the query string as the key to the hash array can FAIL if the string
@@ -1581,7 +1590,7 @@ class DBObjectSearch extends DBSearch
if (!isset($oSQLQuery))
{
$oKPI = new ExecutionKPI();
$oSQLQuery = $oSearch->BuildSQLQueryStruct($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr, $aSelectedClasses);
$oSQLQuery = $oSearch->BuildSQLQueryStruct($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr, $aSelectedClasses, $aSelectExpr);
$oKPI->ComputeStats('BuildSQLQueryStruct', $sOqlQuery);
if (self::$m_bQueryCacheEnabled)
@@ -1601,16 +1610,17 @@ class DBObjectSearch extends DBSearch
}
/**
* @param $aAttToLoad
* @param $bGetCount
* @param $aModifierProperties
* @param null $aGroupByExpr
* @param null $aSelectedClasses
* @param array $aAttToLoad
* @param bool $bGetCount
* @param array $aModifierProperties
* @param array $aGroupByExpr
* @param array $aSelectedClasses
* @param array $aSelectExpr
* @return null|SQLObjectQuery
*/
protected function BuildSQLQueryStruct($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr = null, $aSelectedClasses = null)
protected function BuildSQLQueryStruct($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr = null, $aSelectedClasses = null, $aSelectExpr = null)
{
$oBuild = new QueryBuilderContext($this, $aModifierProperties, $aGroupByExpr, $aSelectedClasses);
$oBuild = new QueryBuilderContext($this, $aModifierProperties, $aGroupByExpr, $aSelectedClasses, $aSelectExpr);
$oSQLQuery = $this->MakeSQLObjectQuery($oBuild, $aAttToLoad, array());
$oSQLQuery->SetCondition($oBuild->m_oQBExpressions->GetCondition());
@@ -1624,6 +1634,17 @@ class DBObjectSearch extends DBSearch
{
$oSQLQuery->SetSelect($oBuild->m_oQBExpressions->GetSelect());
}
if ($aSelectExpr)
{
// Get the fields corresponding to the select expressions
foreach($oBuild->m_oQBExpressions->GetSelect() as $sAlias => $oExpr)
{
if (key_exists($sAlias, $aSelectExpr))
{
$oSQLQuery->AddSelect($sAlias, $oExpr);
}
}
}
$aMandatoryTables = null;
if (self::$m_bOptimizeQueries)

View File

@@ -429,8 +429,53 @@ abstract class DBSearch
protected static $m_aQueryStructCache = array();
public function MakeGroupByQuery($aArgs, $aGroupByExpr, $bExcludeNullValues = false)
/** Generate a Group By SQL request from a search
* @param array $aArgs
* @param array $aGroupByExpr array('alias' => Expression)
* @param bool $bExcludeNullValues
* @param array $aSelectExpr array('alias' => Expression) Additional expressions added to the request
* @param array $aOrderBy array('alias' => bool) true = ASC false = DESC
* @param int $iLimitCount
* @param int $iLimitStart
* @return string SQL query generated
* @throws Exception
*/
public function MakeGroupByQuery($aArgs, $aGroupByExpr, $bExcludeNullValues = false, $aSelectExpr = array(), $aOrderBy = array(), $iLimitCount = 0, $iLimitStart = 0)
{
// Sanity check
foreach($aGroupByExpr as $sAlias => $oExpr)
{
if (!($oExpr instanceof Expression))
{
throw new CoreException("Wrong parameter for 'Group By' for [$sAlias] (an array('alias' => Expression) is awaited)");
}
}
foreach($aSelectExpr as $sAlias => $oExpr)
{
if (array_key_exists($sAlias, $aGroupByExpr))
{
throw new CoreException("Alias collision between 'Group By' and 'Select Expressions' [$sAlias]");
}
if (!($oExpr instanceof Expression))
{
throw new CoreException("Wrong parameter for 'Select Expressions' for [$sAlias] (an array('alias' => Expression) is awaited)");
}
}
foreach($aOrderBy as $sAlias => $bAscending)
{
if (!array_key_exists($sAlias, $aGroupByExpr) && !array_key_exists($sAlias, $aSelectExpr) && ($sAlias != '_itop_count_'))
{
$aAllowedAliases = array_keys($aSelectExpr);
$aAllowedAliases = array_merge($aAllowedAliases, array_keys($aGroupByExpr));
$aAllowedAliases[] = '_itop_count_';
throw new CoreException("Wrong alias [$sAlias] for 'Order By'. Allowed values are: ", null, implode(", ", $aAllowedAliases));
}
if (!is_bool($bAscending))
{
throw new CoreException("Wrong direction in ORDER BY spec, found '$bAscending' and expecting a boolean value for '$sAlias''");
}
}
if ($bExcludeNullValues)
{
// Null values are not handled (though external keys set to 0 are allowed)
@@ -448,15 +493,15 @@ abstract class DBSearch
}
$aAttToLoad = array();
$oSQLQuery = $oQueryFilter->GetSQLQuery(array(), $aArgs, $aAttToLoad, null, 0, 0, false, $aGroupByExpr);
$oSQLQuery = $oQueryFilter->GetSQLQuery(array(), $aArgs, $aAttToLoad, null, 0, 0, false, $aGroupByExpr, $aSelectExpr);
$aScalarArgs = MetaModel::PrepareQueryArguments($aArgs, $this->GetInternalParams());
try
{
$bBeautifulSQL = self::$m_bTraceQueries || self::$m_bDebugQuery || self::$m_bIndentQueries;
$sRes = $oSQLQuery->RenderGroupBy($aScalarArgs, $bBeautifulSQL);
$sRes = $oSQLQuery->RenderGroupBy($aScalarArgs, $bBeautifulSQL, $aOrderBy, $iLimitCount, $iLimitStart);
}
catch (MissingQueryArgument $e)
catch (Exception $e)
{
// Add some information...
$e->addInfo('OQL', $this->ToOQL());
@@ -563,9 +608,9 @@ abstract class DBSearch
}
protected function GetSQLQuery($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount, $aGroupByExpr = null)
protected function GetSQLQuery($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount, $aGroupByExpr = null, $aSelectExpr = null)
{
$oSQLQuery = $this->GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr);
$oSQLQuery = $this->GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr, null, $aSelectExpr);
$oSQLQuery->SetSourceOQL($this->ToOQL());
// Join to an additional table, if required...
@@ -587,7 +632,7 @@ abstract class DBSearch
}
public abstract function GetSQLQueryStructure(
$aAttToLoad, $bGetCount, $aGroupByExpr = null, $aSelectedClasses = null
$aAttToLoad, $bGetCount, $aGroupByExpr = null, $aSelectedClasses = null, $aSelectExpr = null
);
////////////////////////////////////////////////////////////////////////////

View File

@@ -143,7 +143,7 @@ class DBUnionSearch extends DBSearch
/**
* Limited to the selected classes
*/
*/
public function GetClassName($sAlias)
{
if (array_key_exists($sAlias, $this->aSelectedClasses))
@@ -474,15 +474,17 @@ class DBUnionSearch extends DBSearch
throw new Exception('MakeUpdateQuery is not implemented for the unions!');
}
public function GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr = null, $aSelectedClasses = null)
public function GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr = null, $aSelectedClasses = null, $aSelectExpr = null)
{
if (count($this->aSearches) == 1)
{
return $this->aSearches[0]->GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr);
return $this->aSearches[0]->GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr, $aSelectExpr);
}
$aSQLQueries = array();
$aAliases = array_keys($this->aSelectedClasses);
$aQueryAttToLoad = null;
$aUnionQuerySelectExpr = array();
foreach ($this->aSearches as $iSearch => $oSearch)
{
$aSearchAliases = array_keys($oSearch->GetSelectedClasses());
@@ -544,7 +546,43 @@ class DBUnionSearch extends DBSearch
$aQueryGroupByExpr[$sExpressionAlias] = $oExpression->Translate($aTranslationData, false, false);
}
}
$oSubQuery = $oSearch->GetSQLQueryStructure($aQueryAttToLoad, false, $aQueryGroupByExpr, $aSearchSelectedClasses);
if (is_null($aSelectExpr))
{
$aQuerySelectExpr = null;
}
else
{
$aQuerySelectExpr = array();
$aTranslationData = array();
$aQueryColumns = array_keys($oSearch->GetSelectedClasses());
foreach($aAliases as $iColumn => $sAlias)
{
$sQueryAlias = $aQueryColumns[$iColumn];
$aTranslationData[$sAlias]['*'] = $sQueryAlias;
}
foreach($aSelectExpr as $sExpressionAlias => $oExpression)
{
$oExpression->Browse(function ($oNode) use (&$aQuerySelectExpr, &$aTranslationData)
{
if ($oNode instanceof FieldExpression)
{
$sAlias = $oNode->GetParent()."__".$oNode->GetName();
if (!key_exists($sAlias, $aQuerySelectExpr))
{
$aQuerySelectExpr[$sAlias] = $oNode->Translate($aTranslationData, false, false);
}
$aTranslationData[$oNode->GetParent()][$oNode->GetName()] = new FieldExpression($sAlias);
}
});
// Only done for the first select as aliases are named after the first query
if (!array_key_exists($sExpressionAlias, $aUnionQuerySelectExpr))
{
$aUnionQuerySelectExpr[$sExpressionAlias] = $oExpression->Translate($aTranslationData, false, false);
}
}
}
$oSubQuery = $oSearch->GetSQLQueryStructure($aQueryAttToLoad, false, $aQueryGroupByExpr, $aSearchSelectedClasses, $aQuerySelectExpr);
if (count($aSearchAliases) > 1)
{
// Necessary to make sure that selected columns will match throughout all the queries
@@ -554,7 +592,7 @@ class DBUnionSearch extends DBSearch
$aSQLQueries[] = $oSubQuery;
}
$oSQLQuery = new SQLUnionQuery($aSQLQueries, $aGroupByExpr);
$oSQLQuery = new SQLUnionQuery($aSQLQueries, $aGroupByExpr, $aUnionQuerySelectExpr);
//MyHelpers::var_dump_html($oSQLQuery, true);
//MyHelpers::var_dump_html($oSQLQuery->RenderSelect(), true);
if (self::$m_bDebugQuery) $oSQLQuery->DisplayHtml();

View File

@@ -70,7 +70,7 @@ abstract class Expression
{
return base64_encode($this->Render());
}
static public function unserialize($sValue)
{
return self::FromOQL(base64_decode($sValue));
@@ -1373,7 +1373,7 @@ class QueryBuilderExpressions
*/
protected $m_aClassIds;
public function __construct(DBObjectSearch $oSearch, $aGroupByExpr = null)
public function __construct(DBObjectSearch $oSearch, $aGroupByExpr = null, $aSelectExpr = null)
{
$this->m_oConditionExpr = $oSearch->GetCriteria();
if (!$oSearch->GetShowObsoleteData())
@@ -1387,7 +1387,7 @@ class QueryBuilderExpressions
}
}
}
$this->m_aSelectExpr = array();
$this->m_aSelectExpr = is_null($aSelectExpr) ? array() : $aSelectExpr;
$this->m_aGroupByExpr = $aGroupByExpr;
$this->m_aJoinFields = array();
@@ -1448,8 +1448,10 @@ class QueryBuilderExpressions
/**
* Get tables representing the queried objects
* Could be further optimized: when the first join is an outer join, then the rest can be omitted
*/
* Could be further optimized: when the first join is an outer join, then the rest can be omitted
* @param array $aTables
* @return array
*/
public function GetMandatoryTables(&$aTables = null)
{
if (is_null($aTables)) $aTables = array();
@@ -1458,6 +1460,8 @@ class QueryBuilderExpressions
{
$oExpression->CollectUsedParents($aTables);
}
return $aTables;
}
public function GetUnresolvedFields($sAlias, &$aUnresolved)
@@ -1498,7 +1502,6 @@ class QueryBuilderExpressions
{
$this->m_aJoinFields[$index] = $oExpression->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);
}
foreach($this->m_aClassIds as $sClass => $oExpression)
{
$this->m_aClassIds[$sClass] = $oExpression->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);

View File

@@ -34,10 +34,10 @@ class QueryBuilderContext
public $m_oQBExpressions;
public function __construct($oFilter, $aModifierProperties, $aGroupByExpr = null, $aSelectedClasses = null)
public function __construct($oFilter, $aModifierProperties, $aGroupByExpr = null, $aSelectedClasses = null, $aSelectExpr = null)
{
$this->m_oRootFilter = $oFilter;
$this->m_oQBExpressions = new QueryBuilderExpressions($oFilter, $aGroupByExpr);
$this->m_oQBExpressions = new QueryBuilderExpressions($oFilter, $aGroupByExpr, $aSelectExpr);
$this->m_aClassAliases = $oFilter->GetJoinedClasses();
$this->m_aTableAliases = array();

View File

@@ -293,11 +293,12 @@ class SQLObjectQuery extends SQLQuery
* Needed for the unions
* @param $aOrderBy
* @return string
* @throws CoreException
*/
public function RenderOrderByClause($aOrderBy)
{
$this->PrepareRendering();
$sOrderBy = self::ClauseOrderBy($aOrderBy);
$sOrderBy = self::ClauseOrderBy($aOrderBy, $this->__aFields);
return $sOrderBy;
}
@@ -357,8 +358,8 @@ class SQLObjectQuery extends SQLQuery
}
else
{
$sSelect = self::ClauseSelect($this->__aFields);
$sOrderBy = self::ClauseOrderBy($aOrderBy);
$sSelect = self::ClauseSelect($this->__aFields, $sLineSep);
$sOrderBy = self::ClauseOrderBy($aOrderBy, $this->__aFields);
if (!empty($sOrderBy))
{
$sOrderBy = "ORDER BY $sOrderBy$sLineSep";
@@ -381,21 +382,42 @@ class SQLObjectQuery extends SQLQuery
/**
* @param array $aArgs
* @param bool $bBeautifulQuery
* @param array $aOrderBy
* @param int $iLimitCount
* @param int $iLimitStart
* @return string
* @throws CoreException
*/
public function RenderGroupBy($aArgs = array(), $bBeautifulQuery = false)
public function RenderGroupBy($aArgs = array(), $bBeautifulQuery = false, $aOrderBy = array(), $iLimitCount = 0, $iLimitStart = 0)
{
$this->m_bBeautifulQuery = $bBeautifulQuery;
$sLineSep = $this->m_bBeautifulQuery ? "\n" : '';
$sIndent = $this->m_bBeautifulQuery ? " " : null;
$this->PrepareRendering();
$sSelect = self::ClauseSelect($this->__aFields);
$sFrom = self::ClauseFrom($this->__aFrom, $sIndent);
$sWhere = self::ClauseWhere($this->m_oConditionExpr, $aArgs);
$sGroupBy = self::ClauseGroupBy($this->__aGroupBy);
$sSQL = "SELECT $sSelect,$sLineSep COUNT(*) AS _itop_count_$sLineSep FROM $sFrom$sLineSep WHERE $sWhere$sLineSep GROUP BY $sGroupBy";
$sOrderBy = self::ClauseOrderBy($aOrderBy, $this->__aFields);
if (!empty($sGroupBy))
{
$sGroupBy = "GROUP BY $sGroupBy$sLineSep";
}
if (!empty($sOrderBy))
{
$sOrderBy = "ORDER BY $sOrderBy$sLineSep";
}
if ($iLimitCount > 0)
{
$sLimit = 'LIMIT '.$iLimitStart.', '.$iLimitCount;
}
else
{
$sLimit = '';
}
$sSQL = "SELECT $sSelect,$sLineSep COUNT(*) AS _itop_count_$sLineSep FROM $sFrom$sLineSep WHERE $sWhere$sLineSep $sGroupBy $sOrderBy$sLineSep $sLimit";
return $sSQL;
}

View File

@@ -69,18 +69,18 @@ abstract class SQLQuery
abstract public function RenderDelete($aArgs = array());
abstract public function RenderUpdate($aArgs = array());
abstract public function RenderSelect($aOrderBy = array(), $aArgs = array(), $iLimitCount = 0, $iLimitStart = 0, $bGetCount = false, $bBeautifulQuery = false);
abstract public function RenderGroupBy($aArgs = array(), $bBeautifulQuery = false);
abstract public function RenderGroupBy($aArgs = array(), $bBeautifulQuery = false, $aOrderBy = array(), $iLimitCount = 0, $iLimitStart = 0);
abstract public function OptimizeJoins($aUsedTables, $bTopCall = true);
protected static function ClauseSelect($aFields)
protected static function ClauseSelect($aFields, $sLineSep = '')
{
$aSelect = array();
foreach ($aFields as $sFieldAlias => $sSQLExpr)
{
$aSelect[] = "$sSQLExpr AS $sFieldAlias";
}
$sSelect = implode(', ', $aSelect);
$sSelect = implode(",$sLineSep ", $aSelect);
return $sSelect;
}
@@ -181,7 +181,13 @@ abstract class SQLQuery
}
}
protected static function ClauseOrderBy($aOrderBy)
/**
* @param array $aOrderBy
* @param array $aExistingFields
* @return string
* @throws CoreException
*/
protected static function ClauseOrderBy($aOrderBy, $aExistingFields)
{
$aOrderBySpec = array();
foreach($aOrderBy as $sFieldAlias => $bAscending)

View File

@@ -38,8 +38,9 @@ class SQLUnionQuery extends SQLQuery
{
protected $aQueries;
protected $aGroupBy;
protected $aSelectExpr;
public function __construct($aQueries, $aGroupBy)
public function __construct($aQueries, $aGroupBy, $aSelectExpr = array())
{
parent::__construct();
@@ -49,6 +50,7 @@ class SQLUnionQuery extends SQLQuery
$this->aQueries[] = $oSQLQuery->DeepClone();
}
$this->aGroupBy = $aGroupBy;
$this->aSelectExpr = $aSelectExpr;
}
public function DisplayHtml()
@@ -129,7 +131,17 @@ class SQLUnionQuery extends SQLQuery
}
// Interface, build the SQL query
public function RenderGroupBy($aArgs = array(), $bBeautifulQuery = false)
/**
* @param array $aArgs
* @param bool $bBeautifulQuery
* @param array $aOrderBy
* @param int $iLimitCount
* @param int $iLimitStart
* @return string
* @throws CoreException
*/
public function RenderGroupBy($aArgs = array(), $bBeautifulQuery = false, $aOrderBy = array(), $iLimitCount = 0, $iLimitStart = 0)
{
$this->m_bBeautifulQuery = $bBeautifulQuery;
$sLineSep = $this->m_bBeautifulQuery ? "\n" : '';
@@ -143,15 +155,41 @@ class SQLUnionQuery extends SQLQuery
$sSelects = '('.implode(")$sLineSep UNION$sLineSep(", $aSelects).')';
$sFrom = "($sLineSep$sSelects$sLineSep) as __selects__";
$aAliases = array();
$aSelectAliases = array();
$aGroupAliases = array();
foreach ($this->aGroupBy as $sGroupAlias => $trash)
{
$aAliases[] = "`$sGroupAlias`";
$aSelectAliases[$sGroupAlias] = "`$sGroupAlias`";
$aGroupAliases[] = "`$sGroupAlias`";
}
foreach($this->aSelectExpr as $sSelectAlias => $oExpr)
{
$aSelectAliases[$sSelectAlias] = $oExpr->Render()." AS `$sSelectAlias`";
}
$sSelect = implode(', ', $aAliases);
$sGroupBy = implode(', ', $aAliases);
$sSQL = "SELECT $sSelect,$sLineSep COUNT(*) AS _itop_count_$sLineSep FROM $sFrom$sLineSep GROUP BY $sGroupBy";
$sSelect = implode(",$sLineSep ", $aSelectAliases);
$sGroupBy = implode(', ', $aGroupAliases);
$sOrderBy = self::ClauseOrderBy($aOrderBy, $aSelectAliases);
if (!empty($sGroupBy))
{
$sGroupBy = "GROUP BY $sGroupBy$sLineSep";
}
if (!empty($sOrderBy))
{
$sOrderBy = "ORDER BY $sOrderBy$sLineSep";
}
if ($iLimitCount > 0)
{
$sLimit = 'LIMIT '.$iLimitStart.', '.$iLimitCount;
}
else
{
$sLimit = '';
}
$sSQL = "SELECT $sSelect,$sLineSep COUNT(*) AS _itop_count_$sLineSep FROM $sFrom$sLineSep $sGroupBy $sOrderBy$sLineSep $sLimit";
return $sSQL;
}

View File

@@ -0,0 +1,343 @@
<?php
// Copyright (c) 2010-2018 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
//
require_once ('../approot.inc.php');
require_once(APPROOT.'application/application.inc.php');
require_once(APPROOT.'application/itopwebpage.class.inc.php');
require_once(APPROOT.'application/startup.inc.php');
require_once(APPROOT.'application/loginwebpage.class.inc.php');
/////////////////////////////////////////////////////////////////////
// Main program
//
LoginWebPage::DoLogin(true); // Check user rights and prompt if needed
$sSubmit = utils::ReadParam('submit', '', false, 'raw_data');
if ($sSubmit != 'Reset')
{
$sOQL = utils::ReadParam('OQL_Request', '', false, 'raw_data');
}
else
{
$sOQL = '';
}
$bError = false;
$oP = new iTopWebPage('Database inconsistencies');
$oP->set_base(utils::GetAbsoluteUrlAppRoot().'test/');
$oP->set_title('Grouping with functions');
$oP->add('<div style="padding: 15px;"><h2>Grouping with functions</h2>');
$oP->add('<div style="padding: 15px; background: #ddd;">');
try
{
if (!empty($sOQL))
{
// Getting class attributes
$oSearch = DBSearch::FromOQL($sOQL);
$aSearches = $oSearch->GetSearches();
if ($oSearch instanceof DBUnionSearch)
{
$sClass = $aSearches[0]->GetClassAlias();
$sRealClass = $aSearches[0]->GetClass();
}
else
{
$sClass = $oSearch->GetClassAlias();
$sRealClass = $oSearch->GetClass();
}
$sGroupBy1 = utils::ReadParam('groupby_1', '');
$sGroupBy2 = utils::ReadParam('groupby_2', '');
$sOrderBy1 = utils::ReadParam('orderby_1', '');
$sOrderBy2 = utils::ReadParam('orderby_2', '');
$sAttributesOptions1 = '';
$sAttributesOptions2 = '';
$sAttributesOptions3 = '';
$sAttributesOptions4 = '';
foreach(array('_itop_sum_', '_itop_avg_', '_itop_min_', '_itop_max_', '_itop_count_', 'group1', 'group2') as $sAttCode)
{
$sAttributesOptions3 .= '<option value="'.$sAttCode.'" '.($sOrderBy1 == $sAttCode ? 'selected' : '').'>'.$sAttCode.'</option>';
$sAttributesOptions4 .= '<option value="'.$sAttCode.'" '.($sOrderBy2 == $sAttCode ? 'selected' : '').'>'.$sAttCode.'</option>';
}
foreach(MetaModel::ListAttributeDefs($sRealClass) as $sAttCode => $oAttDef)
{
// Skip this attribute if not defined in this table
if ($oSearch instanceof DBUnionSearch)
{
foreach($aSearches as $oSubQuery)
{
$sSubClass = $oSubQuery->GetClass();
if (!MetaModel::IsValidAttCode($sSubClass, $sAttCode))
{
continue 2;
}
}
}
$sAttributesOptions1 .= '<option value="'.$sAttCode.'" '.($sGroupBy1 == $sAttCode ? 'selected' : '').'>'.$sAttCode.'</option>';
$sAttributesOptions2 .= '<option value="'.$sAttCode.'" '.($sGroupBy2 == $sAttCode ? 'selected' : '').'>'.$sAttCode.'</option>';
}
$iLimit = intval(utils::ReadParam('top', '0'));
$sInvOrder1 = utils::ReadParam('desc1', '');
$sCheck1 = ($sInvOrder1 == 'on' ? 'checked' : '');
$sInvOrder2 = utils::ReadParam('desc2', '');
$sCheck2 = ($sInvOrder2 == 'on' ? 'checked' : '');
$sFuncField = utils::ReadParam('funcfield', '');
$sFuncFieldOption = '';
foreach(MetaModel::ListAttributeDefs($sRealClass) as $sAttCode => $oAttDef)
{
// Skip this attribute if not defined in this table
if ($oSearch instanceof DBUnionSearch)
{
foreach($aSearches as $oSubQuery)
{
$sSubClass = $oSubQuery->GetClass();
if (!MetaModel::IsValidAttCode($sSubClass, $sAttCode))
{
continue 2;
}
}
}
switch (get_class($oAttDef))
{
case 'Integer':
case 'AttributeDecimal':
case 'AttributeDuration':
case 'AttributeSubItem':
case 'AttributePercentage':
$sFuncFieldOption .= '<option value="'.$sAttCode.'" '.($sFuncField == $sAttCode ? 'selected' : '').'>'.$sAttCode.'</option>';
break;
}
}
}
}
catch (Exception $e)
{
$oP->p('<div class="header_message message_error">'.$e->getMessage().'</div>');
$bError = true;
}
$oP->add("<div><form>");
$oP->add("<input type=\"submit\" name=\"submit\" value=\"Reset\">\n");
$oP->add("</form></div>");
$oP->add("<form>");
$oP->add(
<<<EOF
<div>
<label>Search OQL:</label>
<div>
<textarea id='OQL_Request' name='OQL_Request' cols='60' rows='5'>$sOQL</textarea>
</div>
</div>
EOF
);
if (!empty($sOQL) && !$bError)
{
$oP->add(
<<<EOF
<div>
<label>Group by:</label>
<div>
<select id="groupby_1" name="groupby_1">
$sAttributesOptions1
</select>
<select id="groupby_2" name="groupby_2">
<option></option>
$sAttributesOptions2
</select>
</div>
</div>
<div>
<label>Order by:</label>
<div>
<select id="orderby_1" name="orderby_1">$sAttributesOptions3</select>
<label>Inv order</label><input type="checkbox" name="desc1" $sCheck1/>
</div>
<div>
<select id="orderby_2" name="orderby_2">
<option></option>
$sAttributesOptions4
</select>
<label>Inv order</label><input type="checkbox" name="desc2" $sCheck2/>
</div>
</div>
<div>
<label>Functions on:</label>
<div>
<select id="funcfield" name="funcfield">$sFuncFieldOption</select>
</div>
</div>
<div>
<label>Top:</label>
<div><input type="text" id="top" name="top" value="$iLimit"/>
</div>
</div>
EOF
);
}
$oP->add("<input type=\"submit\" name=\"submit\" value=\"Search\">\n");
$oP->add("</form>");
$sSQL = '';
if (empty($sOQL) || empty($sGroupBy1))
{
$oP->output();
return;
}
try
{
$iLimitStart = 0;
$aOrderBy = array();
if (!empty($sOrderBy1))
{
$aOrderBy[$sOrderBy1] = ($sInvOrder1 != 'on');
}
if (!empty($sOrderBy2))
{
$aOrderBy[$sOrderBy2] = ($sInvOrder2 != 'on');
}
$aGroupBy = array();
$oExpr1 = Expression::FromOQL($sClass.'.'.$sGroupBy1);
$aGroupBy["group1"] = $oExpr1;
if (!empty($sGroupBy2))
{
$oExpr2 = Expression::FromOQL($sClass.'.'.$sGroupBy2);
$aGroupBy["group2"] = $oExpr2;
}
$aArgs = array();
if (empty($sFuncField))
{
$aFunctions = array();
}
else
{
$oTimeExpr = Expression::FromOQL($sClass.'.'.$sFuncField);
$oSumExpr = new FunctionExpression('SUM', array($oTimeExpr));
$oAvgExpr = new FunctionExpression('AVG', array($oTimeExpr));
$oMinExpr = new FunctionExpression('MIN', array($oTimeExpr));
$oMaxExpr = new FunctionExpression('MAX', array($oTimeExpr));
// Alias => Expression
$aFunctions = array(
'_itop_sum_' => $oSumExpr,
'_itop_avg_' => $oAvgExpr,
'_itop_min_' => $oMinExpr,
'_itop_max_' => $oMaxExpr,
);
}
$sSQL = $oSearch->MakeGroupByQuery($aArgs, $aGroupBy, false, $aFunctions, $aOrderBy, $iLimit, $iLimitStart);
$aRes = CMDBSource::QueryToArray($sSQL);
// Display results
if (!empty($aRes))
{
$oP->add('<div>');
$oP->add('<table class="listResults">');
$aLine = $aRes[0];
$aCols = array();
$oP->add('<tr>');
foreach(array_keys($aLine) as $item)
{
if (!is_numeric($item))
{
$aCols[] = $item;
$oP->add("<th>$item</th>");
}
}
$oP->add('</tr>');
foreach($aRes as $aLine)
{
$oP->add('<tr>');
foreach($aCols as $sCol)
{
$oP->add("<td>".$aLine[$sCol]."</td>");
}
$oP->add('</tr>');
}
$oP->add('</table>');
$oP->add('</div>');
}
else
{
$oP->add("<p>No Result</p>\n");
}
}
catch (Exception $e)
{
$oP->p('<div class="header_message message_error">'.$e->getMessage().'</div>');
$bError = true;
}
$oP->add("<div class=\"header_message message_info\">$sSQL</div>\n");
$oP->output();
return;
/*
echo "<pre>";
$aClassSelection = MetaModel::GetClasses();
foreach($aClassSelection as $sClass)
{
if (!MetaModel::HasTable($sClass))
{
continue;
}
foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
{
// Skip this attribute if not defined in this table
if (!MetaModel::IsAttributeOrigin($sClass, $sAttCode))
{
continue;
}
switch (get_class($oAttDef))
{
case 'Integer':
case 'AttributeDecimal':
case 'AttributeDuration':
case 'AttributeSubItem':
case 'AttributePercentage':
echo "$sClass:$sAttCode = ".get_class($oAttDef)."\n";
break;
}
}
}
*/

483
test/core/DBSearchTest.php Normal file
View File

@@ -0,0 +1,483 @@
<?php
// Copyright (c) 2010-2018 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
//
/**
* Created by PhpStorm.
* User: Eric
* Date: 06/02/2018
* Time: 09:58
*/
namespace Combodo\iTop\Test\UnitTest\Core;
use CMDBSource;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use DBSearch;
use Exception;
use Expression;
use FunctionExpression;
use PHPUnit\Framework\TestCase;
/**
* Tests of the DBSearch class.
* <ul>
* <li>MakeGroupByQuery</li>
* </ul>
*
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
* @backupGlobals disabled
*/
class DBSearchTest extends ItopDataTestCase
{
/**
* @throws \Exception
*/
protected function setUp()
{
parent::setUp();
}
/**
* @dataProvider UReqProvider
* @param $iOrgNb
* @param $iPersonNb
* @param $aReq
* @param $iLimit
* @param $aCountRes
* @throws Exception
*/
public function testMakeGroupByQuery($iOrgNb, $iPersonNb, $aReq, $iLimit, $aCountRes)
{
$sOrgs = $this->init_db($iOrgNb, $iPersonNb, $aReq);
$oSearch = DBSearch::FromOQL("SELECT UserRequest WHERE org_id IN ($sOrgs)");
$this->assertNotNull($oSearch);
$oExpr1 = Expression::FromOQL('UserRequest.org_id');
// Alias => Expression
$aGroupBy = array('org_id' => $oExpr1);
$oTimeExpr = Expression::FromOQL('UserRequest.time_spent');
$oSumExpr = new FunctionExpression('SUM', array($oTimeExpr));
$oAvgExpr = new FunctionExpression('AVG', array($oTimeExpr));
$oMinExpr = new FunctionExpression('MIN', array($oTimeExpr));
$oMaxExpr = new FunctionExpression('MAX', array($oTimeExpr));
// Alias => Expression
$aFunctions = array(
'_itop_sum_' => $oSumExpr,
'_itop_avg_' => $oAvgExpr,
'_itop_min_' => $oMinExpr,
'_itop_max_' => $oMaxExpr,
);
// Alias => Order
$aOrderBy = array('_itop_sum_' => true, '_itop_count_' => true);
$aArgs = array();
$sSQL = $oSearch->MakeGroupByQuery($aArgs, $aGroupBy, false, $aFunctions, $aOrderBy, $iLimit);
$this->debug($sSQL);
$aRes = CMDBSource::QueryToArray($sSQL);
$this->debug($aRes);
$this->assertEquals(count($aCountRes), count($aRes));
for ($i = 0; $i < count($aCountRes); $i++)
{
$this->assertEquals($aCountRes[$i], $aRes[$i]['_itop_count_']);
}
}
public function UReqProvider()
{
return array(
"1 line" => array(1, 1, array(array(1, 0, 0)), 0, array('1')),
"2 same lines" => array(1, 1, array(array(1, 0, 0), array(1, 0, 0)), 0, array('2')),
"2 diff lines" => array(2, 2, array(array(1, 0, 0), array(1, 1, 1)), 0, array('1', '1')),
"4 lines" => array(2, 2, array(array(1, 0, 0), array(1, 1, 1), array(1, 0, 0), array(1, 1, 1)), 0, array('2', '2')),
"5 lines" => array(2, 2, array(array(1, 0, 0), array(1, 0, 0), array(1, 1, 1), array(1, 0, 0), array(1, 1, 1)), 0, array('2', '3')),
"6 lines" => array(2, 4, array(array(1, 0, 0), array(1, 1, 3), array(1, 1, 1), array(1, 1, 3), array(1, 0, 2), array(1, 1, 1)), 0, array('2', '4')),
"6 lines limit" => array(2, 4, array(array(1, 0, 0), array(1, 1, 3), array(1, 1, 1), array(1, 1, 1), array(1, 0, 0), array(1, 1, 1)), 1, array('2')),
);
}
/**
* @param int $iOrgNb Number of Organization to create
* @param int $iPersonNb Number of Person to create
* @param array $aReq UserRequests to create: array(array([time_spent value], [org index], [person index]))
* @return string organization list for select
* @throws Exception
*/
private function init_db($iOrgNb, $iPersonNb, $aReq)
{
$aOrgIds = array();
$sOrgs = '';
for($i = 0; $i < $iOrgNb; $i++)
{
$oObj = $this->CreateOrganization('UnitTest_Org'.$i);
$sKey = $oObj->GetKey();
$aOrgIds[] = $sKey;
if ($i > 0)
{
$sOrgs .= ",";
}
$sOrgs .= $sKey;
}
$this->assertEquals($iOrgNb, count($aOrgIds));
$aPersonIds = array();
for($i = 0; $i < $iPersonNb; $i++)
{
$oObj = $this->CreatePerson($i, $aOrgIds[$i % $iOrgNb]);
$aPersonIds[] = $oObj->GetKey();
}
$this->assertEquals($iPersonNb, count($aPersonIds));
$i = 0;
foreach($aReq as $aParams)
{
$oObj = $this->CreateUserRequest($i, $aParams[0], $aOrgIds[$aParams[1]], $aPersonIds[$aParams[2]]);
$this->assertNotNull($oObj);
$i++;
}
return $sOrgs;
}
/**
* @throws Exception
*/
public function testGroupByUnion()
{
$oServer = $this->CreateServer(1);
$this->CreatePhysicalInterface(1, 1000, $oServer->GetKey());
$this->CreateFiberChannelInterface(1, 1000, $oServer->GetKey());
$oSearch = DBSearch::FromOQL("SELECT FiberChannelInterface AS FCI WHERE FCI.name = '1' UNION SELECT PhysicalInterface AS PHI WHERE PHI.name = '1'");
$this->assertNotNull($oSearch);
$oExpr1 = Expression::FromOQL('FCI.name');
// Alias => Expression (first select reference)
$aGroupBy = array('group1' => $oExpr1);
$oTimeExpr = Expression::FromOQL('FCI.speed');
$oSumExpr = new FunctionExpression('SUM', array($oTimeExpr));
$oAvgExpr = new FunctionExpression('AVG', array($oTimeExpr));
$oMinExpr = new FunctionExpression('MIN', array($oTimeExpr));
$oMaxExpr = new FunctionExpression('MAX', array($oTimeExpr));
// Alias => Expression
$aFunctions = array(
'_itop_sum_' => $oSumExpr,
'_itop_avg_' => $oAvgExpr,
'_itop_min_' => $oMinExpr,
'_itop_max_' => $oMaxExpr,
);
// Alias => Order
$aOrderBy = array('group1' => true, '_itop_count_' => true);
$aArgs = array();
$sSQL = $oSearch->MakeGroupByQuery($aArgs, $aGroupBy, false, $aFunctions, $aOrderBy);
$this->debug($sSQL);
$aRes = CMDBSource::QueryToArray($sSQL);
$this->debug($aRes);
}
/**
* @throws Exception
*/
public function testOrderBy_1()
{
$oSearch = DBSearch::FromOQL("SELECT FiberChannelInterface");
$this->assertNotNull($oSearch);
// Alias => Expression (first select reference)
$oExpr1 = Expression::FromOQL('FiberChannelInterface.name');
$aGroupBy = array('group1' => $oExpr1);
$oTimeExpr = Expression::FromOQL('FiberChannelInterface.speed');
$oSumExpr = new FunctionExpression('SUM', array($oTimeExpr));
$oAvgExpr = new FunctionExpression('AVG', array($oTimeExpr));
$oMinExpr = new FunctionExpression('MIN', array($oTimeExpr));
$oMaxExpr = new FunctionExpression('MAX', array($oTimeExpr));
// Alias => Expression
$aFunctions = array(
'_itop_sum_' => $oSumExpr,
'_itop_avg_' => $oAvgExpr,
'_itop_min_' => $oMinExpr,
'_itop_max_' => $oMaxExpr,
);
$aArgs = array();
// Alias => Order
$aOrderBy = array(
'group1' => true,
'_itop_sum_' => true,
'_itop_avg_' => true,
'_itop_min_' => true,
'_itop_max_' => true);
$sSQL = $oSearch->MakeGroupByQuery($aArgs, $aGroupBy, false, $aFunctions, $aOrderBy);
$this->assertNotEmpty($sSQL);
// Alias => Order
$aOrderBy = array('nothing_good' => true);
$this->expectException("CoreException");
$oSearch->MakeGroupByQuery($aArgs, $aGroupBy, false, $aFunctions, $aOrderBy);
$this->assertTrue(false);
}
/**
* @throws Exception
*/
public function testSanity_1()
{
$oSearch = DBSearch::FromOQL("SELECT FiberChannelInterface AS FCI WHERE FCI.name = '1' UNION SELECT PhysicalInterface AS PHI WHERE PHI.name = '1'");
$this->assertNotNull($oSearch);
$oExpr1 = Expression::FromOQL('FCI.name');
// Alias => Expression (first select reference)
$aGroupBy = array('group1' => $oExpr1);
$oTimeExpr = Expression::FromOQL('FCI.speed');
$oSumExpr = new FunctionExpression('SUM', array($oTimeExpr));
$oAvgExpr = new FunctionExpression('AVG', array($oTimeExpr));
$oMinExpr = new FunctionExpression('MIN', array($oTimeExpr));
$oMaxExpr = new FunctionExpression('MAX', array($oTimeExpr));
// Alias => Expression
$aFunctions = array(
'_itop_sum_' => $oSumExpr,
'_itop_avg_' => $oAvgExpr,
'group1' => $oMinExpr,
'_itop_max_' => $oMaxExpr,
);
$aArgs = array();
// Alias => Order
$aOrderBy = array(
'group1' => true,
'_itop_sum_' => true,
'_itop_avg_' => true,
'_itop_min_' => true,
'_itop_max_' => true);
$this->expectException("CoreException");
$oSearch->MakeGroupByQuery($aArgs, $aGroupBy, false, $aFunctions, $aOrderBy);
$this->assertTrue(false);
}
/**
* @throws Exception
*/
public function testSanity_2()
{
$oSearch = DBSearch::FromOQL("SELECT FiberChannelInterface AS FCI WHERE FCI.name = '1' UNION SELECT PhysicalInterface AS PHI WHERE PHI.name = '1'");
$this->assertNotNull($oSearch);
// Alias => Expression (first select reference)
$oExpr1 = Expression::FromOQL('FCI.name');
$aGroupBy = array('group1' => $oExpr1);
$oTimeExpr = Expression::FromOQL('FCI.speed');
$oSumExpr = new FunctionExpression('SUM', array($oTimeExpr));
$oAvgExpr = new FunctionExpression('AVG', array($oTimeExpr));
$oMinExpr = new FunctionExpression('MIN', array($oTimeExpr));
$oMaxExpr = new FunctionExpression('MAX', array($oTimeExpr));
// Alias => Expression
$aFunctions = array(
'_itop_sum_' => $oSumExpr,
'_itop_avg_' => $oAvgExpr,
'_itop_min_' => $oMinExpr,
'_itop_max_' => $oMaxExpr,
);
$aArgs = array();
// Alias => Order
$aOrderBy = array(
'group1' => true,
'_itop_sum_' => true,
'_itop_avg_' => true,
'_itop_min_' => true,
'_itop_max_' => true);
$sSQL = $oSearch->MakeGroupByQuery($aArgs, $aGroupBy, false, $aFunctions, $aOrderBy);
$this->assertNotEmpty($sSQL);
$aGroupBy = array('group1' => 'FCI.name');
$this->expectException("CoreException");
$oSearch->MakeGroupByQuery($aArgs, $aGroupBy, false, $aFunctions, $aOrderBy);
$this->assertTrue(false);
}
/**
* @throws Exception
*/
public function testSanity_3()
{
$oSearch = DBSearch::FromOQL("SELECT FiberChannelInterface AS FCI WHERE FCI.name = '1' UNION SELECT PhysicalInterface AS PHI WHERE PHI.name = '1'");
$this->assertNotNull($oSearch);
// Alias => Expression (first select reference)
$oExpr1 = Expression::FromOQL('FCI.name');
$aGroupBy = array('group1' => $oExpr1);
$oTimeExpr = Expression::FromOQL('FCI.speed');
$oSumExpr = new FunctionExpression('SUM', array($oTimeExpr));
$oAvgExpr = new FunctionExpression('AVG', array($oTimeExpr));
$oMinExpr = new FunctionExpression('MIN', array($oTimeExpr));
$oMaxExpr = new FunctionExpression('MAX', array($oTimeExpr));
// Alias => Expression
$aFunctions = array(
'_itop_sum_' => $oSumExpr,
'_itop_avg_' => $oAvgExpr,
'_itop_min_' => $oMinExpr,
'_itop_max_' => $oMaxExpr,
);
$aArgs = array();
// Alias => Order
$aOrderBy = array(
'group1' => true,
'_itop_sum_' => true,
'_itop_avg_' => true,
'_itop_min_' => true,
'_itop_max_' => true);
$sSQL = $oSearch->MakeGroupByQuery($aArgs, $aGroupBy, false, $aFunctions, $aOrderBy);
$this->assertNotEmpty($sSQL);
$aFunctions = array(
'_itop_sum_' => 'SumExpr',
'_itop_avg_' => $oAvgExpr,
'_itop_min_' => $oMinExpr,
'_itop_max_' => $oMaxExpr,
);
$this->expectException("CoreException");
$oSearch->MakeGroupByQuery($aArgs, $aGroupBy, false, $aFunctions, $aOrderBy);
$this->assertTrue(false);
}
/**
* @throws Exception
*/
public function testSanity_4()
{
$oSearch = DBSearch::FromOQL("SELECT FiberChannelInterface AS FCI WHERE FCI.name = '1' UNION SELECT PhysicalInterface AS PHI WHERE PHI.name = '1'");
$this->assertNotNull($oSearch);
// Alias => Expression (first select reference)
$oExpr1 = Expression::FromOQL('FCI.name');
$aGroupBy = array('group1' => $oExpr1);
$oTimeExpr = Expression::FromOQL('FCI.speed');
$oSumExpr = new FunctionExpression('SUM', array($oTimeExpr));
$oAvgExpr = new FunctionExpression('AVG', array($oTimeExpr));
$oMinExpr = new FunctionExpression('MIN', array($oTimeExpr));
$oMaxExpr = new FunctionExpression('MAX', array($oTimeExpr));
// Alias => Expression
$aFunctions = array(
'_itop_sum_' => $oSumExpr,
'_itop_avg_' => $oAvgExpr,
'_itop_min_' => $oMinExpr,
'_itop_max_' => $oMaxExpr,
);
$aArgs = array();
// Alias => Order
$aOrderBy = array(
'group1' => true,
'_itop_sum_' => true,
'_itop_avg_' => true,
'_itop_min_' => true,
'_itop_max_' => true);
$sSQL = $oSearch->MakeGroupByQuery($aArgs, $aGroupBy, false, $aFunctions, $aOrderBy);
$this->assertNotEmpty($sSQL);
$aOrderBy = array(
'group1' => true,
'_itop_sum_' => true,
'_itop_avg_' => 'ASC',
'_itop_min_' => true,
'_itop_max_' => true);
$this->expectException("CoreException");
$oSearch->MakeGroupByQuery($aArgs, $aGroupBy, false, $aFunctions, $aOrderBy);
$this->assertTrue(false);
}
/**
* @throws Exception
*/
public function testSanity_5()
{
$oSearch = DBSearch::FromOQL("SELECT FiberChannelInterface AS FCI WHERE FCI.name = '1' UNION SELECT PhysicalInterface AS PHI WHERE PHI.name = '1'");
$this->assertNotNull($oSearch);
// Alias => Expression (first select reference)
$oExpr1 = Expression::FromOQL('FCI.name');
$aGroupBy = array('group1' => $oExpr1);
$oTimeExpr = Expression::FromOQL('FCI.speed');
$oSumExpr = new FunctionExpression('SUM', array($oTimeExpr));
$oAvgExpr = new FunctionExpression('AVG', array($oTimeExpr));
$oMinExpr = new FunctionExpression('MIN', array($oTimeExpr));
$oMaxExpr = new FunctionExpression('MAX', array($oTimeExpr));
// Alias => Expression
$aFunctions = array(
'_itop_sum_' => $oSumExpr,
'_itop_avg_' => $oAvgExpr,
'_itop_min_' => $oMinExpr,
'_itop_max_' => $oMaxExpr,
);
$aArgs = array();
// Alias => Order
$aOrderBy = array(
'group1' => true,
'_itop_sum_' => true,
'_itop_avg_' => true,
'_itop_min_' => true,
'_itop_max_' => true);
$sSQL = $oSearch->MakeGroupByQuery($aArgs, $aGroupBy, false, $aFunctions, $aOrderBy);
$this->assertNotEmpty($sSQL);
// Alias => Order
$aOrderBy = array('nothing_good' => true);
$this->expectException("CoreException");
$oSearch->MakeGroupByQuery($aArgs, $aGroupBy, false, $aFunctions, $aOrderBy);
$this->assertTrue(false);
}
}