Dashboard: optimized group by (done by MySQL) + proto of a group by on 2 dimensions

SVN:trunk[2041]
This commit is contained in:
Romain Quetiez
2012-05-25 16:04:18 +00:00
parent 2c00c115d6
commit e4c113e412
6 changed files with 452 additions and 138 deletions

View File

@@ -373,20 +373,63 @@ abstract class DashletGroupBy extends Dashlet
}
else
{
$oFilter = DBObjectSearch::FromOQL($sQuery);
$sClassAlias = $oFilter->GetClassAlias();
if (preg_match('/^(.*):(.*)$/', $sGroupBy, $aMatches))
{
$sAttCode = $aMatches[1];
$sFunction = $aMatches[2];
switch($sFunction)
{
case 'hour':
$sGroupByLabel = 'Hour of '.$sAttCode. ' (0-23)';
$sGroupByExpr = "DATE_FORMAT($sClassAlias.$sAttCode, '%H')"; // 0 -> 31
break;
case 'month':
$sGroupByLabel = 'Month of '.$sAttCode. ' (1 - 12)';
$sGroupByExpr = "DATE_FORMAT($sClassAlias.$sAttCode, '%m')"; // 0 -> 31
break;
case 'day_of_week':
$sGroupByLabel = 'Day of week for '.$sAttCode. ' (sunday to saturday)';
$sGroupByExpr = "DATE_FORMAT($sClassAlias.$sAttCode, '%w')";
break;
case 'day_of_month':
$sGroupByLabel = 'Day of month for'.$sAttCode;
$sGroupByExpr = "DATE_FORMAT($sClassAlias.$sAttCode, '%e')"; // 0 -> 31
break;
default:
$sGroupByLabel = 'Unknown group by function '.$sFunction;
$sGroupByExpr = $sClassAlias.'.'.$sAttCode;
}
}
else
{
$sAttCode = $sGroupBy;
$sGroupByExpr = $sClassAlias.'.'.$sAttCode;
$sGroupByLabel = MetaModel::GetLabel($oFilter->GetClass(), $sAttCode);
}
switch($sStyle)
{
case 'bars':
$sXML = '<itopblock BlockClass="DisplayBlock" type="open_flash_chart" parameters="chart_type:bars;chart_title:'.$sTitle.';group_by:'.$sGroupBy.'" asynchronous="false" encoding="text/oql">'.$sQuery.'</itopblock>';
$sXML = '<itopblock BlockClass="DisplayBlock" type="open_flash_chart" parameters="chart_type:bars;chart_title:'.$sGroupByLabel.';group_by:'.$sGroupByExpr.';group_by_label:'.$sGroupByLabel.'" asynchronous="false" encoding="text/oql">'.$sQuery.'</itopblock>';
$sHtmlTitle = ''; // done in the itop block
break;
case 'pie':
$sXML = '<itopblock BlockClass="DisplayBlock" type="open_flash_chart" parameters="chart_type:pie;chart_title:'.$sTitle.';group_by:'.$sGroupBy.'" asynchronous="false" encoding="text/oql">'.$sQuery.'</itopblock>';
$sXML = '<itopblock BlockClass="DisplayBlock" type="open_flash_chart" parameters="chart_type:pie;chart_title:'.$sGroupByLabel.';group_by:'.$sGroupByExpr.';group_by_label:'.$sGroupByLabel.'" asynchronous="false" encoding="text/oql">'.$sQuery.'</itopblock>';
$sHtmlTitle = ''; // done in the itop block
break;
case 'table':
default:
$sHtmlTitle = htmlentities(Dict::S($sTitle), ENT_QUOTES, 'UTF-8'); // done in the itop block
$sXML = '<itopblock BlockClass="DisplayBlock" type="count" parameters="group_by:'.$sGroupBy.'" asynchronous="false" encoding="text/oql">'.$sQuery.'</itopblock>';
$sXML = '<itopblock BlockClass="DisplayBlock" type="count" parameters="group_by:'.$sGroupByExpr.';group_by_label:'.$sGroupByLabel.'" asynchronous="false" encoding="text/oql">'.$sQuery.'</itopblock>';
break;
}
@@ -429,10 +472,11 @@ abstract class DashletGroupBy extends Dashlet
if ($oAttDef instanceof AttributeDateTime)
{
//date_format(start_date, '%d')
$aGroupBy['date_of_'.$sAttCode] = 'Day of '.$oAttDef->GetLabel();
$aGroupBy[$sAttCode.':hour'] = $oAttDef->GetLabel().' (hour)';
$aGroupBy[$sAttCode.':month'] = $oAttDef->GetLabel().' (month)';
$aGroupBy[$sAttCode.':day_of_week'] = $oAttDef->GetLabel().' (day of week)';
$aGroupBy[$sAttCode.':day_of_month'] = $oAttDef->GetLabel().' (day of month)';
}
}
@@ -719,3 +763,92 @@ class DashletBadge extends Dashlet
}
}
class DashletProto extends Dashlet
{
public function __construct($sId)
{
parent::__construct($sId);
$this->aProperties['class'] = 'Foo';
}
public function Render($oPage, $bEditMode = false, $aExtraParams = array())
{
$sClass = $this->aProperties['class'];
$oFilter = DBObjectSearch::FromOQL('SELECT FunctionalCI AS fci');
$sGroupBy1 = 'status';
$sGroupBy2 = 'org_id_friendlyname';
$sHtmlTitle = "Hardcoded on $sGroupBy1 and $sGroupBy2...";
$sAlias = $oFilter->GetClassAlias();
$oGroupByExp1 = new FieldExpression($sGroupBy1, $sAlias);
$sGroupByLabel1 = MetaModel::GetLabel($oFilter->GetClass(), $sGroupBy1);
$oGroupByExp2 = new FieldExpression($sGroupBy2, $sAlias);
$sGroupByLabel2 = MetaModel::GetLabel($oFilter->GetClass(), $sGroupBy2);
$aGroupBy = array();
$aGroupBy['grouped_by_1'] = $oGroupByExp1;
$aGroupBy['grouped_by_2'] = $oGroupByExp2;
$sSql = MetaModel::MakeGroupByQuery($oFilter, array(), $aGroupBy);
$aRes = CMDBSource::QueryToArray($sSql);
$iTotalCount = 0;
$aData = array();
$oAppContext = new ApplicationContext();
$sParams = $oAppContext->GetForLink();
foreach ($aRes as $aRow)
{
$iCount = $aRow['_itop_count_'];
$iTotalCount += $iCount;
$sValue1 = $aRow['grouped_by_1'];
$sValue2 = $aRow['grouped_by_2'];
// Build the search for this subset
$oSubsetSearch = clone $oFilter;
$oCondition = new BinaryExpression($oGroupByExp1, '=', new ScalarExpression($sValue1));
$oSubsetSearch->AddConditionExpression($oCondition);
$oCondition = new BinaryExpression($oGroupByExp2, '=', new ScalarExpression($sValue2));
$oSubsetSearch->AddConditionExpression($oCondition);
$sFilter = urlencode($oSubsetSearch->serialize());
$aData[] = array (
'group1' => htmlentities($sValue1, ENT_QUOTES, 'UTF-8'),
'group2' => htmlentities($sValue2, ENT_QUOTES, 'UTF-8'),
'value' => "<a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=search&dosearch=1&$sParams&filter=$sFilter\">$iCount</a>"
); // TO DO: add the context information
}
$aAttribs =array(
'group1' => array('label' => $sGroupByLabel1, 'description' => ''),
'group2' => array('label' => $sGroupByLabel2, 'description' => ''),
'value' => array('label'=> Dict::S('UI:GroupBy:Count'), 'description' => Dict::S('UI:GroupBy:Count+'))
);
$oPage->add('<div style="text-align:center" class="dashlet-content">');
$oPage->add('<h1>'.$sHtmlTitle.'</h1>');
$oPage->p(Dict::Format('UI:Pagination:HeaderNoSelection', $iTotalCount));
$oPage->table($aAttribs, $aData);
$oPage->add('</div>');
}
public function GetPropertiesFields(DesignerForm $oForm)
{
$oField = new DesignerTextField('class', 'Class', $this->aProperties['class']);
$oForm->AddField($oField);
}
static public function GetInfo()
{
return array(
'label' => 'Test3D',
'icon' => 'images/xxxxxx.png',
'description' => 'Group by on two dimensions',
);
}
}

