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;
}