diff --git a/core/dbobjectsearch.class.php b/core/dbobjectsearch.class.php index 1ea481aed..63d31de09 100644 --- a/core/dbobjectsearch.class.php +++ b/core/dbobjectsearch.class.php @@ -1133,7 +1133,91 @@ class DBObjectSearch extends DBSearch { return $this->m_oSearchCondition->ApplyParameters(array_merge($this->m_aParams, $aArgs)); } - + + public function ShorthandExpansion($bClone = false) + { + if ($bClone) + { + $oDbObject = $this->DeepClone(); + } + else + { + $oDbObject = $this; + } + + + $callback = function ($oExpression) use ($oDbObject) + { + if (!$oExpression instanceof ExternalFieldExpression) { + return; + } + /** @var FieldExpression[] $aFieldExpressionsPointingTo */ + $aFields = $oExpression->GetFields(); + + $aExpressionNewConditions = array(); + $aRealiasingMap = array(); + foreach ($aFields as $aFieldExpressionPointingTo) + { + $oFilter = new DBObjectSearch($aFieldExpressionPointingTo['sClass']); + $aExpressionNewConditions[] = array( + 'oFilter' => $oFilter, + 'sExtKeyAttCode' => $aFieldExpressionPointingTo['sAttCode'], + ); + + $aRealiasingMap[$aFieldExpressionPointingTo['sClass']] = $aFieldExpressionPointingTo['sAlias']; + + } + + + /** + * the iteration beleow is weird because wee need to + * - iterate in the reverse order + * - the iteration access the "index+1" so wee start at "length-1" (which is "count()-2") + * - whe stop at the index 1, because the index 0 is merged into the $oDbObject + */ + for ($i = count($aExpressionNewConditions) - 2; $i > 0; $i--) + { + $aExpressionNewConditions[$i]['oFilter']->AddCondition_PointingTo( + $aExpressionNewConditions[$i+1]['oFilter'], + $aExpressionNewConditions[$i]['sExtKeyAttCode'], + TREE_OPERATOR_EQUALS, + $aRealiasingMap + ); + } + + + $oDbObject->AddCondition_PointingTo( + $aExpressionNewConditions[1]['oFilter'], + $aExpressionNewConditions[0]['sExtKeyAttCode'], + TREE_OPERATOR_EQUALS, + $aRealiasingMap + ); + + foreach ($aRealiasingMap as $sOldAlias => $sNewAlias) + { + if ($sOldAlias == $sNewAlias) + { + continue; + } + + if ($sOldAlias != $aFields['sAlias']) + { + continue; + } + $aFields['sAlias'] = $sNewAlias; + } + + $oExpression->SetFields($aFields); + }; //end of the callback definition + + + $this->m_oSearchCondition->Browse($callback); + + $this->m_oSearchCondition->Translate(array()); + + return $oDbObject; + } + public function ToOQL($bDevelopParams = false, $aContextParams = null, $bWithAllowAllFlag = false) { // Currently unused, but could be useful later @@ -1264,7 +1348,20 @@ class DBObjectSearch extends DBSearch } elseif ($oExpression instanceof ExternalFieldOqlExpression) { - return new ExternalFieldExpression($oExpression->GetName(), $oExpression->GetExpressions()); + //TODO : convert FieldOqlExpression[] to FieldExpression[] + $aOqlFieldExpression = $oExpression->GetExpressions(); + $aFields = array(); + + foreach ($aOqlFieldExpression as $oOqlFieldExpression) + { + $aFields[] = array( + 'sClass' => $oOqlFieldExpression->GetParent(), + 'sAlias' => $oOqlFieldExpression->GetParent(), + 'sAttCode' => $oOqlFieldExpression->GetName(), + ); + } + + return new ExternalFieldExpression($oExpression->GetName(), $aFields); } elseif ($oExpression instanceof FieldOqlExpression) { @@ -1534,7 +1631,7 @@ class DBObjectSearch extends DBSearch $aContextData['sRequestUri'] = ''; } - // Need to identify the query + // Need to identify the querySELECT `Contact` FROM Contact AS `Contact` JOIN Organization AS `Organization` ON `Contact`.org_id = `Organization`.id JOIN DeliveryModel AS `DeliveryModel` ON `Organization`.deliverymodel_id = `DeliveryModel`.id WHERE ((((Contact.org_id->deliverymodel_id->name = 'Standard support') AND 1) AND 1) AND 1) $sOqlQuery = $oSearch->ToOql(false, null, true); if ((strpos($sOqlQuery, '`id` IN (') !== false) || (strpos($sOqlQuery, '`id` NOT IN (') !== false)) { @@ -1640,7 +1737,8 @@ class DBObjectSearch extends DBSearch if (!isset($oSQLQuery)) { - $oKPI = new ExecutionKPI(); + $oSearch = $oSearch->ShorthandExpansion(true);//TODO : check if the clone is really needed (1st param of ShorthandExpansion) + $oKPI = new ExecutionKPI(); $oSQLQuery = $oSearch->BuildSQLQueryStruct($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr, $aSelectedClasses, $aSelectExpr); $oKPI->ComputeStats('BuildSQLQueryStruct', $sOqlQuery); diff --git a/core/oql/expression.class.inc.php b/core/oql/expression.class.inc.php index bb7f40463..10d546ff7 100644 --- a/core/oql/expression.class.inc.php +++ b/core/oql/expression.class.inc.php @@ -843,13 +843,56 @@ class FalseExpression extends ScalarExpression class ExternalFieldExpression extends UnaryExpression { - protected $m_aFieldExpressions = array(); + /** + * @var array[] array containing the shorthand chained fields & their classes + * ['sClass'] string the Class + * ['sAlias'] string the Class alias + * ['sAttCode'] string the attribute code + */ + protected $m_aFields = array(); protected $m_sName; - public function __construct($sName, $aExpressions) + public function __construct($sName, $aFields) { parent::__construct($sName); + + $this->SetFields($aFields); } + + public function SetFields($aFields) + { + $this->m_aFields = $aFields; + } + + public function GetFields() + { + return $this->m_aFields; + } + + public function Translate($aTranslationData, $bMatchAll = true, $bMarkFieldsAsResolved = true) + { + $aFields = $this->GetFields(); + $aLastField = end($aFields); + + + $oRet = new FieldExpression($aLastField['sAttCode'], $aLastField['sAlias']); +// $oRet->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved); + + return $oRet; + } + + public function Render(&$aArgs = null, $bRetrofitParams = false) + { + $aAttCode = array(); + + $aFields = $this->GetFields(); + foreach ($aFields as $field) { + $aAttCode[] = $field['sAttCode']; + } + + return $aFields[0]['sAlias'] . '.' . implode('->', $aAttCode); + } + } class FieldExpression extends UnaryExpression diff --git a/core/oql/oqlquery.class.inc.php b/core/oql/oqlquery.class.inc.php index 2a3dc5260..af5f49d73 100644 --- a/core/oql/oqlquery.class.inc.php +++ b/core/oql/oqlquery.class.inc.php @@ -253,7 +253,7 @@ class ExternalFieldOqlExpression extends ExternalFieldExpression implements Chec } else { - if ($oFieldOqlExpression->GetName() != 'id') + if ($oFieldOqlExpression->GetName() != 'id') //on lastIteration, the id field can be used, but since it is not supporter by IsValidAttCode we must avoid it { if (!$oModelReflection->IsValidAttCode($sClass, $oFieldOqlExpression->GetName())) { diff --git a/test/core/oql/OqlInterpreterTest.php b/test/core/oql/OqlInterpreterTest.php index d58ff22f8..8402d2722 100644 --- a/test/core/oql/OqlInterpreterTest.php +++ b/test/core/oql/OqlInterpreterTest.php @@ -9,9 +9,9 @@ namespace Combodo\iTop\Test\UnitTest\Core\Oql; use OqlInterpreter; -use Combodo\iTop\Test\UnitTest\ItopTestCase; +use Combodo\iTop\Test\UnitTest\ItopDataTestCase; -class OqlInterpreterTest extends ItopTestCase +class OqlInterpreterTest extends ItopDataTestCase { /** * @throws \Exception @@ -21,26 +21,81 @@ class OqlInterpreterTest extends ItopTestCase parent::setUp(); require_once(APPROOT.'/core/cmdbobject.class.inc.php'); - require_once(APPROOT."core/oql/oqlinterpreter.class.inc.php"); + require_once(APPROOT."core/oql/oqlinterpreter.class.inc.php"); + require_once(APPROOT.'core/dbobject.class.php'); + require_once(APPROOT."core/dbobjectsearch.class.php"); + require_once(APPROOT."core/modelreflection.class.inc.php"); + + } - /** - * @dataProvider ParseProvider - * @param $sQuery - */ - public function testParse($sQuery) - { - $oOql = new OqlInterpreter($sQuery); - $oTrash = $oOql->Parse(); // Not expecting a given format, otherwise use ParseExpression/ParseObjectQuery/ParseValueSetQuery - $this->debug($oTrash); - } - public function ParseProvider() - { - return array( - array("SELECT Contact WHERE org_id->deliverymodel_id->name = 'toto'"), - array("SELECT Contact WHERE cis_list->name = 'toto'"), - ); - } + + public function testShorthandExpansionCloningOption() + { + $sQuery = "SELECT Contact WHERE org_id->deliverymodel_id->name = 'Standard support' AND 1 AND 1 AND 1"; + + $oDbObjectSearch = \DBObjectSearch::FromOQL($sQuery); + $oDbObjectSearchExpandedClone1 = $oDbObjectSearch->ShorthandExpansion(true); + $oDbObjectSearchExpandedClone2 = $oDbObjectSearch->ShorthandExpansion(true); + $oDbObjectSearchExpandedBase1 = $oDbObjectSearch->ShorthandExpansion(); + $oDbObjectSearchExpandedBase2 = $oDbObjectSearch->ShorthandExpansion(); + + $this->assertSame($oDbObjectSearchExpandedBase1, $oDbObjectSearch); + $this->assertSame($oDbObjectSearchExpandedBase2, $oDbObjectSearch); + + $this->assertNotSame($oDbObjectSearchExpandedClone1, $oDbObjectSearch); + $this->assertNotSame($oDbObjectSearchExpandedClone2, $oDbObjectSearch); + + $this->assertNotSame($oDbObjectSearchExpandedClone1, $oDbObjectSearchExpandedClone2); + + $this->debug($oDbObjectSearch->ToOQL()); + } + + /** + * @dataProvider ShorthandExpansionProvider + * @param $sQuery + */ + public function testShorthandExpansion($sQuery) + { + $oDbObjectSearch = \DBObjectSearch::FromOQL($sQuery); + $oDbObjectSearchExpanded = $oDbObjectSearch->ShorthandExpansion(); + + + $this->assertSame($oDbObjectSearch, $oDbObjectSearchExpanded); + + $this->debug($oDbObjectSearch->ToOQL()); + } + + public function ShorthandExpansionProvider() + { + return array( + array("SELECT Contact WHERE org_id->deliverymodel_id->name = 'tato'"), + array('SELECT Contact WHERE cis_list->name = "Cluster1"'), + array('SELECT Contact WHERE cis_list->name like "%m%"'), + ); + } + + + /** + * @dataProvider ParseProvider + * @param $sQuery + */ + public function testParse($sQuery) + { + $oOql = new OqlInterpreter($sQuery); + $oTrash = $oOql->Parse(); // Not expecting a given format, otherwise use ParseExpression/ParseObjectQuery/ParseValueSetQuery + + $this->debug($oTrash); + } + + public function ParseProvider() + { + return array( + array("SELECT Contact WHERE org_id->deliverymodel_id->name = 'Standard support'"), + array("SELECT Contact WHERE cis_list->name = 'toto'"), + ); + } + }