View File

@@ -347,34 +347,51 @@ class DisplayBlock
case 'count':
if (isset($aExtraParams['group_by']))
{
$sGroupByField = $aExtraParams['group_by'];
if (isset($aExtraParams['group_by_label']))
{
$oGroupByExp = Expression::FromOQL($aExtraParams['group_by']);
$sGroupByLabel = $aExtraParams['group_by_label'];
}
else
{
// Backward compatibility: group_by is simply a field id
$sAlias = $this->m_oFilter->GetClassAlias();
$oGroupByExp = new FieldExpression($aExtraParams['group_by'], $sAlias);
$sGroupByLabel = MetaModel::GetLabel($this->m_oFilter->GetClass(), $aExtraParams['group_by']);
}
$aGroupBy = array();
$aGroupBy['grouped_by_1'] = $oGroupByExp;
$sSql = MetaModel::MakeGroupByQuery($this->m_oFilter, $aQueryParams, $aGroupBy);
$aRes = CMDBSource::QueryToArray($sSql);
$aGroupBy = array();
$sLabels = array();
$iTotalCount = $this->m_oSet->Count();
while($oObj = $this->m_oSet->Fetch())
$iTotalCount = 0;
foreach ($aRes as $aRow)
{
if (isset($aExtraParams['group_by_expr']))
{
eval("\$sValue = ".sprintf($aExtraParams['group_by_expr'], $oObj->Get($sGroupByField)).';');
}
else
{
$sValue = $oObj->Get($sGroupByField);
}
$aGroupBy[$sValue] = isset($aGroupBy[$sValue]) ? $aGroupBy[$sValue]+1 : 1;
$sLabels[$sValue] = $oObj->GetAsHtml($sGroupByField);
$sValue = $aRow['grouped_by_1'];
$sLabels[$sValue] = htmlentities($sValue, ENT_QUOTES, 'UTF-8');
$aGroupBy[$sValue] = (int) $aRow['_itop_count_'];
$iTotalCount += $aRow['_itop_count_'];
}
$sFilter = urlencode($this->m_oFilter->serialize());
$aData = array();
$oAppContext = new ApplicationContext();
$sParams = $oAppContext->GetForLink();
foreach($aGroupBy as $sValue => $iCount)
{
// Build the search for this subset
$oSubsetSearch = clone $this->m_oFilter;
$oCondition = new BinaryExpression($oGroupByExp, '=', new ScalarExpression($sValue));
$oSubsetSearch->AddConditionExpression($oCondition);
$sFilter = urlencode($oSubsetSearch->serialize());
$aData[] = array ( 'group' => $sLabels[$sValue],
'value' => "<a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=search&dosearch=1&$sParams&filter=$sFilter&$sGroupByField=".urlencode($sValue)."\">$iCount</a>"); // TO DO: add the context information
'value' => "<a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=search&dosearch=1&$sParams&filter=$sFilter\">$iCount</a>"); // TO DO: add the context information
}
$aAttribs =array(
'group' => array('label' => MetaModel::GetLabel($this->m_oFilter->GetClass(), $sGroupByField), 'description' => ''),
'group' => array('label' => $sGroupByLabel, 'description' => ''),
'value' => array('label'=> Dict::S('UI:GroupBy:Count'), 'description' => Dict::S('UI:GroupBy:Count+'))
);
$sFormat = isset($aExtraParams['format']) ? $aExtraParams['format'] : 'UI:Pagination:HeaderNoSelection';
@@ -748,35 +765,60 @@ EOF
$sFilter = $this->m_oFilter->serialize();
$sHtml .= "<div id=\"my_chart_$sId{$iChartCounter}\">If the chart does not display, <a href=\"http://get.adobe.com/flash/\" target=\"_blank\">install Flash</a></div>\n";
$oPage->add_script("function ofc_resize(left, width, top, height) { /* do nothing special */ }");
if (isset($aExtraParams['group_by_label']))
{
$sUrl = urlencode(utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=open_flash_chart&params[group_by]=$sGroupBy{$sGroupByExpr}&params[group_by_label]={$aExtraParams['group_by_label']}&params[chart_type]=$sChartType&params[chart_title]=$sTitle&params[currentId]=$sId&id=$sId&filter=".$sFilter);
}
else
{
$sUrl = urlencode(utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=open_flash_chart&params[group_by]=$sGroupBy{$sGroupByExpr}&params[chart_type]=$sChartType&params[chart_title]=$sTitle&params[currentId]=$sId&id=$sId&filter=".$sFilter);
}
$oPage->add_ready_script("swfobject.embedSWF(\"../images/open-flash-chart.swf\", \"my_chart_$sId{$iChartCounter}\", \"100%\", \"300\",\"9.0.0\", \"expressInstall.swf\",
{\"data-file\":\"".urlencode(utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=open_flash_chart&params[group_by]=$sGroupBy{$sGroupByExpr}&params[chart_type]=$sChartType&params[chart_title]=$sTitle&params[currentId]=$sId&id=$sId&filter=".$sFilter)."\"}, {wmode: 'transparent'} );\n");
{\"data-file\":\"".$sUrl."\"}, {wmode: 'transparent'} );\n");
$iChartCounter++;
if (isset($aExtraParams['group_by']))
{
$sGroupByField = $aExtraParams['group_by'];
$aGroupBy = array();
while($oObj = $this->m_oSet->Fetch())
if (isset($aExtraParams['group_by_label']))
{
if (isset($aExtraParams['group_by_expr']))
{
eval("\$sValue = ".sprintf($aExtraParams['group_by_expr'], $oObj->Get($sGroupByField)).';');
}
else
{
$sValue = $oObj->Get($sGroupByField);
}
$aGroupBy[$sValue] = isset($aGroupBy[$sValue]) ? $aGroupBy[$sValue]+1 : 1;
$oGroupByExp = Expression::FromOQL($aExtraParams['group_by']);
$sGroupByLabel = $aExtraParams['group_by_label'];
}
$sFilter = urlencode($this->m_oFilter->serialize());
else
{
// Backward compatibility: group_by is simply a field id
$sAlias = $this->m_oFilter->GetClassAlias();
$oGroupByExp = new FieldExpression($aExtraParams['group_by'], $sAlias);
$sGroupByLabel = MetaModel::GetLabel($this->m_oFilter->GetClass(), $aExtraParams['group_by']);
}
$aGroupBy = array();
$aGroupBy['grouped_by_1'] = $oGroupByExp;
$sSql = MetaModel::MakeGroupByQuery($this->m_oFilter, $aQueryParams, $aGroupBy);
$aRes = CMDBSource::QueryToArray($sSql);
$aGroupBy = array();
$sLabels = array();
$iTotalCount = 0;
foreach ($aRes as $aRow)
{
$sValue = $aRow['grouped_by_1'];
$sLabels[$sValue] = htmlentities($sValue, ENT_QUOTES, 'UTF-8');
$aGroupBy[$sValue] = (int) $aRow['_itop_count_'];
$iTotalCount += $aRow['_itop_count_'];
}
$aData = array();
$aLabels = array();
$idx = 0;
$aURLs = array();
foreach($aGroupBy as $sValue => $iValue)
{
$oDrillDownFilter = clone $this->m_oFilter;
$oDrillDownFilter->AddCondition($sGroupByField, $sValue, '=');
$aURLs[$idx] = $oDrillDownFilter->serialize();
// Build the search for this subset
$oSubsetSearch = clone $this->m_oFilter;
$oCondition = new BinaryExpression($oGroupByExp, '=', new ScalarExpression($sValue));
$oSubsetSearch->AddConditionExpression($oCondition);
$aURLs[$idx] = $oSubsetSearch->serialize();
$idx++;
}
$sURLList = '';
@@ -810,21 +852,35 @@ EOF
if (isset($aExtraParams['group_by']))
{
$sGroupByField = $aExtraParams['group_by'];
$aGroupBy = array();
while($oObj = $this->m_oSet->Fetch())
if (isset($aExtraParams['group_by_label']))
{
if (isset($aExtraParams['group_by_expr']))
{
eval("\$sValue = ".sprintf($aExtraParams['group_by_expr'], $oObj->Get($sGroupByField)).';');
}
else
{
$sValue = $oObj->Get($sGroupByField);
}
$aGroupBy[$sValue] = isset($aGroupBy[$sValue]) ? $aGroupBy[$sValue]+1 : 1;
$oGroupByExp = Expression::FromOQL($aExtraParams['group_by']);
$sGroupByLabel = $aExtraParams['group_by_label'];
}
$sFilter = urlencode($this->m_oFilter->serialize());
else
{
// Backward compatibility: group_by is simply a field id
$sAlias = $this->m_oFilter->GetClassAlias();
$oGroupByExp = new FieldExpression($aExtraParams['group_by'], $sAlias);
$sGroupByLabel = MetaModel::GetLabel($this->m_oFilter->GetClass(), $aExtraParams['group_by']);
}
$aGroupBy = array();
$aGroupBy['grouped_by_1'] = $oGroupByExp;
$sSql = MetaModel::MakeGroupByQuery($this->m_oFilter, $aQueryParams, $aGroupBy);
$aRes = CMDBSource::QueryToArray($sSql);
$aGroupBy = array();
$sLabels = array();
$iTotalCount = 0;
foreach ($aRes as $aRow)
{
$sValue = $aRow['grouped_by_1'];
$sLabels[$sValue] = htmlentities($sValue, ENT_QUOTES, 'UTF-8');
$aGroupBy[$sValue] = (int) $aRow['_itop_count_'];
$iTotalCount += $aRow['_itop_count_'];
}
$aData = array();
$aLabels = array();
$maxValue = 0;
@@ -876,21 +932,35 @@ EOF
$oChartElement->set_colours( array('#FF8A00', '#909980', '#2C2B33', '#CCC08D', '#596664') );
if (isset($aExtraParams['group_by']))
{
$sGroupByField = $aExtraParams['group_by'];
$aGroupBy = array();
while($oObj = $this->m_oSet->Fetch())
if (isset($aExtraParams['group_by_label']))
{
if (isset($aExtraParams['group_by_expr']))
{
eval("\$sValue = ".sprintf($aExtraParams['group_by_expr'], $oObj->Get($sGroupByField)).';');
}
else
{
$sValue = $oObj->Get($sGroupByField);
}
$aGroupBy[$sValue] = isset($aGroupBy[$sValue]) ? $aGroupBy[$sValue]+1 : 1;
$oGroupByExp = Expression::FromOQL($aExtraParams['group_by']);
$sGroupByLabel = $aExtraParams['group_by_label'];
}
$sFilter = urlencode($this->m_oFilter->serialize());
else
{
// Backward compatibility: group_by is simply a field id
$sAlias = $this->m_oFilter->GetClassAlias();
$oGroupByExp = new FieldExpression($aExtraParams['group_by'], $sAlias);
$sGroupByLabel = MetaModel::GetLabel($this->m_oFilter->GetClass(), $aExtraParams['group_by']);
}
$aGroupBy = array();
$aGroupBy['grouped_by_1'] = $oGroupByExp;
$sSql = MetaModel::MakeGroupByQuery($this->m_oFilter, $aQueryParams, $aGroupBy);
$aRes = CMDBSource::QueryToArray($sSql);
$aGroupBy = array();
$sLabels = array();
$iTotalCount = 0;
foreach ($aRes as $aRow)
{
$sValue = $aRow['grouped_by_1'];
$sLabels[$sValue] = htmlentities($sValue, ENT_QUOTES, 'UTF-8');
$aGroupBy[$sValue] = (int) $aRow['_itop_count_'];
$iTotalCount += $aRow['_itop_count_'];
}
$aData = array();
foreach($aGroupBy as $sValue => $iValue)
{

View File

@@ -951,12 +951,14 @@ class QueryBuilderExpressions
{
protected $m_oConditionExpr;
protected $m_aSelectExpr;
protected $m_aGroupByExpr;
protected $m_aJoinFields;
public function __construct($oCondition)
public function __construct($oCondition, $aGroupByExpr = null)
{
$this->m_oConditionExpr = $oCondition;
$this->m_aSelectExpr = array();
$this->m_aGroupByExpr = $aGroupByExpr;
$this->m_aJoinFields = array();
}
@@ -965,6 +967,11 @@ class QueryBuilderExpressions
return $this->m_aSelectExpr;
}
public function GetGroupBy()
{
return $this->m_aGroupByExpr;
}
public function GetCondition()
{
return $this->m_oConditionExpr;
@@ -998,6 +1005,13 @@ class QueryBuilderExpressions
{
$oExpr->GetUnresolvedFields($sAlias, $aUnresolved);
}
if ($this->m_aGroupByExpr)
{
foreach($this->m_aGroupByExpr as $sColAlias => $oExpr)
{
$oExpr->GetUnresolvedFields($sAlias, $aUnresolved);
}
}
foreach($this->m_aJoinFields as $oExpression)
{
$oExpression->GetUnresolvedFields($sAlias, $aUnresolved);
@@ -1011,6 +1025,13 @@ class QueryBuilderExpressions
{
$this->m_aSelectExpr[$sColAlias] = $oExpr->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);
}
if ($this->m_aGroupByExpr)
{
foreach($this->m_aGroupByExpr as $sColAlias => $oExpr)
{
$this->m_aGroupByExpr[$sColAlias] = $oExpr->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);
}
}
foreach($this->m_aJoinFields as $index => $oExpression)
{
$this->m_aJoinFields[$index] = $oExpression->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);
@@ -1024,6 +1045,13 @@ class QueryBuilderExpressions
{
$this->m_aSelectExpr[$sColAlias] = $oExpr->RenameParam($sOldName, $sNewName);
}
if ($this->m_aGroupByExpr)
{
foreach($this->m_aGroupByExpr as $sColAlias => $oExpr)
{
$this->m_aGroupByExpr[$sColAlias] = $oExpr->RenameParam($sOldName, $sNewName);
}
}
foreach($this->m_aJoinFields as $index => $oExpression)
{
$this->m_aJoinFields[$index] = $oExpression->RenameParam($sOldName, $sNewName);

View File

@@ -1933,7 +1933,81 @@ abstract class MetaModel
return $aScalarArgs;
}
public static function MakeGroupByQuery(DBObjectSearch $oFilter, $aArgs, $aGroupByExpr)
{
$aAttToLoad = array();
$oSelect = self::MakeSelectStructure($oFilter, array(), $aArgs, $aAttToLoad, null, 0, 0, false, $aGroupByExpr);
$aScalarArgs = array_merge(self::PrepareQueryArguments($aArgs), $oFilter->GetInternalParams());
try
{
$sRes = $oSelect->RenderGroupBy($aScalarArgs);
}
catch (MissingQueryArgument $e)
{
// Add some information...
$e->addInfo('OQL', $sOqlQuery);
throw $e;
}
return $sRes;
}
public static function MakeSelectQuery(DBObjectSearch $oFilter, $aOrderBy = array(), $aArgs = array(), $aAttToLoad = null, $aExtendedDataSpec = null, $iLimitCount = 0, $iLimitStart = 0, $bGetCount = false)
{
// Check the order by specification, and prefix with the class alias
// and make sure that the ordering columns are going to be selected
//
$aOrderSpec = array();
foreach ($aOrderBy as $sFieldAlias => $bAscending)
{
if ($sFieldAlias != 'id')
{
MyHelpers::CheckValueInArray('field name in ORDER BY spec', $sFieldAlias, self::GetAttributesList($oFilter->GetFirstJoinedClass()));
}
if (!is_bool($bAscending))
{
throw new CoreException("Wrong direction in ORDER BY spec, found '$bAscending' and expecting a boolean value");
}
$sFirstClassAlias = $oFilter->GetFirstJoinedClassAlias();
if (self::IsValidAttCode($oFilter->GetClass(), $sFieldAlias))
{
$oAttDef = self::GetAttributeDef($oFilter->GetClass(), $sFieldAlias);
foreach($oAttDef->GetOrderBySQLExpressions($sFirstClassAlias) as $sSQLExpression)
{
$aOrderSpec[$sSQLExpression] = $bAscending;
}
}
else
{
$aOrderSpec['`'.$sFirstClassAlias.$sFieldAlias.'`'] = $bAscending;
}
// Make sure that the columns used for sorting are present in the loaded columns
if (!is_null($aAttToLoad) && !isset($aAttToLoad[$sFirstClassAlias][$sFieldAlias]))
{
$aAttToLoad[$sFirstClassAlias][$sFieldAlias] = MetaModel::GetAttributeDef($oFilter->GetFirstJoinedClass(), $sFieldAlias);
}
}
$oSelect = self::MakeSelectStructure($oFilter, $aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount);
$aScalarArgs = array_merge(self::PrepareQueryArguments($aArgs), $oFilter->GetInternalParams());
try
{
$sRes = $oSelect->RenderSelect($aOrderSpec, $aScalarArgs, $iLimitCount, $iLimitStart, $bGetCount);
}
catch (MissingQueryArgument $e)
{
// Add some information...
$e->addInfo('OQL', $sOqlQuery);
throw $e;
}
return $sRes;
}
protected static function MakeSelectStructure(DBObjectSearch $oFilter, $aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount, $aGroupByExpr = null)
{
// Hide objects that are not visible to the current user
//
@@ -1984,6 +2058,13 @@ abstract class MetaModel
$sRawId = $sOqlQuery.'|'.implode(',', array_keys($aAttributes));
}
}
if (!is_null($aGroupByExpr))
{
foreach($aGroupByExpr as $sAlias => $oExpr)
{
$sRawId = 'g:'.$sAlias.'!'.$oExpr->Render();
}
}
$sOqlId = md5($sRawId);
}
else
@@ -2029,48 +2110,25 @@ abstract class MetaModel
}
}
// Check the order by specification, and prefix with the class alias
// and make sure that the ordering columns are going to be selected
//
$aOrderSpec = array();
foreach ($aOrderBy as $sFieldAlias => $bAscending)
{
if ($sFieldAlias != 'id')
{
MyHelpers::CheckValueInArray('field name in ORDER BY spec', $sFieldAlias, self::GetAttributesList($oFilter->GetFirstJoinedClass()));
}
if (!is_bool($bAscending))
{
throw new CoreException("Wrong direction in ORDER BY spec, found '$bAscending' and expecting a boolean value");
}
$sFirstClassAlias = $oFilter->GetFirstJoinedClassAlias();
if (self::IsValidAttCode($oFilter->GetClass(), $sFieldAlias))
{
$oAttDef = self::GetAttributeDef($oFilter->GetClass(), $sFieldAlias);
foreach($oAttDef->GetOrderBySQLExpressions($sFirstClassAlias) as $sSQLExpression)
{
$aOrderSpec[$sSQLExpression] = $bAscending;
}
}
else
{
$aOrderSpec['`'.$sFirstClassAlias.$sFieldAlias.'`'] = $bAscending;
}
// Make sure that the columns used for sorting are present in the loaded columns
if (!is_null($aAttToLoad) && !isset($aAttToLoad[$sFirstClassAlias][$sFieldAlias]))
{
$aAttToLoad[$sFirstClassAlias][$sFieldAlias] = MetaModel::GetAttributeDef($oFilter->GetFirstJoinedClass(), $sFieldAlias);
}
}
if (!isset($oSelect))
{
$oBuild = new QueryBuilderContext($oFilter, $aModifierProperties);
$oBuild = new QueryBuilderContext($oFilter, $aModifierProperties, $aGroupByExpr);
$oKPI = new ExecutionKPI();
$oSelect = self::MakeQuery($oBuild, $oFilter, $aAttToLoad, array(), true /* main query */);
$oSelect->SetCondition($oBuild->m_oQBExpressions->GetCondition());
$oSelect->SetSourceOQL($sOqlQuery);
if ($aGroupByExpr)
{
$aCols = $oBuild->m_oQBExpressions->GetGroupBy();
$oSelect->SetGroupBy($aCols);
$oSelect->SetSelect($aCols);
}
else
{
$oSelect->SetSelect($oBuild->m_oQBExpressions->GetSelect());
}
$oKPI->ComputeStats('MakeQuery (select)', $sOqlQuery);
if (self::$m_bQueryCacheEnabled)
@@ -2101,22 +2159,6 @@ abstract class MetaModel
$oSelect->AddInnerJoin($oSelectExt, 'id', $aExtendedDataSpec['join_key'] /*, $sTableAlias*/);
}
// Go
//
$aScalarArgs = array_merge(self::PrepareQueryArguments($aArgs), $oFilter->GetInternalParams());
try
{
$sRes = $oSelect->RenderSelect($aOrderSpec, $aScalarArgs, $iLimitCount, $iLimitStart, $bGetCount);
//echo "<p>MakeQuery: $sRes</p>";
}
catch (MissingQueryArgument $e)
{
// Add some information...
$e->addInfo('OQL', $sOqlQuery);
throw $e;
}
if (self::$m_bTraceQueries)
{
$sQueryId = md5($sRes);
@@ -2140,7 +2182,7 @@ abstract class MetaModel
}
}
return $sRes;
return $oSelect;
}
public static function ShowQueryTrace()
@@ -2205,6 +2247,8 @@ abstract class MetaModel
$aModifierProperties = self::MakeModifierProperties($oFilter);
$oBuild = new QueryBuilderContext($oFilter, $aModifierProperties);
$oSelect = self::MakeQuery($oBuild, $oFilter, null, array(), true /* main query */);
$oSelect->SetCondition($oBuild->m_oQBExpressions->GetCondition());
$oSelect->SetSelect($oBuild->m_oQBExpressions->GetSelect());
$aScalarArgs = array_merge(self::PrepareQueryArguments($aArgs), $oFilter->GetInternalParams());
return $oSelect->RenderDelete($aScalarArgs);
}
@@ -2215,11 +2259,13 @@ abstract class MetaModel
$aModifierProperties = self::MakeModifierProperties($oFilter);
$oBuild = new QueryBuilderContext($oFilter, $aModifierProperties);
$oSelect = self::MakeQuery($oBuild, $oFilter, null, $aValues, true /* main query */);
$oSelect->SetCondition($oBuild->m_oQBExpressions->GetCondition());
$oSelect->SetSelect($oBuild->m_oQBExpressions->GetSelect());
$aScalarArgs = array_merge(self::PrepareQueryArguments($aArgs), $oFilter->GetInternalParams());
return $oSelect->RenderUpdate($aScalarArgs);
}
private static function MakeQuery(&$oBuild, DBObjectSearch $oFilter, $aAttToLoad = null, $aValues = array(), $bIsMainQuery = false)
private static function MakeQuery(&$oBuild, DBObjectSearch $oFilter, $aAttToLoad = null, $aValues = array(), $bIsMainQueryUNUSED = false)
{
// Note: query class might be different than the class of the filter
// -> this occurs when we are linking our class to an external class (referenced by, or pointing to)
@@ -2442,14 +2488,6 @@ abstract class MetaModel
}
}
// Translate the conditions... and go
//
if ($bIsMainQuery)
{
$oSelectBase->SetCondition($oBuild->m_oQBExpressions->GetCondition());
$oSelectBase->SetSelect($oBuild->m_oQBExpressions->GetSelect());
}
// That's all... cross fingers and we'll get some working query
//MyHelpers::var_dump_html($oSelectBase, true);

