diff --git a/core/dbobjectsearch.class.php b/core/dbobjectsearch.class.php index f1295407d..dbd40e605 100644 --- a/core/dbobjectsearch.class.php +++ b/core/dbobjectsearch.class.php @@ -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) diff --git a/core/dbsearch.class.php b/core/dbsearch.class.php index f81d6a79a..8b8b9fb6f 100644 --- a/core/dbsearch.class.php +++ b/core/dbsearch.class.php @@ -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 ); //////////////////////////////////////////////////////////////////////////// diff --git a/core/dbunionsearch.class.php b/core/dbunionsearch.class.php index 27e370fc0..d0c967671 100644 --- a/core/dbunionsearch.class.php +++ b/core/dbunionsearch.class.php @@ -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(); diff --git a/core/oql/expression.class.inc.php b/core/oql/expression.class.inc.php index 3438eb2c5..80db27c07 100644 --- a/core/oql/expression.class.inc.php +++ b/core/oql/expression.class.inc.php @@ -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); diff --git a/core/querybuildercontext.class.inc.php b/core/querybuildercontext.class.inc.php index 71f456502..87cd5ed27 100644 --- a/core/querybuildercontext.class.inc.php +++ b/core/querybuildercontext.class.inc.php @@ -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(); diff --git a/core/sqlobjectquery.class.inc.php b/core/sqlobjectquery.class.inc.php index ab7136a4b..47e89ae39 100644 --- a/core/sqlobjectquery.class.inc.php +++ b/core/sqlobjectquery.class.inc.php @@ -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; } diff --git a/core/sqlquery.class.inc.php b/core/sqlquery.class.inc.php index 4370f8c8a..f759b09be 100644 --- a/core/sqlquery.class.inc.php +++ b/core/sqlquery.class.inc.php @@ -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) diff --git a/core/sqlunionquery.class.inc.php b/core/sqlunionquery.class.inc.php index 64192fbd4..5d3e81320 100644 --- a/core/sqlunionquery.class.inc.php +++ b/core/sqlunionquery.class.inc.php @@ -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; } diff --git a/test/GroupByAndFunctions.php b/test/GroupByAndFunctions.php new file mode 100644 index 000000000..015b8b673 --- /dev/null +++ b/test/GroupByAndFunctions.php @@ -0,0 +1,343 @@ + +// + +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('
| $item | "); + } + } + $oP->add('
|---|
| ".$aLine[$sCol]." | "); + } + $oP->add('
No Result
\n"); + } +} +catch (Exception $e) +{ + $oP->p(''); + $bError = true; +} + +$oP->add("\n"); + +$oP->output(); + +return; + +/* +echo "";
+$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;
+ }
+ }
+}
+*/
\ No newline at end of file
diff --git a/test/core/DBSearchTest.php b/test/core/DBSearchTest.php
new file mode 100644
index 000000000..79c847075
--- /dev/null
+++ b/test/core/DBSearchTest.php
@@ -0,0 +1,483 @@
+
+//
+
+/**
+ * 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.
+ *