diff --git a/application/dashlet.class.inc.php b/application/dashlet.class.inc.php
index 37469603f..240a462c3 100644
--- a/application/dashlet.class.inc.php
+++ b/application/dashlet.class.inc.php
@@ -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 = ''.$sQuery.'';
+ $sXML = ''.$sQuery.'';
$sHtmlTitle = ''; // done in the itop block
break;
case 'pie':
- $sXML = ''.$sQuery.'';
+ $sXML = ''.$sQuery.'';
$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 = ''.$sQuery.'';
+ $sXML = ''.$sQuery.'';
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' => "$iCount"
+ ); // 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('
');
+
+ $oPage->add('
'.$sHtmlTitle.'
');
+ $oPage->p(Dict::Format('UI:Pagination:HeaderNoSelection', $iTotalCount));
+ $oPage->table($aAttribs, $aData);
+
+ $oPage->add('');
+ }
+
+ 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',
+ );
+ }
+}
diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php
index 36c0b3c2a..4a449f95e 100644
--- a/application/displayblock.class.inc.php
+++ b/application/displayblock.class.inc.php
@@ -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' => "$iCount"); // TO DO: add the context information
+ 'value' => "$iCount"); // 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 .= "\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¶ms[group_by]=$sGroupBy{$sGroupByExpr}¶ms[group_by_label]={$aExtraParams['group_by_label']}¶ms[chart_type]=$sChartType¶ms[chart_title]=$sTitle¶ms[currentId]=$sId&id=$sId&filter=".$sFilter);
+ }
+ else
+ {
+ $sUrl = urlencode(utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=open_flash_chart¶ms[group_by]=$sGroupBy{$sGroupByExpr}¶ms[chart_type]=$sChartType¶ms[chart_title]=$sTitle¶ms[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¶ms[group_by]=$sGroupBy{$sGroupByExpr}¶ms[chart_type]=$sChartType¶ms[chart_title]=$sTitle¶ms[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)
{
diff --git a/core/expression.class.inc.php b/core/expression.class.inc.php
index 75f6184fa..fe414112b 100644
--- a/core/expression.class.inc.php
+++ b/core/expression.class.inc.php
@@ -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);
diff --git a/core/metamodel.class.php b/core/metamodel.class.php
index e31dba553..973904dca 100644
--- a/core/metamodel.class.php
+++ b/core/metamodel.class.php
@@ -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 "MakeQuery: $sRes
";
- }
- 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);
diff --git a/core/querybuildercontext.class.inc.php b/core/querybuildercontext.class.inc.php
index 8c6e88544..f4cc5f50c 100644
--- a/core/querybuildercontext.class.inc.php
+++ b/core/querybuildercontext.class.inc.php
@@ -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();
diff --git a/core/sqlquery.class.inc.php b/core/sqlquery.class.inc.php
index 674ede0ab..ed0743445 100644
--- a/core/sqlquery.class.inc.php
+++ b/core/sqlquery.class.inc.php
@@ -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 ...
\n";
echo "\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;