View File

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

View File

@@ -41,6 +41,7 @@ class SQLQuery
private $m_sTable = '';
private $m_sTableAlias = '';
private $m_aFields = array();
private $m_aGroupBy = array();
private $m_oConditionExpr = null;
private $m_bToDelete = true; // The current table must be listed for deletion ?
private $m_aValues = array(); // Values to set in case of an update query
@@ -62,6 +63,7 @@ class SQLQuery
$this->m_sTable = $sTable;
$this->m_sTableAlias = $sTableAlias;
$this->m_aFields = $aFields;
$this->m_aGroupBy = null;
$this->m_oConditionExpr = null;
$this->m_bToDelete = $bToDelete;
$this->m_aValues = $aValues;
@@ -125,11 +127,12 @@ class SQLQuery
}
$aFrom = array();
$aFields = array();
$aGroupBy = array();
$oCondition = null;
$aDelTables = array();
$aSetValues = array();
$aSelectedIdFields = array();
$this->privRender($aFrom, $aFields, $oCondition, $aDelTables, $aSetValues, $aSelectedIdFields);
$this->privRender($aFrom, $aFields, $aGroupBy, $oCondition, $aDelTables, $aSetValues, $aSelectedIdFields);
echo "From ...<br/>\n";
echo "<pre style=\"font-size: smaller;\">\n";
print_r($aFrom);
@@ -141,6 +144,11 @@ class SQLQuery
$this->m_aFields = $aExpressions;
}
public function SetGroupBy($aExpressions)
{
$this->m_aGroupBy = $aExpressions;
}
public function SetCondition($oConditionExpr)
{
$this->m_oConditionExpr = $oConditionExpr;
@@ -235,11 +243,12 @@ class SQLQuery
// The goal will be to complete the list as we build the Joins
$aFrom = array();
$aFields = array();
$aGroupBy = arry();
$oCondition = null;
$aDelTables = array();
$aSetValues = array();
$aSelectedIdFields = array();
$this->privRender($aFrom, $aFields, $oCondition, $aDelTables, $aSetValues, $aSelectedIdFields);
$this->privRender($aFrom, $aFields, $aGroupBy, $oCondition, $aDelTables, $aSetValues, $aSelectedIdFields);
// Target: DELETE myAlias1, myAlias2 FROM t1 as myAlias1, t2 as myAlias2, t3 as topreserve WHERE ...
@@ -270,11 +279,12 @@ class SQLQuery
// The goal will be to complete the list as we build the Joins
$aFrom = array();
$aFields = array();
$aGroupBy = array();
$oCondition = null;
$aDelTables = array();
$aSetValues = array();
$aSelectedIdFields = array();
$this->privRender($aFrom, $aFields, $oCondition, $aDelTables, $aSetValues, $aSelectedIdFields);
$this->privRender($aFrom, $aFields, $aGroupBy, $oCondition, $aDelTables, $aSetValues, $aSelectedIdFields);
$sFrom = self::ClauseFrom($aFrom);
$sValues = self::ClauseValues($aSetValues);
$sWhere = self::ClauseWhere($oCondition, $aArgs);
@@ -287,11 +297,12 @@ class SQLQuery
// The goal will be to complete the lists as we build the Joins
$aFrom = array();
$aFields = array();
$aGroupBy = array();
$oCondition = null;
$aDelTables = array();
$aSetValues = array();
$aSelectedIdFields = array();
$this->privRender($aFrom, $aFields, $oCondition, $aDelTables, $aSetValues, $aSelectedIdFields);
$this->privRender($aFrom, $aFields, $aGroupBy, $oCondition, $aDelTables, $aSetValues, $aSelectedIdFields);
$sFrom = self::ClauseFrom($aFrom);
$sWhere = self::ClauseWhere($oCondition, $aArgs);
@@ -328,6 +339,27 @@ class SQLQuery
return $sSQL;
}
// Interface, build the SQL query
public function RenderGroupBy($aArgs = array())
{
// The goal will be to complete the lists as we build the Joins
$aFrom = array();
$aFields = array();
$aGroupBy = array();
$oCondition = null;
$aDelTables = array();
$aSetValues = array();
$aSelectedIdFields = array();
$this->privRender($aFrom, $aFields, $aGroupBy, $oCondition, $aDelTables, $aSetValues, $aSelectedIdFields);
$sSelect = self::ClauseSelect($aFields);
$sFrom = self::ClauseFrom($aFrom);
$sWhere = self::ClauseWhere($oCondition, $aArgs);
$sGroupBy = self::ClauseGroupBy($aGroupBy);
$sSQL = "SELECT $sSelect, COUNT(*) AS _itop_count_ FROM $sFrom WHERE $sWhere GROUP BY $sGroupBy";
return $sSQL;
}
private static function ClauseSelect($aFields)
{
$aSelect = array();
@@ -339,6 +371,12 @@ class SQLQuery
return $sSelect;
}
private static function ClauseGroupBy($aGroupBy)
{
$sRes = implode(', ', $aGroupBy);
return $sRes;
}
private static function ClauseDelete($aDelTableAliases)
{
$aDelTables = array();
@@ -415,14 +453,14 @@ class SQLQuery
}
// Purpose: prepare the query data, once for all
private function privRender(&$aFrom, &$aFields, &$oCondition, &$aDelTables, &$aSetValues, &$aSelectedIdFields)
private function privRender(&$aFrom, &$aFields, &$aGroupBy, &$oCondition, &$aDelTables, &$aSetValues, &$aSelectedIdFields)
{
$sTableAlias = $this->privRenderSingleTable($aFrom, $aFields, $aDelTables, $aSetValues, $aSelectedIdFields, '', array('jointype' => 'first'));
$sTableAlias = $this->privRenderSingleTable($aFrom, $aFields, $aGroupBy, $aDelTables, $aSetValues, $aSelectedIdFields, '', array('jointype' => 'first'));
$oCondition = $this->m_oConditionExpr;
return $sTableAlias;
}
private function privRenderSingleTable(&$aFrom, &$aFields, &$aDelTables, &$aSetValues, &$aSelectedIdFields, $sCallerAlias = '', $aJoinData)
private function privRenderSingleTable(&$aFrom, &$aFields, &$aGroupBy, &$aDelTables, &$aSetValues, &$aSelectedIdFields, $sCallerAlias = '', $aJoinData)
{
$aActualTableFields = CMDBSource::GetTableFieldsList($this->m_sTable);
@@ -506,6 +544,13 @@ class SQLQuery
{
$aFields["`$sAlias`"] = $oExpression->Render();
}
if ($this->m_aGroupBy)
{
foreach($this->m_aGroupBy as $sAlias => $oExpression)
{
$aGroupBy["`$sAlias`"] = $oExpression->Render();
}
}
if ($this->m_bToDelete)
{
$aDelTables[] = "`{$this->m_sTableAlias}`";
@@ -528,7 +573,7 @@ class SQLQuery
{
$oRightSelect = $aJoinData["select"];
$sJoinTableAlias = $oRightSelect->privRenderSingleTable($aTempFrom, $aFields, $aDelTables, $aSetValues, $aSelectedIdFields, $this->m_sTableAlias, $aJoinData);
$sJoinTableAlias = $oRightSelect->privRenderSingleTable($aTempFrom, $aFields, $aGroupBy, $aDelTables, $aSetValues, $aSelectedIdFields, $this->m_sTableAlias, $aJoinData);
}
$aFrom[$this->m_sTableAlias]['subfrom'] = $aTempFrom;