diff --git a/application/ajaxwebpage.class.inc.php b/application/ajaxwebpage.class.inc.php index ed464a163..8620ef09b 100644 --- a/application/ajaxwebpage.class.inc.php +++ b/application/ajaxwebpage.class.inc.php @@ -1,5 +1,5 @@ ".Dict::Format('UI:SearchFor_Class_Objects', $sClassesCombo)."\n"; $index = 0; $sHtml .= "

\n"; - $aFilterCriteria = $oSet->GetFilter()->GetCriteria(); + //$aFilterCriteria = $oSet->GetFilter()->GetCriteria(); $aMapCriteria = array(); // Todo: Investigate... The search criteria is an expression, i.e. a tree! // I wonder if that code could work... cleanup required/recommended + // Temporary fix (unions do fail with this) + $aFilterCriteria = array(); foreach($aFilterCriteria as $aCriteria) { $aMapCriteria[$aCriteria['filtercode']][] = array('value' => $aCriteria['value'], 'opcode' => $aCriteria['opcode']); @@ -3196,7 +3198,7 @@ EOF return $res; } - protected static function BulkUpdateTracked_Internal(DBObjectSearch $oFilter, array $aValues) + protected static function BulkUpdateTracked_Internal(DBSearch $oFilter, array $aValues) { // Todo - invoke the extension return parent::BulkUpdateTracked_Internal($oFilter, $aValues); diff --git a/application/csvpage.class.inc.php b/application/csvpage.class.inc.php index 062301858..a39ec60a9 100644 --- a/application/csvpage.class.inc.php +++ b/application/csvpage.class.inc.php @@ -1,5 +1,5 @@ s_content); echo "\n"; - if (class_exists('MetaModel')) + if (class_exists('DBSearch')) { - MetaModel::RecordQueryTrace(); + DBSearch::RecordQueryTrace(); } if (class_exists('ExecutionKPI')) { diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index fa2276ca6..8161d3ff0 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -1,5 +1,5 @@ m_oFilter = $oFilter->DeepClone(); $this->m_aConditions = array(); @@ -407,7 +407,7 @@ class DisplayBlock $aGroupBy = array(); $aGroupBy['grouped_by_1'] = $oGroupByExp; - $sSql = MetaModel::MakeGroupByQuery($this->m_oFilter, $aQueryParams, $aGroupBy, true); + $sSql = $this->m_oFilter->MakeGroupByQuery($aQueryParams, $aGroupBy, true); $aRes = CMDBSource::QueryToArray($sSql); $aGroupBy = array(); @@ -911,7 +911,7 @@ EOF $aGroupBy = array(); $aGroupBy['grouped_by_1'] = $oGroupByExp; - $sSql = MetaModel::MakeGroupByQuery($this->m_oFilter, $aQueryParams, $aGroupBy, true); + $sSql = $this->m_oFilter->MakeGroupByQuery($aQueryParams, $aGroupBy, true); $aRes = CMDBSource::QueryToArray($sSql); $aGroupBy = array(); @@ -986,7 +986,7 @@ EOF $aGroupBy = array(); $aGroupBy['grouped_by_1'] = $oGroupByExp; - $sSql = MetaModel::MakeGroupByQuery($this->m_oFilter, $aQueryParams, $aGroupBy, true); + $sSql = $this->m_oFilter->MakeGroupByQuery($aQueryParams, $aGroupBy, true); $aRes = CMDBSource::QueryToArray($sSql); $aGroupBy = array(); @@ -1068,7 +1068,7 @@ EOF $aGroupBy = array(); $aGroupBy['grouped_by_1'] = $oGroupByExp; - $sSql = MetaModel::MakeGroupByQuery($this->m_oFilter, $aQueryParams, $aGroupBy, true); + $sSql = $this->m_oFilter->MakeGroupByQuery($aQueryParams, $aGroupBy, true); $aRes = CMDBSource::QueryToArray($sSql); $aGroupBy = array(); @@ -1128,7 +1128,7 @@ EOF } /** - * Add a condition (restriction) to the current DBObjectSearch on which the display block is based + * Add a condition (restriction) to the current DBSearch on which the display block is based * taking into account the hierarchical keys for which the condition is based on the 'below' operator */ protected function AddCondition($sFilterCode, $condition, $sOpCode = null) @@ -1216,7 +1216,7 @@ class HistoryBlock extends DisplayBlock protected $iLimitCount; protected $iLimitStart; - public function __construct(DBObjectSearch $oFilter, $sStyle = 'list', $bAsynchronous = false, $aParams = array(), $oSet = null) + public function __construct(DBSearch $oFilter, $sStyle = 'list', $bAsynchronous = false, $aParams = array(), $oSet = null) { parent::__construct($oFilter, $sStyle, $bAsynchronous, $aParams, $oSet); $this->iLimitStart = 0; @@ -1543,7 +1543,7 @@ class MenuBlock extends DisplayBlock { $aQueryParams = $aExtraParams['query_params']; } - $sSql = MetaModel::MakeGroupByQuery($this->m_oFilter, $aQueryParams, $aGroupBy); + $sSql = $this->m_oFilter->MakeGroupByQuery($aQueryParams, $aGroupBy); $aRes = CMDBSource::QueryToArray($sSql); if (count($aRes) == 1) { diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index 4f70bed96..f4bbc1483 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -1,5 +1,5 @@ Output($sOutputName, 'I'); } } - MetaModel::RecordQueryTrace(); + DBSearch::RecordQueryTrace(); ExecutionKPI::ReportStats(); } diff --git a/application/ui.linksdirectwidget.class.inc.php b/application/ui.linksdirectwidget.class.inc.php index c3b1e8872..fcac2f0e7 100644 --- a/application/ui.linksdirectwidget.class.inc.php +++ b/application/ui.linksdirectwidget.class.inc.php @@ -1,5 +1,5 @@ \n"; echo "\n"; - if (class_exists('MetaModel')) + if (class_exists('DBSearch')) { - MetaModel::RecordQueryTrace(); + DBSearch::RecordQueryTrace(); } if (class_exists('ExecutionKPI')) { diff --git a/application/xmlpage.class.inc.php b/application/xmlpage.class.inc.php index d9d6b99fc..0c6f749b5 100644 --- a/application/xmlpage.class.inc.php +++ b/application/xmlpage.class.inc.php @@ -1,5 +1,5 @@ s_content; } - if (class_exists('MetaModel')) + if (class_exists('DBSearch')) { - MetaModel::RecordQueryTrace(); + DBSearch::RecordQueryTrace(); } } diff --git a/core/bulkexport.class.inc.php b/core/bulkexport.class.inc.php index 5ef3a91f3..a3cd3727d 100644 --- a/core/bulkexport.class.inc.php +++ b/core/bulkexport.class.inc.php @@ -147,7 +147,7 @@ abstract class BulkExport /** * Find the first class capable of exporting the data in the given format * @param string $sFormat The lowercase format (e.g. html, csv, spreadsheet, xlsx, xml, json, pdf...) - * @param DBObjectSearch $oSearch The search/filter defining the set of objects to export or null when listing the supported formats + * @param DBSearch $oSearch The search/filter defining the set of objects to export or null when listing the supported formats * @return iBulkExport|NULL */ static public function FindExporter($sFormatCode, $oSearch = null) @@ -251,7 +251,7 @@ abstract class BulkExport * (non-PHPdoc) * @see iBulkExport::SetObjectList() */ - public function SetObjectList(DBObjectSearch $oSearch) + public function SetObjectList(DBSearch $oSearch) { $this->oSearch = $oSearch; } diff --git a/core/cmdbobject.class.inc.php b/core/cmdbobject.class.inc.php index 29685d2e0..397ffe055 100644 --- a/core/cmdbobject.class.inc.php +++ b/core/cmdbobject.class.inc.php @@ -20,7 +20,7 @@ /** * Class cmdbObject * - * @copyright Copyright (C) 2010-2012 Combodo SARL + * @copyright Copyright (C) 2010-2015 Combodo SARL * @license http://opensource.org/licenses/AGPL-3.0 */ @@ -50,6 +50,8 @@ require_once('expression.class.inc.php'); require_once('cmdbsource.class.inc.php'); require_once('sqlquery.class.inc.php'); +require_once('sqlobjectquery.class.inc.php'); +require_once('sqlunionquery.class.inc.php'); require_once('oql/oqlquery.class.inc.php'); require_once('oql/oqlexception.class.inc.php'); require_once('oql/oql-parser.php'); @@ -57,7 +59,7 @@ require_once('oql/oql-lexer.php'); require_once('oql/oqlinterpreter.class.inc.php'); require_once('dbobject.class.php'); -require_once('dbobjectsearch.class.php'); +require_once('dbsearch.class.php'); require_once('dbobjectset.class.php'); require_once('backgroundprocess.inc.php'); @@ -522,18 +524,18 @@ abstract class CMDBObject extends DBObject return $ret; } - public static function BulkUpdate(DBObjectSearch $oFilter, array $aValues) + public static function BulkUpdate(DBSearch $oFilter, array $aValues) { return $this->BulkUpdateTracked_Internal($oFilter, $aValues); } - public static function BulkUpdateTracked(CMDBChange $oChange, DBObjectSearch $oFilter, array $aValues) + public static function BulkUpdateTracked(CMDBChange $oChange, DBSearch $oFilter, array $aValues) { self::SetCurrentChange($oChange); $this->BulkUpdateTracked_Internal($oFilter, $aValues); } - protected static function BulkUpdateTracked_Internal(DBObjectSearch $oFilter, array $aValues) + protected static function BulkUpdateTracked_Internal(DBSearch $oFilter, array $aValues) { // $aValues is an array of $sAttCode => $value diff --git a/core/dbobject.class.php b/core/dbobject.class.php index beb546d69..7c01d2278 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -1862,7 +1862,7 @@ abstract class DBObject implements iDisplay $oFilter = new DBObjectSearch(get_class($this)); $oFilter->AddCondition('id', $this->m_iKey, '='); - $sSQL = MetaModel::MakeUpdateQuery($oFilter, $aChanges); + $sSQL = $oFilter->MakeUpdateQuery($aChanges); CMDBSource::Query($sSQL); } } @@ -2627,7 +2627,6 @@ abstract class DBObject implements iDisplay } foreach (MetaModel::EnumRelationQueries(get_class($this), $sHackedRelCode, $bDown) as $sDummy => $aQueryInfo) { - MetaModel::DbgTrace("object=".$this->GetKey().", depth=$iMaxDepth, rel=".$aQueryInfo['sQueryDown']); $sQuery = $bDown ? $aQueryInfo['sQueryDown'] : $aQueryInfo['sQueryUp']; //$bPropagate = $aQueryInfo["bPropagate"]; //$iDepth = $bPropagate ? $iMaxDepth - 1 : 0; diff --git a/core/dbobjectsearch.class.php b/core/dbobjectsearch.class.php index 8ea0d0df1..06693a434 100644 --- a/core/dbobjectsearch.class.php +++ b/core/dbobjectsearch.class.php @@ -1,5 +1,5 @@ class name), the first item is the class corresponding to this filter (the rest is coming from subfilters) private $m_aSelectedClasses; // selected for the output (alias => class name) @@ -43,15 +34,11 @@ class DBObjectSearch private $m_aFullText; private $m_aPointingTo; private $m_aReferencedBy; - private $m_aRelatedTo; - private $m_bDataFiltered; - - // By default, some information may be hidden to the current user - // But it may happen that we need to disable that feature - private $m_bAllowAllData = false; public function __construct($sClass, $sClassAlias = null) { + parent::__construct(); + if (is_null($sClassAlias)) $sClassAlias = $sClass; if(!is_string($sClass)) throw new Exception('DBObjectSearch::__construct called with a non-string parameter: $sClass = '.print_r($sClass, true)); if(!MetaModel::IsValidClass($sClass)) throw new Exception('DBObjectSearch::__construct called for an invalid class: "'.$sClass.'"'); @@ -63,31 +50,24 @@ class DBObjectSearch $this->m_aFullText = array(); $this->m_aPointingTo = array(); $this->m_aReferencedBy = array(); - $this->m_aRelatedTo = array(); - $this->m_bDataFiltered = false; - $this->m_aParentConditions = array(); - - $this->m_aModifierProperties = array(); } - /** - * Perform a deep clone (as opposed to "clone" which does copy a reference to the underlying objects) - **/ - public function DeepClone() + // Create a search definition that leads to 0 result, still a valid search object + static public function FromEmptySet($sClass) { - return unserialize(serialize($this)); // Beware this serializes/unserializes the search and its parameters as well + $oResultFilter = new DBObjectSearch($sClass); + $oResultFilter->m_oSearchCondition = new FalseExpression; + return $oResultFilter; } - public function AllowAllData() {$this->m_bAllowAllData = true;} - public function IsAllDataAllowed() {return $this->m_bAllowAllData;} - public function IsDataFiltered() {return $this->m_bDataFiltered; } - public function SetDataFiltered() {$this->m_bDataFiltered = true;} + + public function GetJoinedClasses() {return $this->m_aClasses;} public function GetClassName($sAlias) { - if (array_key_exists($sAlias, $this->m_aClasses)) + if (array_key_exists($sAlias, $this->m_aSelectedClasses)) { - return $this->m_aClasses[$sAlias]; + return $this->m_aSelectedClasses[$sAlias]; } else { @@ -95,8 +75,6 @@ class DBObjectSearch } } - public function GetJoinedClasses() {return $this->m_aClasses;} - public function GetClass() { return reset($this->m_aSelectedClasses); @@ -129,13 +107,18 @@ class DBObjectSearch } else { - if (!array_key_exists($sAlias, $this->m_aClasses)) + if (!array_key_exists($sAlias, $this->m_aSelectedClasses)) { // discard silently - necessary when recursing on the related nodes (see code below) return; } } $sCurrClass = $this->GetClassName($sAlias); + if ($sNewClass == $sCurrClass) + { + // Skip silently + return; + } if (!MetaModel::IsParentClass($sCurrClass, $sNewClass)) { throw new Exception("Could not change the search class from '$sCurrClass' to '$sNewClass'. Only child classes are permitted."); @@ -148,10 +131,6 @@ class DBObjectSearch // Change for all the related node (yes, this was necessary with some queries - strange effects otherwise) // - foreach($this->m_aRelatedTo as $aRelatedTo) - { - $aRelatedTo['flt']->ChangeClass($sNewClass, $sAlias); - } foreach($this->m_aPointingTo as $sExtKeyAttCode=>$aPointingTo) { foreach($aPointingTo as $iOperatorCode => $aFilter) @@ -171,19 +150,6 @@ class DBObjectSearch } } - public function SetSelectedClasses($aNewSet) - { - $this->m_aSelectedClasses = array(); - foreach ($aNewSet as $sAlias => $sClass) - { - if (!array_key_exists($sAlias, $this->m_aClasses)) - { - throw new CoreException('Unexpected class alias', array('alias'=>$sAlias, 'expected'=>$this->m_aClasses)); - } - $this->m_aSelectedClasses[$sAlias] = $sClass; - } - } - public function GetSelectedClasses() { return $this->m_aSelectedClasses; @@ -213,154 +179,8 @@ class DBObjectSearch if (count($this->m_aFullText) > 0) return false; if (count($this->m_aPointingTo) > 0) return false; if (count($this->m_aReferencedBy) > 0) return false; - if (count($this->m_aRelatedTo) > 0) return false; - if (count($this->m_aParentConditions) > 0) return false; return true; } - - public function Describe() - { - // To replace __Describe - } - - public function DescribeConditionPointTo($sExtKeyAttCode, $aPointingTo) - { - if (empty($aPointingTo)) return ""; - foreach($aPointingTo as $iOperatorCode => $oFilter) - { - if ($oFilter->IsAny()) break; - $oAtt = MetaModel::GetAttributeDef($this->GetClass(), $sExtKeyAttCode); - $sOperator = ''; - switch($iOperatorCode) - { - case TREE_OPERATOR_EQUALS: - $sOperator = 'having'; - break; - - case TREE_OPERATOR_BELOW: - $sOperator = 'below'; - break; - - case TREE_OPERATOR_BELOW_STRICT: - $sOperator = 'strictly below'; - break; - - case TREE_OPERATOR_NOT_BELOW: - $sOperator = 'not below'; - break; - - case TREE_OPERATOR_NOT_BELOW_STRICT: - $sOperator = 'strictly not below'; - break; - - case TREE_OPERATOR_ABOVE: - $sOperator = 'above'; - break; - - case TREE_OPERATOR_ABOVE_STRICT: - $sOperator = 'strictly above'; - break; - - case TREE_OPERATOR_NOT_ABOVE: - $sOperator = 'not above'; - break; - - case TREE_OPERATOR_NOT_ABOVE_STRICT: - $sOperator = 'strictly not above'; - break; - } - $aDescription[] = $oAtt->GetLabel()."$sOperator ({$oFilter->DescribeConditions()})"; - } - return implode(' and ', $aDescription); - } - - public function DescribeConditionRefBy($sForeignClass, $sForeignExtKeyAttCode) - { - if (!isset($this->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode])) return ""; - $oFilter = $this->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode]; - if ($oFilter->IsAny()) return ""; - $oAtt = MetaModel::GetAttributeDef($sForeignClass, $sForeignExtKeyAttCode); - return "being ".$oAtt->GetLabel()." for ".$sForeignClass."s in ({$oFilter->DescribeConditions()})"; - } - - public function DescribeConditionRelTo($aRelInfo) - { - $oFilter = $aRelInfo['flt']; - $sRelCode = $aRelInfo['relcode']; - $iMaxDepth = $aRelInfo['maxdepth']; - return "related ($sRelCode... peut mieux faire !, $iMaxDepth dig depth) to a {$oFilter->GetClass()} ({$oFilter->DescribeConditions()})"; - } - - - public function DescribeConditions() - { - $aConditions = array(); - - $aCondFT = array(); - foreach($this->m_aFullText as $sFullText) - { - $aCondFT[] = " contain word(s) '$sFullText'"; - } - if (count($aCondFT) > 0) - { - $aConditions[] = "which ".implode(" and ", $aCondFT); - } - - // #@# todo - review textual description of the JOIN and search condition (is that still feasible?) - $aConditions[] = $this->RenderCondition(); - - $aCondPoint = array(); - foreach($this->m_aPointingTo as $sExtKeyAttCode => $aPointingTo) - { - $aCondPoint[] = $this->DescribeConditionPointTo($sExtKeyAttCode, $aPointingTo); - } - if (count($aCondPoint) > 0) - { - $aConditions[] = implode(" and ", $aCondPoint); - } - - $aCondReferred= array(); - foreach($this->m_aReferencedBy as $sForeignClass=>$aReferences) - { - foreach($aReferences as $sForeignExtKeyAttCode=>$oForeignFilter) - { - if ($oForeignFilter->IsAny()) continue; - $aCondReferred[] = $this->DescribeConditionRefBy($sForeignClass, $sForeignExtKeyAttCode); - } - } - foreach ($this->m_aRelatedTo as $aRelInfo) - { - $aCondReferred[] = $this->DescribeConditionRelTo($aRelInfo); - } - if (count($aCondReferred) > 0) - { - $aConditions[] = implode(" and ", $aCondReferred); - } - - foreach ($this->m_aParentConditions as $aRelInfo) - { - $aCondReferred[] = $this->DescribeConditionParent($aRelInfo); - } - - return implode(" and ", $aConditions); - } - - public function __DescribeHTML() - { - try - { - $sConditionDesc = $this->DescribeConditions(); - } - catch (MissingQueryArgument $e) - { - $sConditionDesc = '?missing query argument?'; - } - if (!empty($sConditionDesc)) - { - return "Objects of class '".$this->GetClass()."', $sConditionDesc"; - } - return "Any object of class '".$this->GetClass()."'"; - } protected function TransferConditionExpression($oFilter, $aTranslation) { @@ -381,11 +201,7 @@ class DBObjectSearch $oFilter->m_aParams[$sParam.$index] = $secondValue; } } -//echo "

TransferConditionExpression:
"; -//echo "Adding Conditions:

oFilter:\n".print_r($oFilter, true)."\naTranslation:\n".print_r($aTranslation, true)."
\n"; -//echo "

"; $oTranslated = $oFilter->GetCriteria()->Translate($aTranslation, false, false /* leave unresolved fields */); -//echo "Adding Conditions (translated):
".print_r($oTranslated, true)."
\n"; $this->AddConditionExpression($oTranslated); $this->m_aParams = array_merge($this->m_aParams, $oFilter->m_aParams); } @@ -393,10 +209,6 @@ class DBObjectSearch protected function RenameParam($sOldName, $sNewName) { $this->m_oSearchCondition->RenameParam($sOldName, $sNewName); - foreach($this->m_aRelatedTo as $aRelatedTo) - { - $aRelatedTo['flt']->RenameParam($sOldName, $sNewName); - } foreach($this->m_aPointingTo as $sExtKeyAttCode=>$aPointingTo) { foreach($aPointingTo as $iOperatorCode => $aFilter) @@ -414,17 +226,11 @@ class DBObjectSearch $oForeignFilter->RenameParam($sOldName, $sNewName); } } - - foreach($this->m_aParentConditions as $aParent) - { - $aParent['expression']->RenameParam($sOldName, $sNewName); - } } public function ResetCondition() { $this->m_oSearchCondition = new TrueExpression(); - $this->m_aParentConditions = array(); // ? is that usefull/enough, do I need to rebuild the list after the subqueries ? } @@ -603,20 +409,6 @@ class DBObjectSearch $this->m_aFullText[] = $sFullText; } - public function AddCondition_Parent($sAttCode, $iOperatorCode, $oExpression) - { - $oAttDef = MetaModel::GetAttributeDef($this->GetClass(), $sAttCode); - if (!$oAttDef instanceof AttributeHierarchicalKey) - { - throw new Exception("AddCondition_Parent can only be used on hierarchical keys. '$sAttCode' is not a hierarchical key."); - } - $this->m_aParentConditions[] = array( - 'attCode' => $sAttCode, - 'operator' => $iOperatorCode, - 'expression' => $oExpression, - ); - } - protected function AddToNameSpace(&$aClassAliases, &$aAliasTranslation, $bTranslateMainAlias = true) { if ($bTranslateMainAlias) @@ -733,7 +525,7 @@ class DBObjectSearch throw new CoreException("The specified tree operator $iOperatorCode is not applicable to the key '{$this->GetClass()}::$sExtKeyAttCode', which is not a HierarchicalKey"); } // Note: though it seems to be a good practice to clone the given source filter - // (as it was done and fixed an issue in MergeWith()) + // (as it was done and fixed an issue in Intersect()) // this was not implemented here because it was causing a regression (login as admin, select an org, click on any badge) // root cause: FromOQL relies on the fact that the passed filter can be modified later // NO: $oFilter = $oFilter->DeepClone(); @@ -767,18 +559,18 @@ class DBObjectSearch throw new CoreException("The specified filter (objects referencing an object of class {$this->GetClass()}) is not compatible with the key '{$sForeignClass}::$sForeignExtKeyAttCode', which is pointing to {$oAttExtKey->GetTargetClass()}"); } // Note: though it seems to be a good practice to clone the given source filter - // (as it was done and fixed an issue in MergeWith()) + // (as it was done and fixed an issue in Intersect()) // this was not implemented here because it was causing a regression (login as admin, select an org, click on any badge) // root cause: FromOQL relies on the fact that the passed filter can be modified later // NO: $oFilter = $oFilter->DeepClone(); // See also: Trac #639, and self::AddCondition_PointingTo() $aAliasTranslation = array(); - $res = $this->AddCondition_ReferencedBy_InNameSpace($oFilter, $sForeignExtKeyAttCode, $this->m_aClasses, $aAliasTranslation); + $res = $this->AddCondition_ReferencedBy_InNameSpace(DBObjectSearch, $sForeignExtKeyAttCode, $this->m_aClasses, $aAliasTranslation); $this->TransferConditionExpression($oFilter, $aAliasTranslation); return $res; } - protected function AddCondition_ReferencedBy_InNameSpace(DBObjectSearch $oFilter, $sForeignExtKeyAttCode, &$aClassAliases, &$aAliasTranslation) + protected function AddCondition_ReferencedBy_InNameSpace(DBSearch $oFilter, $sForeignExtKeyAttCode, &$aClassAliases, &$aAliasTranslation) { $sForeignClass = $oFilter->GetClass(); @@ -801,19 +593,56 @@ class DBObjectSearch } } - public function AddCondition_RelatedTo(DBObjectSearch $oFilter, $sRelCode, $iMaxDepth) + public function Intersect(DBSearch $oFilter) { - MyHelpers::CheckValueInArray('relation code', $sRelCode, MetaModel::EnumRelations()); - $this->m_aRelatedTo[] = array('flt'=>$oFilter, 'relcode'=>$sRelCode, 'maxdepth'=>$iMaxDepth); - } + if ($oFilter instanceof DBUnionSearch) + { + // Develop! + $aFilters = $oFilter->GetSearches(); + } + else + { + $aFilters = array($oFilter); + } - public function MergeWith($oFilter) - { - $oFilter = $oFilter->DeepClone(); - $aAliasTranslation = array(); - $res = $this->MergeWith_InNamespace($oFilter, $this->m_aClasses, $aAliasTranslation); - $this->TransferConditionExpression($oFilter, $aAliasTranslation); - return $res; + $aSearches = array(); + foreach ($aFilters as $oRightFilter) + { + $oLeftFilter = $this->DeepClone(); + $oRightFilter = $oRightFilter->DeepClone(); + + if ($oLeftFilter->GetClass() != $oRightFilter->GetClass()) + { + if (MetaModel::IsParentClass($oLeftFilter->GetClass(), $oRightFilter->GetClass())) + { + // Specialize $oLeftFilter + $oLeftFilter->ChangeClass($oRightFilter->GetClass()); + } + elseif (MetaModel::IsParentClass($oRightFilter->GetClass(), $oLeftFilter->GetClass())) + { + // Specialize $oRightFilter + $oRightFilter->ChangeClass($oLeftFilter->GetClass()); + } + else + { + throw new CoreException("Attempting to merge a filter of class '{$oLeftFilter->GetClass()}' with a filter of class '{$oRightFilter->GetClass()}'"); + } + } + + $aAliasTranslation = array(); + $oLeftFilter->MergeWith_InNamespace($oRightFilter, $oRet->m_aClasses, $aAliasTranslation); + $oLeftFilter->TransferConditionExpression($oRightFilter, $aAliasTranslation); + $aSearches[] = $oLeftFilter; + } + if (count($aSearches) == 1) + { + // return a DBObjectSearch + return $aSearches[0]; + } + else + { + return new DBUnionSearch($aSearches); + } } protected function MergeWith_InNamespace($oFilter, &$aClassAliases, &$aAliasTranslation) @@ -827,7 +656,6 @@ class DBObjectSearch $aAliasTranslation[$oFilter->GetClassAlias()]['*'] = $this->GetClassAlias(); $this->m_aFullText = array_merge($this->m_aFullText, $oFilter->m_aFullText); - $this->m_aRelatedTo = array_merge($this->m_aRelatedTo, $oFilter->m_aRelatedTo); foreach($oFilter->m_aPointingTo as $sExtKeyAttCode=>$aPointingTo) { @@ -873,10 +701,6 @@ class DBObjectSearch if (!array_key_exists($sForeignExtKeyAttCode, $this->m_aReferencedBy[$sRemoteClass])) return null; return $this->m_aReferencedBy[$sRemoteClass][$sForeignExtKeyAttCode]; } - public function GetCriteria_RelatedTo() - { - return $this->m_aRelatedTo; - } public function SetInternalParams($aParams) { @@ -900,11 +724,6 @@ class DBObjectSearch return $this->m_oSearchCondition->ListConstantFields(); } - public function RenderCondition() - { - return $this->m_oSearchCondition->Render($this->m_aParams, false); - } - /** * Turn the parameters (:xxx) into scalar values in order to easily * serialize a search @@ -914,106 +733,6 @@ class DBObjectSearch return $this->m_oSearchCondition->ApplyParameters(array_merge($this->m_aParams, $aArgs)); } - public function serialize($bDevelopParams = false, $aContextParams = null) - { - $sOql = $this->ToOql($bDevelopParams, $aContextParams); - return base64_encode(serialize(array($sOql, $this->m_aParams, $this->m_aModifierProperties))); - } - - static public function unserialize($sValue) - { - $aData = unserialize(base64_decode($sValue)); - $sOql = $aData[0]; - $aParams = $aData[1]; - // We've tried to use gzcompress/gzuncompress, but for some specific queries - // it was not working at all (See Trac #193) - // gzuncompress was issuing a warning "data error" and the return object was null - $oRetFilter = self::FromOQL($sOql, $aParams); - $oRetFilter->m_aModifierProperties = $aData[2]; - return $oRetFilter; - } - - // SImple BUt Structured Query Languag - SubuSQL - // - static private function Value2Expression($value) - { - $sRet = $value; - if (is_array($value)) - { - $sRet = VS_START.implode(', ', $value).VS_END; - } - else if (!is_numeric($value)) - { - $sRet = "'".addslashes($value)."'"; - } - return $sRet; - } - static private function Expression2Value($sExpr) - { - $retValue = $sExpr; - if ((substr($sExpr, 0, 1) == "'") && (substr($sExpr, -1, 1) == "'")) - { - $sNoQuotes = substr($sExpr, 1, -1); - return stripslashes($sNoQuotes); - } - if ((substr($sExpr, 0, 1) == VS_START) && (substr($sExpr, -1, 1) == VS_END)) - { - $sNoBracket = substr($sExpr, 1, -1); - $aRetValue = array(); - foreach (explode(",", $sNoBracket) as $sItem) - { - $aRetValue[] = self::Expression2Value(trim($sItem)); - } - return $aRetValue; - } - return $retValue; - } - - // Alternative to object mapping: the data are transfered directly into an array - // This is 10 times faster than creating a set of objects, and makes sense when optimization is required - /** - * @param hash $aOrderBy Array of '[.]attcode' => bAscending - */ - public function ToDataArray($aColumns = array(), $aOrderBy = array(), $aArgs = array()) - { - $sSQL = MetaModel::MakeSelectQuery($this, $aOrderBy, $aArgs); - $resQuery = CMDBSource::Query($sSQL); - if (!$resQuery) return; - - if (count($aColumns) == 0) - { - $aColumns = array_keys(MetaModel::ListAttributeDefs($this->GetClass())); - // Add the standard id (as first column) - array_unshift($aColumns, 'id'); - } - - $aQueryCols = CMDBSource::GetColumns($resQuery); - - $sClassAlias = $this->GetClassAlias(); - $aColMap = array(); - foreach ($aColumns as $sAttCode) - { - $sColName = $sClassAlias.$sAttCode; - if (in_array($sColName, $aQueryCols)) - { - $aColMap[$sAttCode] = $sColName; - } - } - - $aRes = array(); - while ($aRow = CMDBSource::FetchArray($resQuery)) - { - $aMappedRow = array(); - foreach ($aColMap as $sAttCode => $sColName) - { - $aMappedRow[$sAttCode] = $aRow[$sColName]; - } - $aRes[] = $aMappedRow; - } - CMDBSource::FreeResult($resQuery); - return $aRes; - } - public function ToOQL($bDevelopParams = false, $aContextParams = null) { // Currently unused, but could be useful later @@ -1185,58 +904,17 @@ class DBObjectSearch } } - // Create a search definition that leads to 0 result, still a valid search object - static public function FromEmptySet($sClass) + public function InitFromOqlQuery(OqlQuery $oOqlQuery, $sQuery) { - $oResultFilter = new DBObjectSearch($sClass); - $oResultFilter->m_oSearchCondition = new FalseExpression; - return $oResultFilter; - } - - static protected $m_aOQLQueries = array(); - - // Do not filter out depending on user rights - // In particular when we are currently in the process of evaluating the user rights... - static public function FromOQL_AllData($sQuery, $aParams = null) - { - $oRes = self::FromOQL($sQuery, $aParams); - $oRes->AllowAllData(); - return $oRes; - } - - static public function FromOQL($sQuery, $aParams = null) - { - if (empty($sQuery)) return null; - - // Query caching - $bOQLCacheEnabled = true; - if ($bOQLCacheEnabled && array_key_exists($sQuery, self::$m_aOQLQueries)) - { - // hit! - $oClone = self::$m_aOQLQueries[$sQuery]->DeepClone(); - if (!is_null($aParams)) - { - $oClone->m_aParams = $aParams; - } - return $oClone; - } - - $oOql = new OqlInterpreter($sQuery); - $oOqlQuery = $oOql->ParseObjectQuery(); - - $oMetaModel = new ModelReflectionRuntime(); - $oOqlQuery->Check($oMetaModel, $sQuery); // Exceptions thrown in case of issue - $sClass = $oOqlQuery->GetClass(); $sClassAlias = $oOqlQuery->GetClassAlias(); - $oResultFilter = new DBObjectSearch($sClass, $sClassAlias); $aAliases = array($sClassAlias => $sClass); // Maintain an array of filters, because the flat list is in fact referring to a tree // And this will be an easy way to dispatch the conditions - // $oResultFilter will be referenced by the other filters, or the other way around... - $aJoinItems = array($sClassAlias => $oResultFilter); + // $this will be referenced by the other filters, or the other way around... + $aJoinItems = array($sClassAlias => $this); $aJoinSpecs = $oOqlQuery->GetJoins(); if (is_array($aJoinSpecs)) @@ -1309,43 +987,652 @@ class DBObjectSearch } // Check and prepare the select information - $aSelected = array(); + $this->m_aSelectedClasses = array(); foreach ($oOqlQuery->GetSelectedClasses() as $oClassDetails) { $sClassToSelect = $oClassDetails->GetValue(); - $aSelected[$sClassToSelect] = $aAliases[$sClassToSelect]; + $this->m_aSelectedClasses[$sClassToSelect] = $aAliases[$sClassToSelect]; } - $oResultFilter->m_aClasses = $aAliases; - $oResultFilter->SetSelectedClasses($aSelected); + $this->m_aClasses = $aAliases; $oConditionTree = $oOqlQuery->GetCondition(); if ($oConditionTree instanceof Expression) { - $oResultFilter->m_oSearchCondition = $oResultFilter->OQLExpressionToCondition($sQuery, $oConditionTree, $aAliases); + $this->m_oSearchCondition = $this->OQLExpressionToCondition($sQuery, $oConditionTree, $aAliases); } - - if (!is_null($aParams)) - { - $oResultFilter->m_aParams = $aParams; - } - - if ($bOQLCacheEnabled) - { - self::$m_aOQLQueries[$sQuery] = $oResultFilter->DeepClone(); - } - - return $oResultFilter; } - public function toxpath() + //////////////////////////////////////////////////////////////////////////// + // + // Construction of the SQL queries + // + //////////////////////////////////////////////////////////////////////////// + + public function MakeDeleteQuery($aArgs = array()) { - // #@# a voir... + $aModifierProperties = MetaModel::MakeModifierProperties($this); + $oBuild = new QueryBuilderContext($this, $aModifierProperties); + $oSQLQuery = $this->MakeSQLObjectQuery($oBuild, null, array()); + $oSQLQuery->SetCondition($oBuild->m_oQBExpressions->GetCondition()); + $oSQLQuery->SetSelect($oBuild->m_oQBExpressions->GetSelect()); + $aScalarArgs = array_merge(MetaModel::PrepareQueryArguments($aArgs), $this->GetInternalParams()); + return $oSQLQuery->RenderDelete($aScalarArgs); } - static public function fromxpath() + + public function MakeUpdateQuery($aValues, $aArgs = array()) { - // #@# a voir... + // $aValues is an array of $sAttCode => $value + $aModifierProperties = MetaModel::MakeModifierProperties($this); + $oBuild = new QueryBuilderContext($this, $aModifierProperties); + $oSQLQuery = $this->MakeSQLObjectQuery($oBuild, null, $aValues); + $oSQLQuery->SetCondition($oBuild->m_oQBExpressions->GetCondition()); + $oSQLQuery->SetSelect($oBuild->m_oQBExpressions->GetSelect()); + $aScalarArgs = array_merge(MetaModel::PrepareQueryArguments($aArgs), $this->GetInternalParams()); + return $oSQLQuery->RenderUpdate($aScalarArgs); } + + public function MakeSQLQuery($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr = null, $aSelectedClasses = null) + { + $oBuild = new QueryBuilderContext($this, $aModifierProperties, $aGroupByExpr, $aSelectedClasses); + + $oSQLQuery = $this->MakeSQLObjectQuery($oBuild, $aAttToLoad, array()); + $oSQLQuery->SetCondition($oBuild->m_oQBExpressions->GetCondition()); + if ($aGroupByExpr) + { + $aCols = $oBuild->m_oQBExpressions->GetGroupBy(); + $oSQLQuery->SetGroupBy($aCols); + $oSQLQuery->SetSelect($aCols); + } + else + { + $oSQLQuery->SetSelect($oBuild->m_oQBExpressions->GetSelect()); + } + + if (self::$m_bOptimizeQueries) + { + if ($bGetCount) + { + // Simplify the query if just getting the count + $oSQLQuery->SetSelect(array()); + } + $oBuild->m_oQBExpressions->GetMandatoryTables($aMandatoryTables); + $oSQLQuery->OptimizeJoins($aMandatoryTables); + } + + return $oSQLQuery; + } + + + protected function MakeSQLObjectQuery(&$oBuild, $aAttToLoad = null, $aValues = array()) + { + // 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) + $sClass = $this->GetFirstJoinedClass(); + $sClassAlias = $this->GetFirstJoinedClassAlias(); + + $bIsOnQueriedClass = array_key_exists($sClassAlias, $oBuild->GetRootFilter()->GetSelectedClasses()); + + self::DbgTrace("Entering: ".$this->ToOQL().", ".($bIsOnQueriedClass ? "MAIN" : "SECONDARY")); + + $sRootClass = MetaModel::GetRootClass($sClass); + $sKeyField = MetaModel::DBGetKey($sClass); + + if ($bIsOnQueriedClass) + { + // default to the whole list of attributes + the very std id/finalclass + $oBuild->m_oQBExpressions->AddSelect($sClassAlias.'id', new FieldExpression('id', $sClassAlias)); + if (is_null($aAttToLoad) || !array_key_exists($sClassAlias, $aAttToLoad)) + { + $sSelectedClass = $oBuild->GetSelectedClass($sClassAlias); + $aAttList = MetaModel::ListAttributeDefs($sSelectedClass); + } + else + { + $aAttList = $aAttToLoad[$sClassAlias]; + } + foreach ($aAttList as $sAttCode => $oAttDef) + { + if (!$oAttDef->IsScalar()) continue; + // keep because it can be used for sorting - if (!$oAttDef->LoadInObject()) continue; + + foreach ($oAttDef->GetSQLExpressions() as $sColId => $sSQLExpr) + { + $oBuild->m_oQBExpressions->AddSelect($sClassAlias.$sAttCode.$sColId, new FieldExpression($sAttCode.$sColId, $sClassAlias)); + } + } + + // Transform the full text condition into additional condition expression + $aFullText = $this->GetCriteria_FullText(); + if (count($aFullText) > 0) + { + $aFullTextFields = array(); + foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) + { + if (!$oAttDef->IsScalar()) continue; + if ($oAttDef->IsExternalKey()) continue; + $aFullTextFields[] = new FieldExpression($sAttCode, $sClassAlias); + } + $oTextFields = new CharConcatWSExpression(' ', $aFullTextFields); + + foreach($aFullText as $sFTNeedle) + { + $oNewCond = new BinaryExpression($oTextFields, 'LIKE', new ScalarExpression("%$sFTNeedle%")); + $oBuild->m_oQBExpressions->AddCondition($oNewCond); + } + } + } +//echo "

oQBExpr ".__LINE__.":

\n".print_r($oBuild->m_oQBExpressions, true)."

\n"; + $aExpectedAtts = array(); // array of (attcode => fieldexpression) +//echo "

".__LINE__.": GetUnresolvedFields($sClassAlias, ...)

\n"; + $oBuild->m_oQBExpressions->GetUnresolvedFields($sClassAlias, $aExpectedAtts); + + // Compute a clear view of required joins (from the current class) + // Build the list of external keys: + // -> ext keys required by an explicit join + // -> ext keys mentionned in a 'pointing to' condition + // -> ext keys required for an external field + // -> ext keys required for a friendly name + // + $aExtKeys = array(); // array of sTableClass => array of (sAttCode (keys) => array of (sAttCode (fields)=> oAttDef)) + // + // Optimization: could be partially computed once for all (cached) ? + // + + if ($bIsOnQueriedClass) + { + // Get all Ext keys for the queried class (??) + foreach(MetaModel::GetKeysList($sClass) as $sKeyAttCode) + { + $sKeyTableClass = MetaModel::GetAttributeOrigin($sClass, $sKeyAttCode); + $aExtKeys[$sKeyTableClass][$sKeyAttCode] = array(); + } + } + // Get all Ext keys used by the filter + foreach ($this->GetCriteria_PointingTo() as $sKeyAttCode => $aPointingTo) + { + if (array_key_exists(TREE_OPERATOR_EQUALS, $aPointingTo)) + { + $sKeyTableClass = MetaModel::GetAttributeOrigin($sClass, $sKeyAttCode); + $aExtKeys[$sKeyTableClass][$sKeyAttCode] = array(); + } + } + + $aFNJoinAlias = array(); // array of (subclass => alias) + if (array_key_exists('friendlyname', $aExpectedAtts)) + { + // To optimize: detect a restriction on child classes in the condition expression + // e.g. SELECT FunctionalCI WHERE finalclass IN ('Server', 'VirtualMachine') + $oNameExpression = self::GetExtendedNameExpression($sClass); + + $aNameFields = array(); + $oNameExpression->GetUnresolvedFields('', $aNameFields); + $aTranslateNameFields = array(); + foreach($aNameFields as $sSubClass => $aFields) + { + foreach($aFields as $sAttCode => $oField) + { + $oAttDef = MetaModel::GetAttributeDef($sSubClass, $sAttCode); + if ($oAttDef->IsExternalKey()) + { + $sClassOfAttribute = MetaModel::GetAttributeOrigin($sSubClass, $sAttCode); + $aExtKeys[$sClassOfAttribute][$sAttCode] = array(); + } + elseif ($oAttDef->IsExternalField() || ($oAttDef instanceof AttributeFriendlyName)) + { + $sKeyAttCode = $oAttDef->GetKeyAttCode(); + $sClassOfAttribute = MetaModel::GetAttributeOrigin($sSubClass, $sKeyAttCode); + $aExtKeys[$sClassOfAttribute][$sKeyAttCode][$sAttCode] = $oAttDef; + } + else + { + $sClassOfAttribute = MetaModel::GetAttributeOrigin($sSubClass, $sAttCode); + } + + if (MetaModel::IsParentClass($sClassOfAttribute, $sClass)) + { + // The attribute is part of the standard query + // + $sAliasForAttribute = $sClassAlias; + } + else + { + // The attribute will be available from an additional outer join + // For each subclass (table) one single join is enough + // + if (!array_key_exists($sClassOfAttribute, $aFNJoinAlias)) + { + $sAliasForAttribute = $oBuild->GenerateClassAlias($sClassAlias.'_fn_'.$sClassOfAttribute, $sClassOfAttribute); + $aFNJoinAlias[$sClassOfAttribute] = $sAliasForAttribute; + } + else + { + $sAliasForAttribute = $aFNJoinAlias[$sClassOfAttribute]; + } + } + + $aTranslateNameFields[$sSubClass][$sAttCode] = new FieldExpression($sAttCode, $sAliasForAttribute); + } + } + $oNameExpression = $oNameExpression->Translate($aTranslateNameFields, false); + + $aTranslateNow = array(); + $aTranslateNow[$sClassAlias]['friendlyname'] = $oNameExpression; + $oBuild->m_oQBExpressions->Translate($aTranslateNow, false); + } + + // Add the ext fields used in the select (eventually adds an external key) + foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) + { + if ($oAttDef->IsExternalField() || ($oAttDef instanceof AttributeFriendlyName)) + { + if (array_key_exists($sAttCode, $aExpectedAtts)) + { + $sKeyAttCode = $oAttDef->GetKeyAttCode(); + if ($sKeyAttCode != 'id') + { + // Add the external attribute + $sKeyTableClass = MetaModel::GetAttributeOrigin($sClass, $sKeyAttCode); + $aExtKeys[$sKeyTableClass][$sKeyAttCode][$sAttCode] = $oAttDef; + } + } + } + } + + // First query built upon on the leaf (ie current) class + // + self::DbgTrace("Main (=leaf) class, call MakeSQLObjectQuerySingleTable()"); + if (MetaModel::HasTable($sClass)) + { + $oSelectBase = $this->MakeSQLObjectQuerySingleTable($oBuild, $sClass, $aExtKeys, $aValues); + } + else + { + $oSelectBase = null; + + // As the join will not filter on the expected classes, we have to specify it explicitely + $sExpectedClasses = implode("', '", MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL)); + $oFinalClassRestriction = Expression::FromOQL("`$sClassAlias`.finalclass IN ('$sExpectedClasses')"); + $oBuild->m_oQBExpressions->AddCondition($oFinalClassRestriction); + } + + // Then we join the queries of the eventual parent classes (compound model) + foreach(MetaModel::EnumParentClasses($sClass) as $sParentClass) + { + if (!MetaModel::HasTable($sParentClass)) continue; + + self::DbgTrace("Parent class: $sParentClass... let's call MakeSQLObjectQuerySingleTable()"); + $oSelectParentTable = $this->MakeSQLObjectQuerySingleTable($oBuild, $sParentClass, $aExtKeys, $aValues); + if (is_null($oSelectBase)) + { + $oSelectBase = $oSelectParentTable; + } + else + { + $oSelectBase->AddInnerJoin($oSelectParentTable, $sKeyField, MetaModel::DBGetKey($sParentClass)); + } + } + + // Filter on objects referencing me + foreach ($this->GetCriteria_ReferencedBy() as $sForeignClass => $aKeysAndFilters) + { + foreach ($aKeysAndFilters as $sForeignKeyAttCode => $oForeignFilter) + { + $oForeignKeyAttDef = MetaModel::GetAttributeDef($sForeignClass, $sForeignKeyAttCode); + + self::DbgTrace("Referenced by foreign key: $sForeignKeyAttCode... let's call MakeSQLObjectQuery()"); + //self::DbgTrace($oForeignFilter); + //self::DbgTrace($oForeignFilter->ToOQL()); + //self::DbgTrace($oSelectForeign); + //self::DbgTrace($oSelectForeign->RenderSelect(array())); + + $sForeignClassAlias = $oForeignFilter->GetFirstJoinedClassAlias(); + $oBuild->m_oQBExpressions->PushJoinField(new FieldExpression($sForeignKeyAttCode, $sForeignClassAlias)); + + if ($oForeignKeyAttDef instanceof AttributeObjectKey) + { + $sClassAttCode = $oForeignKeyAttDef->Get('class_attcode'); + + // Add the condition: `$sForeignClassAlias`.$sClassAttCode IN (subclasses of $sClass') + $oClassListExpr = ListExpression::FromScalars(MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL)); + $oClassExpr = new FieldExpression($sClassAttCode, $sForeignClassAlias); + $oClassRestriction = new BinaryExpression($oClassExpr, 'IN', $oClassListExpr); + $oBuild->m_oQBExpressions->AddCondition($oClassRestriction); + } + + $oSelectForeign = $oForeignFilter->MakeSQLObjectQuery($oBuild, $aAttToLoad); + + $oJoinExpr = $oBuild->m_oQBExpressions->PopJoinField(); + $sForeignKeyTable = $oJoinExpr->GetParent(); + $sForeignKeyColumn = $oJoinExpr->GetName(); + $oSelectBase->AddInnerJoin($oSelectForeign, $sKeyField, $sForeignKeyColumn, $sForeignKeyTable); + } + } + + // Additional JOINS for Friendly names + // + foreach ($aFNJoinAlias as $sSubClass => $sSubClassAlias) + { + $oSubClassFilter = new DBObjectSearch($sSubClass, $sSubClassAlias); + $oSelectFN = $oSubClassFilter->MakeSQLObjectQuerySingleTable($oBuild, $sSubClass, $aExtKeys, array()); + $oSelectBase->AddLeftJoin($oSelectFN, $sKeyField, MetaModel::DBGetKey($sSubClass)); + } + + // That's all... cross fingers and we'll get some working query + + //MyHelpers::var_dump_html($oSelectBase, true); + //MyHelpers::var_dump_html($oSelectBase->RenderSelect(), true); + if (self::$m_bDebugQuery) $oSelectBase->DisplayHtml(); + return $oSelectBase; + } + + protected function MakeSQLObjectQuerySingleTable(&$oBuild, $sTableClass, $aExtKeys, $aValues) + { + // $aExtKeys is an array of sTableClass => array of (sAttCode (keys) => array of sAttCode (fields)) +//echo "MakeSQLObjectQuery($sTableClass)-liste des clefs externes($sTableClass):
".print_r($aExtKeys, true)."

\n"; + + // Prepare the query for a single table (compound objects) + // Ignores the items (attributes/filters) that are not on the target table + // Perform an (inner or left) join for every external key (and specify the expected fields) + // + // Returns an SQLQuery + // + $sTargetClass = $this->GetFirstJoinedClass(); + $sTargetAlias = $this->GetFirstJoinedClassAlias(); + $sTable = MetaModel::DBGetTable($sTableClass); + $sTableAlias = $oBuild->GenerateTableAlias($sTargetAlias.'_'.$sTable, $sTable); + + $aTranslation = array(); + $aExpectedAtts = array(); + $oBuild->m_oQBExpressions->GetUnresolvedFields($sTargetAlias, $aExpectedAtts); + + $bIsOnQueriedClass = array_key_exists($sTargetAlias, $oBuild->GetRootFilter()->GetSelectedClasses()); + + self::DbgTrace("Entering: tableclass=$sTableClass, filter=".$this->ToOQL().", ".($bIsOnQueriedClass ? "MAIN" : "SECONDARY")); + + // 1 - SELECT and UPDATE + // + // Note: no need for any values nor fields for foreign Classes (ie not the queried Class) + // + $aUpdateValues = array(); + + + // 1/a - Get the key and friendly name + // + // We need one pkey to be the key, let's take the first one available + $oSelectedIdField = null; + $oIdField = new FieldExpressionResolved(MetaModel::DBGetKey($sTableClass), $sTableAlias); + $aTranslation[$sTargetAlias]['id'] = $oIdField; + + if ($bIsOnQueriedClass) + { + // Add this field to the list of queried fields (required for the COUNT to work fine) + $oSelectedIdField = $oIdField; + } + + // 1/b - Get the other attributes + // + foreach(MetaModel::ListAttributeDefs($sTableClass) as $sAttCode=>$oAttDef) + { + // Skip this attribute if not defined in this table + if (MetaModel::GetAttributeOrigin($sTargetClass, $sAttCode) != $sTableClass) continue; + + // Skip this attribute if not made of SQL columns + if (count($oAttDef->GetSQLExpressions()) == 0) continue; + + // Update... + // + if ($bIsOnQueriedClass && array_key_exists($sAttCode, $aValues)) + { + assert ($oAttDef->IsDirectField()); + foreach ($oAttDef->GetSQLValues($aValues[$sAttCode]) as $sColumn => $sValue) + { + $aUpdateValues[$sColumn] = $sValue; + } + } + } + + // 2 - The SQL query, for this table only + // + $oSelectBase = new SQLObjectQuery($sTable, $sTableAlias, array(), $bIsOnQueriedClass, $aUpdateValues, $oSelectedIdField); + + // 3 - Resolve expected expressions (translation table: alias.attcode => table.column) + // + foreach(MetaModel::ListAttributeDefs($sTableClass) as $sAttCode=>$oAttDef) + { + // Skip this attribute if not defined in this table + if (MetaModel::GetAttributeOrigin($sTargetClass, $sAttCode) != $sTableClass) continue; + + // Select... + // + if ($oAttDef->IsExternalField()) + { + // skip, this will be handled in the joined tables (done hereabove) + } + else + { +//echo "

MakeSQLObjectQuerySingleTable: Field $sAttCode is part of the table $sTable (named: $sTableAlias)

"; + // standard field, or external key + // add it to the output + foreach ($oAttDef->GetSQLExpressions() as $sColId => $sSQLExpr) + { + if (array_key_exists($sAttCode.$sColId, $aExpectedAtts)) + { + $oFieldSQLExp = new FieldExpressionResolved($sSQLExpr, $sTableAlias); + foreach (MetaModel::EnumPlugins('iQueryModifier') as $sPluginClass => $oQueryModifier) + { + $oFieldSQLExp = $oQueryModifier->GetFieldExpression($oBuild, $sTargetClass, $sAttCode, $sColId, $oFieldSQLExp, $oSelectBase); + } + $aTranslation[$sTargetAlias][$sAttCode.$sColId] = $oFieldSQLExp; + } + } + } + } + +//echo "MakeSQLObjectQuery- Classe $sTableClass
\n"; + // 4 - The external keys -> joins... + // + $aAllPointingTo = $this->GetCriteria_PointingTo(); + + if (array_key_exists($sTableClass, $aExtKeys)) + { + foreach ($aExtKeys[$sTableClass] as $sKeyAttCode => $aExtFields) + { + $oKeyAttDef = MetaModel::GetAttributeDef($sTableClass, $sKeyAttCode); + + $aPointingTo = $this->GetCriteria_PointingTo($sKeyAttCode); +//echo "MakeSQLObjectQuery-Cle '$sKeyAttCode'
\n"; + if (!array_key_exists(TREE_OPERATOR_EQUALS, $aPointingTo)) + { +//echo "MakeSQLObjectQuery-Ajoutons l'operateur TREE_OPERATOR_EQUALS pour $sKeyAttCode
\n"; + // The join was not explicitely defined in the filter, + // we need to do it now + $sKeyClass = $oKeyAttDef->GetTargetClass(); + $sKeyClassAlias = $oBuild->GenerateClassAlias($sKeyClass.'_'.$sKeyAttCode, $sKeyClass); + $oExtFilter = new DBObjectSearch($sKeyClass, $sKeyClassAlias); + + $aAllPointingTo[$sKeyAttCode][TREE_OPERATOR_EQUALS][$sKeyClassAlias] = $oExtFilter; + } + } + } +//echo "MakeSQLObjectQuery-liste des clefs de jointure:
".print_r(array_keys($aAllPointingTo), true)."

\n"; + + foreach ($aAllPointingTo as $sKeyAttCode => $aPointingTo) + { + foreach($aPointingTo as $iOperatorCode => $aFilter) + { + foreach($aFilter as $oExtFilter) + { + if (!MetaModel::IsValidAttCode($sTableClass, $sKeyAttCode)) continue; // Not defined in the class, skip it + // The aliases should not conflict because normalization occured while building the filter + $oKeyAttDef = MetaModel::GetAttributeDef($sTableClass, $sKeyAttCode); + $sKeyClass = $oExtFilter->GetFirstJoinedClass(); + $sKeyClassAlias = $oExtFilter->GetFirstJoinedClassAlias(); + +//echo "MakeSQLObjectQuery-$sTableClass::$sKeyAttCode Foreach PointingTo($iOperatorCode) $sKeyClass (alias:$sKeyClassAlias)
\n"; + + // Note: there is no search condition in $oExtFilter, because normalization did merge the condition onto the top of the filter tree + +//echo "MakeSQLObjectQuery-array_key_exists($sTableClass, \$aExtKeys)
\n"; + if ($iOperatorCode == TREE_OPERATOR_EQUALS) + { + if (array_key_exists($sTableClass, $aExtKeys) && array_key_exists($sKeyAttCode, $aExtKeys[$sTableClass])) + { + // Specify expected attributes for the target class query + // ... and use the current alias ! + $aTranslateNow = array(); // Translation for external fields - must be performed before the join is done (recursion...) + foreach($aExtKeys[$sTableClass][$sKeyAttCode] as $sAttCode => $oAtt) + { +//echo "MakeSQLObjectQuery aExtKeys[$sTableClass][$sKeyAttCode] => $sAttCode-oAtt:
".print_r($oAtt, true)."

\n"; + if ($oAtt instanceof AttributeFriendlyName) + { + // Note: for a given ext key, there is one single attribute "friendly name" + $aTranslateNow[$sTargetAlias][$sAttCode] = new FieldExpression('friendlyname', $sKeyClassAlias); +//echo "

aTranslateNow[$sTargetAlias][$sAttCode] = new FieldExpression('friendlyname', $sKeyClassAlias);

\n"; + } + else + { + $sExtAttCode = $oAtt->GetExtAttCode(); + // Translate mainclass.extfield => remoteclassalias.remotefieldcode + $oRemoteAttDef = MetaModel::GetAttributeDef($sKeyClass, $sExtAttCode); + foreach ($oRemoteAttDef->GetSQLExpressions() as $sColID => $sRemoteAttExpr) + { + $aTranslateNow[$sTargetAlias][$sAttCode.$sColId] = new FieldExpression($sExtAttCode, $sKeyClassAlias); +//echo "

aTranslateNow[$sTargetAlias][$sAttCode.$sColId] = new FieldExpression($sExtAttCode, $sKeyClassAlias);

\n"; + } +//echo "

ExtAttr2: $sTargetAlias.$sAttCode to $sKeyClassAlias.$sRemoteAttExpr (class: $sKeyClass)

\n"; + } + } + + if ($oKeyAttDef instanceof AttributeObjectKey) + { + // Add the condition: `$sTargetAlias`.$sClassAttCode IN (subclasses of $sKeyClass') + $sClassAttCode = $oKeyAttDef->Get('class_attcode'); + $oClassAttDef = MetaModel::GetAttributeDef($sTargetClass, $sClassAttCode); + foreach ($oClassAttDef->GetSQLExpressions() as $sColID => $sSQLExpr) + { + $aTranslateNow[$sTargetAlias][$sClassAttCode.$sColId] = new FieldExpressionResolved($sSQLExpr, $sTableAlias); + } + + $oClassListExpr = ListExpression::FromScalars(MetaModel::EnumChildClasses($sKeyClass, ENUM_CHILD_CLASSES_ALL)); + $oClassExpr = new FieldExpression($sClassAttCode, $sTargetAlias); + $oClassRestriction = new BinaryExpression($oClassExpr, 'IN', $oClassListExpr); + $oBuild->m_oQBExpressions->AddCondition($oClassRestriction); + } + + // Translate prior to recursing + // +//echo "

oQBExpr ".__LINE__.":

\n".print_r($oBuild->m_oQBExpressions, true)."\n".print_r($aTranslateNow, true)."

\n"; + $oBuild->m_oQBExpressions->Translate($aTranslateNow, false); +//echo "

oQBExpr ".__LINE__.":

\n".print_r($oBuild->m_oQBExpressions, true)."

\n"; + +//echo "

External key $sKeyAttCode (class: $sKeyClass), call MakeSQLObjectQuery()/p>\n"; + self::DbgTrace("External key $sKeyAttCode (class: $sKeyClass), call MakeSQLObjectQuery()"); + $oBuild->m_oQBExpressions->PushJoinField(new FieldExpression('id', $sKeyClassAlias)); + +//echo "

Recursive MakeSQLObjectQuery ".__LINE__.":

\n".print_r($oBuild->GetRootFilter()->GetSelectedClasses(), true)."

\n"; + $oSelectExtKey = $oExtFilter->MakeSQLObjectQuery($oBuild); + + $oJoinExpr = $oBuild->m_oQBExpressions->PopJoinField(); + $sExternalKeyTable = $oJoinExpr->GetParent(); + $sExternalKeyField = $oJoinExpr->GetName(); + + $aCols = $oKeyAttDef->GetSQLExpressions(); // Workaround a PHP bug: sometimes issuing a Notice if invoking current(somefunc()) + $sLocalKeyField = current($aCols); // get the first column for an external key + + self::DbgTrace("External key $sKeyAttCode, Join on $sLocalKeyField = $sExternalKeyField"); + if ($oKeyAttDef->IsNullAllowed()) + { + $oSelectBase->AddLeftJoin($oSelectExtKey, $sLocalKeyField, $sExternalKeyField, $sExternalKeyTable); + } + else + { + $oSelectBase->AddInnerJoin($oSelectExtKey, $sLocalKeyField, $sExternalKeyField, $sExternalKeyTable); + } + } + } + elseif(MetaModel::GetAttributeOrigin($sKeyClass, $sKeyAttCode) == $sTableClass) + { + $oBuild->m_oQBExpressions->PushJoinField(new FieldExpression($sKeyAttCode, $sKeyClassAlias)); + $oSelectExtKey = $oExtFilter->MakeSQLObjectQuery($oBuild); + $oJoinExpr = $oBuild->m_oQBExpressions->PopJoinField(); + $sExternalKeyTable = $oJoinExpr->GetParent(); + $sExternalKeyField = $oJoinExpr->GetName(); + $sLeftIndex = $sExternalKeyField.'_left'; // TODO use GetSQLLeft() + $sRightIndex = $sExternalKeyField.'_right'; // TODO use GetSQLRight() + + $LocalKeyLeft = $oKeyAttDef->GetSQLLeft(); + $LocalKeyRight = $oKeyAttDef->GetSQLRight(); + + $oSelectBase->AddInnerJoinTree($oSelectExtKey, $LocalKeyLeft, $LocalKeyRight, $sLeftIndex, $sRightIndex, $sExternalKeyTable, $iOperatorCode); + } + } + } + } + + // Translate the selected columns + // +//echo "

oQBExpr ".__LINE__.":

\n".print_r($oBuild->m_oQBExpressions, true)."

\n"; + $oBuild->m_oQBExpressions->Translate($aTranslation, false); +//echo "

oQBExpr ".__LINE__.":

\n".print_r($oBuild->m_oQBExpressions, true)."

\n"; + + //MyHelpers::var_dump_html($oSelectBase->RenderSelect()); + return $oSelectBase; + } + + /** + * Get the friendly name for the class and its subclasses (if finalclass = 'subclass' ...) + * Simplifies the final expression by grouping classes having the same name expression + * Used when querying a parent class + */ + static protected function GetExtendedNameExpression($sClass) + { + // 1st step - get all of the required expressions (instantiable classes) + // and group them using their OQL representation + // + $aFNExpressions = array(); // signature => array('expression' => oExp, 'classes' => array of classes) + foreach (MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL) as $sSubClass) + { + if (($sSubClass != $sClass) && MetaModel::IsAbstract($sSubClass)) continue; + + $oSubClassName = MetaModel::GetNameExpression($sSubClass); + $sSignature = $oSubClassName->Render(); + if (!array_key_exists($sSignature, $aFNExpressions)) + { + $aFNExpressions[$sSignature] = array( + 'expression' => $oSubClassName, + 'classes' => array(), + ); + } + $aFNExpressions[$sSignature]['classes'][] = $sSubClass; + } + + // 2nd step - build the final name expression depending on the finalclass + // + if (count($aFNExpressions) == 1) + { + $aExpData = reset($aFNExpressions); + $oNameExpression = $aExpData['expression']; + } + else + { + $oNameExpression = null; + foreach ($aFNExpressions as $sSignature => $aExpData) + { + $oClassListExpr = ListExpression::FromScalars($aExpData['classes']); + $oClassExpr = new FieldExpression('finalclass', $sClass); + $oClassInList = new BinaryExpression($oClassExpr, 'IN', $oClassListExpr); + + if (is_null($oNameExpression)) + { + $oNameExpression = $aExpData['expression']; + } + else + { + $oNameExpression = new FunctionExpression('IF', array($oClassInList, $aExpData['expression'], $oNameExpression)); + } + } + } + return $oNameExpression; + } + } - - -?> diff --git a/core/dbobjectset.class.php b/core/dbobjectset.class.php index 11b338e92..25bce6fad 100644 --- a/core/dbobjectset.class.php +++ b/core/dbobjectset.class.php @@ -1,5 +1,5 @@ .]attcode' => bAscending * @param hash $aArgs Values to substitute for the search/query parameters (if any). Format: param_name => value * @param hash $aExtendedDataSpec * @param int $iLimitCount Maximum number of rows to load (i.e. equivalent to MySQL's LIMIT start, count) * @param int $iLimitStart Index of the first row to load (i.e. equivalent to MySQL's LIMIT start, count) */ - public function __construct(DBObjectSearch $oFilter, $aOrderBy = array(), $aArgs = array(), $aExtendedDataSpec = null, $iLimitCount = 0, $iLimitStart = 0) + public function __construct(DBSearch $oFilter, $aOrderBy = array(), $aArgs = array(), $aExtendedDataSpec = null, $iLimitCount = 0, $iLimitStart = 0) { $this->m_oFilter = $oFilter->DeepClone(); $this->m_aAddedIds = array(); @@ -86,7 +86,7 @@ class DBObjectSet $sRet = ''; $this->Rewind(); $sRet .= "Set (".$this->m_oFilter->ToOQL().")
\n"; - $sRet .= "Query:
".MetaModel::MakeSelectQuery($this->m_oFilter, array()).")
\n"; + $sRet .= "Query:
".$this->m_oFilter->MakeSelectQuery().")
\n"; $sRet .= $this->Count()." records
\n"; if ($this->Count() > 0) @@ -353,7 +353,7 @@ class DBObjectSet } /** - * Retrieve the DBObjectSearch corresponding to the objects present in this set + * Retrieve the DBSearch corresponding to the objects present in this set * * Limitation: * This method will NOT work for sets with several columns (i.e. several objects per row) @@ -513,11 +513,11 @@ class DBObjectSet if ($this->m_iLimitCount > 0) { - $sSQL = MetaModel::MakeSelectQuery($this->m_oFilter, $this->GetRealSortOrder(), $this->m_aArgs, $this->m_aAttToLoad, $this->m_aExtendedDataSpec, $this->m_iLimitCount, $this->m_iLimitStart); + $sSQL = $this->m_oFilter->MakeSelectQuery($this->GetRealSortOrder(), $this->m_aArgs, $this->m_aAttToLoad, $this->m_aExtendedDataSpec, $this->m_iLimitCount, $this->m_iLimitStart); } else { - $sSQL = MetaModel::MakeSelectQuery($this->m_oFilter, $this->GetRealSortOrder(), $this->m_aArgs, $this->m_aAttToLoad, $this->m_aExtendedDataSpec); + $sSQL = $this->m_oFilter->MakeSelectQuery($this->GetRealSortOrder(), $this->m_aArgs, $this->m_aAttToLoad, $this->m_aExtendedDataSpec); } if (is_object($this->m_oSQLResult)) @@ -549,7 +549,7 @@ class DBObjectSet { if (is_null($this->m_iNumTotalDBRows)) { - $sSQL = MetaModel::MakeSelectQuery($this->m_oFilter, array(), $this->m_aArgs, null, null, 0, 0, true); + $sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, 0, 0, true); $resQuery = CMDBSource::Query($sSQL); if (!$resQuery) return 0; diff --git a/core/dbsearch.class.php b/core/dbsearch.class.php new file mode 100644 index 000000000..3361702d1 --- /dev/null +++ b/core/dbsearch.class.php @@ -0,0 +1,724 @@ + + + +require_once('dbobjectsearch.class.php'); +require_once('dbunionsearch.class.php'); + + +define('TREE_OPERATOR_EQUALS', 0); +define('TREE_OPERATOR_BELOW', 1); +define('TREE_OPERATOR_BELOW_STRICT', 2); +define('TREE_OPERATOR_NOT_BELOW', 3); +define('TREE_OPERATOR_NOT_BELOW_STRICT', 4); +define('TREE_OPERATOR_ABOVE', 5); +define('TREE_OPERATOR_ABOVE_STRICT', 6); +define('TREE_OPERATOR_NOT_ABOVE', 7); +define('TREE_OPERATOR_NOT_ABOVE_STRICT', 8); + +/** + * An object search + * + * Note: in the ancient times of iTop, a search was named after DBObjectSearch. + * When the UNION has been introduced, it has been decided to: + * - declare a hierarchy of search classes, with two leafs : + * - one class to cope with a single query (A JOIN B... WHERE...) + * - and the other to cope with several queries (query1 UNION query2) + * - in order to preserve forward/backward compatibility of the existing modules + * - keep the name of DBObjectSearch even if it a little bit confusing + * - do not provide a type-hint for function parameters defined in the modules + * - leave the statements DBObjectSearch::FromOQL in the modules, though DBSearch is more relevant + * + * @copyright Copyright (C) 2015 Combodo SARL + * @license http://opensource.org/licenses/AGPL-3.0 + */ + +abstract class DBSearch +{ + protected $m_bDataFiltered = false; + protected $m_aModifierProperties = array(); + + // By default, some information may be hidden to the current user + // But it may happen that we need to disable that feature + protected $m_bAllowAllData = false; + + public function __construct() + { + } + + /** + * Perform a deep clone (as opposed to "clone" which does copy a reference to the underlying objects) + **/ + public function DeepClone() + { + return unserialize(serialize($this)); // Beware this serializes/unserializes the search and its parameters as well + } + + public function AllowAllData() {$this->m_bAllowAllData = true;} + public function IsAllDataAllowed() {return $this->m_bAllowAllData;} + public function IsDataFiltered() {return $this->m_bDataFiltered; } + public function SetDataFiltered() {$this->m_bDataFiltered = true;} + + public function SetModifierProperty($sPluginClass, $sProperty, $value) + { + $this->m_aModifierProperties[$sPluginClass][$sProperty] = $value; + } + + public function GetModifierProperties($sPluginClass) + { + if (array_key_exists($sPluginClass, $this->m_aModifierProperties)) + { + return $this->m_aModifierProperties[$sPluginClass]; + } + else + { + return array(); + } + } + + abstract public function GetClassName($sAlias); + abstract public function GetClass(); + abstract public function GetClassAlias(); + + /** + * Change the class (only subclasses are supported as of now, because the conditions must fit the new class) + * Defaults to the first selected class (most of the time it is also the first joined class + */ + abstract public function ChangeClass($sNewClass, $sAlias = null); + abstract public function GetSelectedClasses(); + + abstract public function IsAny(); + + public function Describe(){return 'deprecated - use ToOQL() instead';} + public function DescribeConditionPointTo($sExtKeyAttCode, $aPointingTo){return 'deprecated - use ToOQL() instead';} + public function DescribeConditionRefBy($sForeignClass, $sForeignExtKeyAttCode){return 'deprecated - use ToOQL() instead';} + public function DescribeConditionRelTo($aRelInfo){return 'deprecated - use ToOQL() instead';} + public function DescribeConditions(){return 'deprecated - use ToOQL() instead';} + public function __DescribeHTML(){return 'deprecated - use ToOQL() instead';} + + abstract public function ResetCondition(); + abstract public function MergeConditionExpression($oExpression); + abstract public function AddConditionExpression($oExpression); + abstract public function AddNameCondition($sName); + abstract public function AddCondition($sFilterCode, $value, $sOpCode = null); + /** + * Specify a condition on external keys or link sets + * @param sAttSpec Can be either an attribute code or extkey->[sAttSpec] or linkset->[sAttSpec] and so on, recursively + * Example: infra_list->ci_id->location_id->country + * @param value The value to match (can be an array => IN(val1, val2...) + * @return void + */ + abstract public function AddConditionAdvanced($sAttSpec, $value); + abstract public function AddCondition_FullText($sFullText); + + abstract public function AddCondition_PointingTo(DBObjectSearch $oFilter, $sExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS); + abstract public function AddCondition_ReferencedBy(DBObjectSearch $oFilter, $sForeignExtKeyAttCode); + abstract public function Intersect(DBSearch $oFilter); + + abstract public function SetInternalParams($aParams); + abstract public function GetInternalParams(); + abstract public function GetQueryParams(); + abstract public function ListConstantFields(); + + /** + * Turn the parameters (:xxx) into scalar values in order to easily + * serialize a search + */ + abstract public function ApplyParameters($aArgs); + + public function serialize($bDevelopParams = false, $aContextParams = null) + { + $sOql = $this->ToOql($bDevelopParams, $aContextParams); + return base64_encode(serialize(array($sOql, $this->GetInternalParams(), $this->m_aModifierProperties))); + } + + static public function unserialize($sValue) + { + $aData = unserialize(base64_decode($sValue)); + $sOql = $aData[0]; + $aParams = $aData[1]; + // We've tried to use gzcompress/gzuncompress, but for some specific queries + // it was not working at all (See Trac #193) + // gzuncompress was issuing a warning "data error" and the return object was null + $oRetFilter = self::FromOQL($sOql, $aParams); + $oRetFilter->m_aModifierProperties = $aData[2]; + return $oRetFilter; + } + + abstract public function ToOQL($bDevelopParams = false, $aContextParams = null); + + static protected $m_aOQLQueries = array(); + + // Do not filter out depending on user rights + // In particular when we are currently in the process of evaluating the user rights... + static public function FromOQL_AllData($sQuery, $aParams = null) + { + $oRes = self::FromOQL($sQuery, $aParams); + $oRes->AllowAllData(); + return $oRes; + } + + static public function FromOQL($sQuery, $aParams = null) + { + if (empty($sQuery)) return null; + + // Query caching + $bOQLCacheEnabled = true; + if ($bOQLCacheEnabled && array_key_exists($sQuery, self::$m_aOQLQueries)) + { + // hit! + $oClone = self::$m_aOQLQueries[$sQuery]->DeepClone(); + if (!is_null($aParams)) + { + $oClone->SetInternalParams($aParams); + } + return $oClone; + } + + $oOql = new OqlInterpreter($sQuery); + $oOqlQuery = $oOql->ParseQuery(); + + $oMetaModel = new ModelReflectionRuntime(); + $oOqlQuery->Check($oMetaModel, $sQuery); // Exceptions thrown in case of issue + + $oResultFilter = $oOqlQuery->ToDBSearch($sQuery); + + if (!is_null($aParams)) + { + $oResultFilter->SetInternalParams($aParams); + } + + if ($bOQLCacheEnabled) + { + self::$m_aOQLQueries[$sQuery] = $oResultFilter->DeepClone(); + } + + return $oResultFilter; + } + + // Alternative to object mapping: the data are transfered directly into an array + // This is 10 times faster than creating a set of objects, and makes sense when optimization is required + /** + * @param hash $aOrderBy Array of '[.]attcode' => bAscending + */ + public function ToDataArray($aColumns = array(), $aOrderBy = array(), $aArgs = array()) + { + $sSQL = $this->MakeSelectQuery($aOrderBy, $aArgs); + $resQuery = CMDBSource::Query($sSQL); + if (!$resQuery) return; + + if (count($aColumns) == 0) + { + $aColumns = array_keys(MetaModel::ListAttributeDefs($this->GetClass())); + // Add the standard id (as first column) + array_unshift($aColumns, 'id'); + } + + $aQueryCols = CMDBSource::GetColumns($resQuery); + + $sClassAlias = $this->GetClassAlias(); + $aColMap = array(); + foreach ($aColumns as $sAttCode) + { + $sColName = $sClassAlias.$sAttCode; + if (in_array($sColName, $aQueryCols)) + { + $aColMap[$sAttCode] = $sColName; + } + } + + $aRes = array(); + while ($aRow = CMDBSource::FetchArray($resQuery)) + { + $aMappedRow = array(); + foreach ($aColMap as $sAttCode => $sColName) + { + $aMappedRow[$sAttCode] = $aRow[$sColName]; + } + $aRes[] = $aMappedRow; + } + CMDBSource::FreeResult($resQuery); + return $aRes; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Construction of the SQL queries + // + //////////////////////////////////////////////////////////////////////////// + protected static $m_aQueryStructCache = array(); + + + public function MakeGroupByQuery($aArgs, $aGroupByExpr, $bExcludeNullValues = false) + { + if ($bExcludeNullValues) + { + // Null values are not handled (though external keys set to 0 are allowed) + $oQueryFilter = $this->DeepClone(); + foreach ($aGroupByExpr as $oGroupByExp) + { + $oNull = new FunctionExpression('ISNULL', array($oGroupByExp)); + $oNotNull = new BinaryExpression($oNull, '!=', new TrueExpression()); + $oQueryFilter->AddConditionExpression($oNotNull); + } + } + else + { + $oQueryFilter = $this; + } + + $aAttToLoad = array(); + $oSQLQuery = $oQueryFilter->GetSQLQuery(array(), $aArgs, $aAttToLoad, null, 0, 0, false, $aGroupByExpr); + + $aScalarArgs = array_merge(MetaModel::PrepareQueryArguments($aArgs), $this->GetInternalParams()); + try + { + $bBeautifulSQL = self::$m_bTraceQueries || self::$m_bDebugQuery || self::$m_bIndentQueries; + $sRes = $oSQLQuery->RenderGroupBy($aScalarArgs, $bBeautifulSQL); + } + catch (MissingQueryArgument $e) + { + // Add some information... + $e->addInfo('OQL', $this->ToOQL()); + throw $e; + } + $this->AddQueryTraceGroupBy($aArgs, $aGroupByExpr, $sRes); + return $sRes; + } + + + /** + * @param hash $aOrderBy Array of '[.]attcode' => bAscending + */ + public function MakeSelectQuery($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 + // + $sClass = $this->GetClass(); + $sClassAlias = $this->GetClassAlias(); + $aOrderSpec = array(); + foreach ($aOrderBy as $sFieldAlias => $bAscending) + { + if (!is_bool($bAscending)) + { + throw new CoreException("Wrong direction in ORDER BY spec, found '$bAscending' and expecting a boolean value"); + } + + $iDotPos = strpos($sFieldAlias, '.'); + if ($iDotPos === false) + { + $sAttClass = $sClass; + $sAttClassAlias = $sClassAlias; + $sAttCode = $sFieldAlias; + } + else + { + $sAttClassAlias = substr($sFieldAlias, 0, $iDotPos); + $sAttClass = $this->GetClassName($sAttClassAlias); + $sAttCode = substr($sFieldAlias, $iDotPos + 1); + } + + if ($sAttCode != 'id') + { + MyHelpers::CheckValueInArray('field name in ORDER BY spec', $sAttCode, MetaModel::GetAttributesList($sAttClass)); + + $oAttDef = MetaModel::GetAttributeDef($sAttClass, $sAttCode); + foreach($oAttDef->GetOrderBySQLExpressions($sAttClassAlias) as $sSQLExpression) + { + $aOrderSpec[$sSQLExpression] = $bAscending; + } + } + else + { + $aOrderSpec['`'.$sAttClassAlias.$sAttCode.'`'] = $bAscending; + } + + // Make sure that the columns used for sorting are present in the loaded columns + if (!is_null($aAttToLoad) && !isset($aAttToLoad[$sAttClassAlias][$sAttCode])) + { + $aAttToLoad[$sAttClassAlias][$sAttCode] = MetaModel::GetAttributeDef($sAttClass, $sAttCode); + } + } + + $oSQLQuery = $this->GetSQLQuery($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount); + + $aScalarArgs = array_merge(MetaModel::PrepareQueryArguments($aArgs), $this->GetInternalParams()); + try + { + $bBeautifulSQL = self::$m_bTraceQueries || self::$m_bDebugQuery || self::$m_bIndentQueries; + $sRes = $oSQLQuery->RenderSelect($aOrderSpec, $aScalarArgs, $iLimitCount, $iLimitStart, $bGetCount, $bBeautifulSQL); + if ($sClassAlias == '_itop_') + { + echo $sRes."
\n"; + } + } + catch (MissingQueryArgument $e) + { + // Add some information... + $e->addInfo('OQL', $this->ToOQL()); + throw $e; + } + $this->AddQueryTraceSelect($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount, $sRes); + return $sRes; + } + + + protected function GetSQLQuery($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount, $aGroupByExpr = null) + { + // Hide objects that are not visible to the current user + // + $oSearch = $this; + if (!$this->IsAllDataAllowed() && !$this->IsDataFiltered()) + { + $oVisibleObjects = UserRights::GetSelectFilter($this->GetClass(), $this->GetModifierProperties('UserRightsGetSelectFilter')); + if ($oVisibleObjects === false) + { + // Make sure this is a valid search object, saying NO for all + $oVisibleObjects = DBObjectSearch::FromEmptySet($this->GetClass()); + } + if (is_object($oVisibleObjects)) + { + $oSearch = $this->Intersect($oVisibleObjects); + $oSearch->SetDataFiltered(); + } + else + { + // should be true at this point, meaning that no additional filtering + // is required + } + } + + // Compute query modifiers properties (can be set in the search itself, by the context, etc.) + // + $aModifierProperties = MetaModel::MakeModifierProperties($oSearch); + + // Create a unique cache id + // + if (self::$m_bQueryCacheEnabled || self::$m_bTraceQueries) + { + // Need to identify the query + $sOqlQuery = $oSearch->ToOql(); + + if (count($aModifierProperties)) + { + array_multisort($aModifierProperties); + $sModifierProperties = json_encode($aModifierProperties); + } + else + { + $sModifierProperties = ''; + } + + $sRawId = $sOqlQuery.$sModifierProperties; + if (!is_null($aAttToLoad)) + { + $sRawId .= json_encode($aAttToLoad); + } + if (!is_null($aGroupByExpr)) + { + foreach($aGroupByExpr as $sAlias => $oExpr) + { + $sRawId .= 'g:'.$sAlias.'!'.$oExpr->Render(); + } + } + $sRawId .= $bGetCount; + $sOqlId = md5($sRawId); + } + else + { + $sOqlQuery = "SELECTING... ".$oSearch->GetClass(); + $sOqlId = "query id ? n/a"; + } + + + // Query caching + // + if (self::$m_bQueryCacheEnabled) + { + // Warning: using directly the query string as the key to the hash array can FAIL if the string + // is long and the differences are only near the end... so it's safer (but not bullet proof?) + // to use a hash (like md5) of the string as the key ! + // + // Example of two queries that were found as similar by the hash array: + // SELECT SLT JOIN lnkSLTToSLA AS L1 ON L1.slt_id=SLT.id JOIN SLA ON L1.sla_id = SLA.id JOIN lnkContractToSLA AS L2 ON L2.sla_id = SLA.id JOIN CustomerContract ON L2.contract_id = CustomerContract.id WHERE SLT.ticket_priority = 1 AND SLA.service_id = 3 AND SLT.metric = 'TTO' AND CustomerContract.customer_id = 2 + // and + // SELECT SLT JOIN lnkSLTToSLA AS L1 ON L1.slt_id=SLT.id JOIN SLA ON L1.sla_id = SLA.id JOIN lnkContractToSLA AS L2 ON L2.sla_id = SLA.id JOIN CustomerContract ON L2.contract_id = CustomerContract.id WHERE SLT.ticket_priority = 1 AND SLA.service_id = 3 AND SLT.metric = 'TTR' AND CustomerContract.customer_id = 2 + // the only difference is R instead or O at position 285 (TTR instead of TTO)... + // + if (array_key_exists($sOqlId, self::$m_aQueryStructCache)) + { + // hit! + + $oSQLQuery = unserialize(serialize(self::$m_aQueryStructCache[$sOqlId])); + // Note: cloning is not enough because the subtree is made of objects + } + elseif (self::$m_bUseAPCCache) + { + // Note: For versions of APC older than 3.0.17, fetch() accepts only one parameter + // + $sOqlAPCCacheId = 'itop-'.MetaModel::GetEnvironmentId().'-query-cache-'.$sOqlId; + $oKPI = new ExecutionKPI(); + $result = apc_fetch($sOqlAPCCacheId); + $oKPI->ComputeStats('Query APC (fetch)', $sOqlQuery); + + if (is_object($result)) + { + $oSQLQuery = $result; + self::$m_aQueryStructCache[$sOqlId] = $oSQLQuery; + } + } + } + + if (!isset($oSQLQuery)) + { + $oKPI = new ExecutionKPI(); + $oSQLQuery = $oSearch->MakeSQLQuery($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr); + $oSQLQuery->SetSourceOQL($sOqlQuery); + $oKPI->ComputeStats('MakeSQLQuery', $sOqlQuery); + + if (self::$m_bQueryCacheEnabled) + { + if (self::$m_bUseAPCCache) + { + $oKPI = new ExecutionKPI(); + apc_store($sOqlAPCCacheId, $oSQLQuery, self::$m_iQueryCacheTTL); + $oKPI->ComputeStats('Query APC (store)', $sOqlQuery); + } + + self::$m_aQueryStructCache[$sOqlId] = $oSQLQuery->DeepClone(); + } + } + + // Join to an additional table, if required... + // + if ($aExtendedDataSpec != null) + { + $sTableAlias = '_extended_data_'; + $aExtendedFields = array(); + foreach($aExtendedDataSpec['fields'] as $sColumn) + { + $sColRef = $oSearch->GetClassAlias().'_extdata_'.$sColumn; + $aExtendedFields[$sColRef] = new FieldExpressionResolved($sColumn, $sTableAlias); + } + $oSQLQueryExt = new SQLObjectQuery($aExtendedDataSpec['table'], $sTableAlias, $aExtendedFields); + $oSQLQuery->AddInnerJoin($oSQLQueryExt, 'id', $aExtendedDataSpec['join_key'] /*, $sTableAlias*/); + } + + return $oSQLQuery; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Cache/Trace/Log queries + // + //////////////////////////////////////////////////////////////////////////// + protected static $m_bDebugQuery = false; + protected static $m_aQueriesLog = array(); + protected static $m_bQueryCacheEnabled = false; + protected static $m_bUseAPCCache = false; + protected static $m_iQueryCacheTTL = 3600; + protected static $m_bTraceQueries = false; + protected static $m_bIndentQueries = false; + protected static $m_bOptimizeQueries = false; + + public static function StartDebugQuery() + { + $aBacktrace = debug_backtrace(); + self::$m_bDebugQuery = true; + } + public static function StopDebugQuery() + { + self::$m_bDebugQuery = false; + } + + public static function EnableQueryCache($bEnabled, $bUseAPC, $iTimeToLive = 3600) + { + self::$m_bQueryCacheEnabled = $bEnabled; + self::$m_bUseAPCCache = $bUseAPC; + self::$m_iQueryCacheTTL = $iTimeToLive; + } + public static function EnableQueryTrace($bEnabled) + { + self::$m_bTraceQueries = $bEnabled; + } + public static function EnableQueryIndentation($bEnabled) + { + self::$m_bIndentQueries = $bEnabled; + } + public static function EnableOptimizeQuery($bEnabled) + { + self::$m_bOptimizeQueries = $bEnabled; + } + + + protected function AddQueryTraceSelect($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount, $sSql) + { + if (self::$m_bTraceQueries) + { + $aQueryData = array( + 'type' => 'select', + 'filter' => $this, + 'order_by' => $aOrderBy, + 'args' => $aArgs, + 'att_to_load' => $aAttToLoad, + 'extended_data_spec' => $aExtendedDataSpec, + 'limit_count' => $iLimitCount, + 'limit_start' => $iLimitStart, + 'is_count' => $bGetCount + ); + $sOql = $this->ToOQL(true, $aArgs); + self::AddQueryTrace($aQueryData, $sOql, $sSql); + } + } + + protected function AddQueryTraceGroupBy($aArgs, $aGroupByExpr, $sSql) + { + if (self::$m_bTraceQueries) + { + $aQueryData = array( + 'type' => 'group_by', + 'filter' => $this, + 'args' => $aArgs, + 'group_by_expr' => $aGroupByExpr + ); + $sOql = $this->ToOQL(true, $aArgs); + self::AddQueryTrace($aQueryData, $sOql, $sSql); + } + } + + protected static function AddQueryTrace($aQueryData, $sOql, $sSql) + { + if (self::$m_bTraceQueries) + { + $sQueryId = md5(serialize($aQueryData)); + $sMySQLQueryId = md5($sSql); + if(!isset(self::$m_aQueriesLog[$sQueryId])) + { + self::$m_aQueriesLog[$sQueryId]['data'] = serialize($aQueryData); + self::$m_aQueriesLog[$sQueryId]['oql'] = $sOql; + self::$m_aQueriesLog[$sQueryId]['hits'] = 1; + } + else + { + self::$m_aQueriesLog[$sQueryId]['hits']++; + } + if(!isset(self::$m_aQueriesLog[$sQueryId]['queries'][$sMySQLQueryId])) + { + self::$m_aQueriesLog[$sQueryId]['queries'][$sMySQLQueryId]['sql'] = $sSql; + self::$m_aQueriesLog[$sQueryId]['queries'][$sMySQLQueryId]['count'] = 1; + $iTableCount = count(CMDBSource::ExplainQuery($sSql)); + self::$m_aQueriesLog[$sQueryId]['queries'][$sMySQLQueryId]['table_count'] = $iTableCount; + } + else + { + self::$m_aQueriesLog[$sQueryId]['queries'][$sMySQLQueryId]['count']++; + } + } + } + + public static function RecordQueryTrace() + { + if (!self::$m_bTraceQueries) return; + + $iOqlCount = count(self::$m_aQueriesLog); + $iSqlCount = 0; + foreach (self::$m_aQueriesLog as $sQueryId => $aOqlData) + { + $iSqlCount += $aOqlData['hits']; + } + $sHtml = "

Stats on SELECT queries: OQL=$iOqlCount, SQL=$iSqlCount

\n"; + foreach (self::$m_aQueriesLog as $sQueryId => $aOqlData) + { + $sOql = $aOqlData['oql']; + $sHits = $aOqlData['hits']; + + $sHtml .= "

$sHits hits for OQL query: $sOql

\n"; + $sHtml .= "
    \n"; + foreach($aOqlData['queries'] as $aSqlData) + { + $sQuery = $aSqlData['sql']; + $sSqlHits = $aSqlData['count']; + $iTableCount = $aSqlData['table_count']; + $sHtml .= "
  • $sSqlHits hits for SQL ($iTableCount tables):
    $sQuery
  • \n"; + } + $sHtml .= "
\n"; + } + + $sLogFile = 'queries.latest'; + file_put_contents(APPROOT.'data/'.$sLogFile.'.html', $sHtml); + + $sLog = " $aOqlData) + { + if (!array_key_exists($sQueryId, $aQueriesLog)) + { + $aQueriesLog[$sQueryId] = $aOqlData; + } + } + } + else + { + $aQueriesLog = self::$m_aQueriesLog; + } + $sLog = "".$aBacktrace[1]["function"].""; + + if (is_string($value)) + { + echo "$sIndent$sFunction: $value
\n"; + } + else if (is_object($value)) + { + echo "$sIndent$sFunction:\n
\n";
+			print_r($value);
+			echo "
\n"; + } + else + { + echo "$sIndent$sFunction: $value
\n"; + } + } +} diff --git a/core/dbunionsearch.class.php b/core/dbunionsearch.class.php new file mode 100644 index 000000000..66749617a --- /dev/null +++ b/core/dbunionsearch.class.php @@ -0,0 +1,432 @@ + + + +/** + * A union of DBObjectSearches + * + * @copyright Copyright (C) 2015 Combodo SARL + * @license http://opensource.org/licenses/AGPL-3.0 + */ + +class DBUnionSearch extends DBSearch +{ + protected $aSearches; // source queries + protected $aSelectedClasses; // alias => classes (lowest common ancestors) computed at construction + + public function __construct($aSearches) + { + if (count ($aSearches) == 0) + { + throw new CoreException('A DBUnionSearch must be made of at least one search'); + } + + $this->aSearches = array(); + foreach ($aSearches as $oSearch) + { + if ($oSearch instanceof DBUnionSearch) + { + foreach ($oSearch->aSearches as $oSubSearch) + { + $this->aSearches[] = $oSubSearch->DeepClone(); + } + } + else + { + $this->aSearches[] = $oSearch->DeepClone(); + } + } + + // 1 - Collect all the column/classes + $aColumnToClasses = array(); + foreach ($this->aSearches as $iPos => $oSearch) + { + $aSelected = array_values($oSearch->GetSelectedClasses()); + + if ($iPos != 0) + { + if (count($aSelected) < count($aColumnToClasses)) + { + throw new Exception('Too few selected classes in the subquery #'.($iPos+1)); + } + if (count($aSelected) > count($aColumnToClasses)) + { + throw new Exception('Too many selected classes in the subquery #'.($iPos+1)); + } + } + + foreach ($aSelected as $iColumn => $sClass) + { + $aColumnToClasses[$iColumn][] = $sClass; + } + } + + // 2 - Build the index column => alias + $oFirstSearch = $this->aSearches[0]; + $aColumnToAlias = array_keys($oFirstSearch->GetSelectedClasses()); + + // 3 - Compute alias => lowest common ancestor + $this->aSelectedClasses = array(); + foreach ($aColumnToClasses as $iColumn => $aClasses) + { + $sAlias = $aColumnToAlias[$iColumn]; + $sAncestor = MetaModel::GetLowestCommonAncestor($aClasses); + if (is_null($sAncestor)) + { + throw new Exception('Could not find a common ancestor for the column '.($iColumn+1).' (Classes: '.implode(', ', $aClasses).')'); + } + $this->aSelectedClasses[$sAlias] = $sAncestor; + } + } + + public function GetSearches() + { + return $this->aSearches; + } + + /** + * Limited to the selected classes + */ + public function GetClassName($sAlias) + { + if (array_key_exists($sAlias, $this->aSelectedClasses)) + { + return $this->aSelectedClasses[$sAlias]; + } + else + { + throw new CoreException("Invalid class alias '$sAlias'"); + } + } + + public function GetClass() + { + return reset($this->aSelectedClasses); + } + + public function GetClassAlias() + { + reset($this->aSelectedClasses); + return key($this->aSelectedClasses); + } + + + /** + * Change the class (only subclasses are supported as of now, because the conditions must fit the new class) + * Defaults to the first selected class + * Only the selected classes can be changed + */ + public function ChangeClass($sNewClass, $sAlias = null) + { + if (is_null($sAlias)) + { + $sAlias = $this->GetClassAlias(); + } + elseif (!array_key_exists($sAlias, $this->aSelectedClasses)) + { + // discard silently - necessary when recursing (??? copied from DBObjectSearch) + return; + } + + // 1 - identify the impacted column + $iColumn = array_search($sAlias, array_keys($this->aSelectedClasses)); + + // 2 - change for each search + foreach ($this->aSearches as $oSearch) + { + $aSearchAliases = array_keys($oSearch->GetSelectedClasses()); + $sSearchAlias = $aSearchAliases[$iColumn]; + $oSearch->ChangeClass($sNewClass, $sSearchAlias); + } + + // 3 - record the change + $this->aSelectedClasses[$sAlias] = $sNewClass; + } + + public function GetSelectedClasses() + { + return $this->aSelectedClasses; + } + + public function IsAny() + { + $bIsAny = true; + foreach ($this->aSearches as $oSearch) + { + if (!$oSearch->IsAny()) + { + $bIsAny = false; + break; + } + } + return $bIsAny; + } + + public function ResetCondition() + { + foreach ($this->aSearches as $oSearch) + { + $oSearch->ResetCondition(); + } + } + + public function MergeConditionExpression($oExpression) + { + $aAliases = array_keys($this->aSelectedClasses); + foreach ($this->aSearches as $iSearchIndex => $oSearch) + { + $oClonedExpression = $oExpression->DeepClone(); + if ($iSearchIndex != 0) + { + foreach (array_keys($oSearch->GetSelectedClasses()) as $iColumn => $sSearchAlias) + { + $oClonedExpression->RenameAlias($aAliases[$iColumn], $sSearchAlias); + } + } + $oSearch->MergeConditionExpression($oClonedExpression); + } + } + + public function AddConditionExpression($oExpression) + { + $aAliases = array_keys($this->aSelectedClasses); + foreach ($this->aSearches as $iSearchIndex => $oSearch) + { + $oClonedExpression = $oExpression->DeepClone(); + if ($iSearchIndex != 0) + { + foreach (array_keys($oSearch->GetSelectedClasses()) as $iColumn => $sSearchAlias) + { + $oClonedExpression->RenameAlias($aAliases[$iColumn], $sSearchAlias); + } + } + $oSearch->AddConditionExpression($oClonedExpression); + } + } + + public function AddNameCondition($sName) + { + foreach ($this->aSearches as $oSearch) + { + $oSearch->AddNameCondition($sName); + } + } + + public function AddCondition($sFilterCode, $value, $sOpCode = null) + { + foreach ($this->aSearches as $oSearch) + { + $oSearch->AddCondition($sFilterCode, $value, $sOpCode); + } + } + + /** + * Specify a condition on external keys or link sets + * @param sAttSpec Can be either an attribute code or extkey->[sAttSpec] or linkset->[sAttSpec] and so on, recursively + * Example: infra_list->ci_id->location_id->country + * @param value The value to match (can be an array => IN(val1, val2...) + * @return void + */ + public function AddConditionAdvanced($sAttSpec, $value) + { + foreach ($this->aSearches as $oSearch) + { + $oSearch->AddConditionAdvanced($sAttSpec, $value); + } + } + + public function AddCondition_FullText($sFullText) + { + foreach ($this->aSearches as $oSearch) + { + $oSearch->AddCondition_FullText($sFullText); + } + } + + public function AddCondition_PointingTo(DBObjectSearch $oFilter, $sExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS) + { + foreach ($this->aSearches as $oSearch) + { + $oSearch->AddCondition_PointingTo($oFilter, $sExtKeyAttCode, $iOperatorCode); + } + } + + public function AddCondition_ReferencedBy(DBObjectSearch $oFilter, $sForeignExtKeyAttCode) + { + foreach ($this->aSearches as $oSearch) + { + $oSearch->AddCondition_ReferencedBy($oFilter, $sForeignExtKeyAttCode); + } + } + + public function Intersect(DBSearch $oFilter) + { + $aSearches = array(); + foreach ($this->aSearches as $oSearch) + { + $aSearches[] = $oSearch->Intersect($oFilter); + } + return new DBUnionSearch($aSearches); + } + + public function SetInternalParams($aParams) + { + foreach ($this->aSearches as $oSearch) + { + $oSearch->SetInternalParams($aParams); + } + } + + public function GetInternalParams() + { + $aParams = array(); + foreach ($this->aSearches as $oSearch) + { + $aParams = array_merge($oSearch->GetInternalParams(), $aParams); + } + return $aParams; + } + + public function GetQueryParams() + { + $aParams = array(); + foreach ($this->aSearches as $oSearch) + { + $aParams = array_merge($oSearch->GetQueryParams(), $aParams); + } + return $aParams; + } + + public function ListConstantFields() + { + // Somewhat complex to implement for unions, for a poor benefit + return array(); + } + + /** + * Turn the parameters (:xxx) into scalar values in order to easily + * serialize a search + */ + public function ApplyParameters($aArgs) + { + foreach ($this->aSearches as $oSearch) + { + $oSearch->ApplyParameters($aArgs); + } + } + + /** + * Overloads for query building + */ + public function ToOQL($bDevelopParams = false, $aContextParams = null) + { + $aSubQueries = array(); + foreach ($this->aSearches as $oSearch) + { + $aSubQueries[] = $oSearch->ToOQL($bDevelopParams, $aContextParams); + } + $sRet = implode(' UNION ', $aSubQueries); + return $sRet; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Construction of the SQL queries + // + //////////////////////////////////////////////////////////////////////////// + + public function MakeDeleteQuery($aArgs = array()) + { + throw new Exception('MakeDeleteQuery is not implemented for the unions!'); + } + + public function MakeUpdateQuery($aValues, $aArgs = array()) + { + throw new Exception('MakeUpdateQuery is not implemented for the unions!'); + } + + protected function MakeSQLQuery($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr = null, $aSelectedClasses = null) + { + if (count($this->aSearches) == 1) + { + return $this->aSearches[0]->MakeSQLQuery($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr); + } + + $aSQLQueries = array(); + $aAliases = array_keys($this->aSelectedClasses); + foreach ($this->aSearches as $iSearch => $oSearch) + { + $aSearchAliases = array_keys($oSearch->GetSelectedClasses()); + + // The selected classes from the query build perspective are the lowest common ancestors amongst the various queries + // (used when it comes to determine which attributes must be selected) + $aSearchSelectedClasses = array(); + foreach ($aSearchAliases as $iColumn => $sSearchAlias) + { + $sAlias = $aAliases[$iColumn]; + $aSearchSelectedClasses[$sSearchAlias] = $this->aSelectedClasses[$sAlias]; + } + + if (is_null($aAttToLoad)) + { + $aQueryAttToLoad = null; + } + else + { + // (Eventually) Transform the aliases + $aQueryAttToLoad = array(); + foreach ($aAttToLoad as $sAlias => $aAttributes) + { + $iColumn = array_search($sAlias, $aAliases); + $sQueryAlias = ($iColumn === false) ? $sAlias : $aSearchAliases[$iColumn]; + $aQueryAttToLoad[$sQueryAlias] = $aAttributes; + } + } + + if (is_null($aGroupByExpr)) + { + $aQueryGroupByExpr = null; + } + else + { + // Clone (and eventually transform) the group by expressions + $aQueryGroupByExpr = array(); + $aTranslationData = array(); + $aQueryColumns = array_keys($oSearch->GetSelectedClasses()); + foreach ($aAliases as $iColumn => $sAlias) + { + $sQueryAlias = $aQueryColumns[$iColumn]; + $aTranslationData[$sAlias]['*'] = $sQueryAlias; + $aQueryGroupByExpr[$sAlias.'id'] = new FieldExpression('id', $sQueryAlias); + } + foreach ($aGroupByExpr as $sExpressionAlias => $oExpression) + { + $aQueryGroupByExpr[$sExpressionAlias] = $oExpression->Translate($aTranslationData, false, false); + } + } + $oSubQuery = $oSearch->MakeSQLQuery($aQueryAttToLoad, false, $aModifierProperties, $aQueryGroupByExpr, $aSearchSelectedClasses); + $aSQLQueries[] = $oSubQuery; + } + + $oSQLQuery = new SQLUnionQuery($aSQLQueries, $aGroupByExpr); + //MyHelpers::var_dump_html($oSQLQuery, true); + //MyHelpers::var_dump_html($oSQLQuery->RenderSelect(), true); + if (self::$m_bDebugQuery) $oSQLQuery->DisplayHtml(); + return $oSQLQuery; + } +} diff --git a/core/expression.class.inc.php b/core/expression.class.inc.php index f70c26b74..b1fa54136 100644 --- a/core/expression.class.inc.php +++ b/core/expression.class.inc.php @@ -1,5 +1,5 @@ GetLeftExpr()->RenameParam($sOldName, $sNewName); $this->GetRightExpr()->RenameParam($sOldName, $sNewName); } + + public function RenameAlias($sOldName, $sNewName) + { + $this->GetLeftExpr()->RenameAlias($sOldName, $sNewName); + $this->GetRightExpr()->RenameAlias($sOldName, $sNewName); + } } @@ -374,6 +394,11 @@ class UnaryExpression extends Expression // Do nothing // really ? what about :param{$iParamIndex} ?? } + + public function RenameAlias($sOldName, $sNewName) + { + // Do nothing + } } class ScalarExpression extends UnaryExpression @@ -526,7 +551,7 @@ class FieldExpression extends UnaryExpression /** * Make the most relevant label, given the value of the expression * - * @param DBObjectSearch oFilter The context in which this expression has been used + * @param DBSearch oFilter The context in which this expression has been used * @param string sValue The value returned by the query, for this expression * @param string sDefault The default value if no relevant label could be computed * @return The label @@ -568,6 +593,14 @@ class FieldExpression extends UnaryExpression } return $sRes; } + + public function RenameAlias($sOldName, $sNewName) + { + if ($this->m_sParent == $sOldName) + { + $this->m_sParent = $sNewName; + } + } } // Has been resolved into an SQL expression @@ -792,6 +825,15 @@ class ListExpression extends Expression $this->m_aExpressions[$key] = $oExpr->RenameParam($sOldName, $sNewName); } } + + public function RenameAlias($sOldName, $sNewName) + { + $aRes = array(); + foreach ($this->m_aExpressions as $key => $oExpr) + { + $oExpr->RenameAlias($sOldName, $sNewName); + } + } } @@ -826,7 +868,7 @@ class FunctionExpression extends Expression public function Render(&$aArgs = null, $bRetrofitParams = false) { $aRes = array(); - foreach ($this->m_aArgs as $oExpr) + foreach ($this->m_aArgs as $iPos => $oExpr) { $aRes[] = $oExpr->Render($aArgs, $bRetrofitParams); } @@ -903,10 +945,18 @@ class FunctionExpression extends Expression } } + public function RenameAlias($sOldName, $sNewName) + { + foreach ($this->m_aArgs as $key => $oExpr) + { + $oExpr->RenameAlias($sOldName, $sNewName); + } + } + /** * Make the most relevant label, given the value of the expression * - * @param DBObjectSearch oFilter The context in which this expression has been used + * @param DBSearch oFilter The context in which this expression has been used * @param string sValue The value returned by the query, for this expression * @param string sDefault The default value if no relevant label could be computed * @return The label @@ -1048,6 +1098,11 @@ class IntervalExpression extends Expression { $this->m_oValue->RenameParam($sOldName, $sNewName); } + + public function RenameAlias($sOldName, $sNewName) + { + $this->m_oValue->RenameAlias($sOldName, $sNewName); + } } class CharConcatExpression extends Expression @@ -1152,6 +1207,14 @@ class CharConcatExpression extends Expression $this->m_aExpressions[$key] = $oExpr->RenameParam($sOldName, $sNewName); } } + + public function RenameAlias($sOldName, $sNewName) + { + foreach ($this->m_aExpressions as $key => $oExpr) + { + $oExpr->RenameAlias($sOldName, $sNewName); + } + } } diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 5e0433750..f244f06bc 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -26,7 +26,7 @@ require_once(APPROOT.'core/relationgraph.class.inc.php'); /** * Metamodel * - * @copyright Copyright (C) 2010-2012 Combodo SARL + * @copyright Copyright (C) 2010-2015 Combodo SARL * @license http://opensource.org/licenses/AGPL-3.0 */ @@ -184,53 +184,6 @@ abstract class MetaModel var_dump(get_class_vars(__CLASS__)); } - private static $m_bDebugQuery = false; - private static $m_iStackDepthRef = 0; - - public static function StartDebugQuery() - { - $aBacktrace = debug_backtrace(); - self::$m_iStackDepthRef = count($aBacktrace); - self::$m_bDebugQuery = true; - } - public static function StopDebugQuery() - { - self::$m_bDebugQuery = false; - } - public static function DbgTrace($value) - { - if (!self::$m_bDebugQuery) return; - $aBacktrace = debug_backtrace(); - $iCallStackPos = count($aBacktrace) - self::$m_bDebugQuery; - $sIndent = ""; - for ($i = 0 ; $i < $iCallStackPos ; $i++) - { - $sIndent .= " .-=^=-. "; - } - $aCallers = array(); - foreach($aBacktrace as $aStackInfo) - { - $aCallers[] = $aStackInfo["function"]; - } - $sCallers = "Callstack: ".implode(', ', $aCallers); - $sFunction = "".$aBacktrace[1]["function"].""; - - if (is_string($value)) - { - echo "$sIndent$sFunction: $value
\n"; - } - else if (is_object($value)) - { - echo "$sIndent$sFunction:\n
\n";
-			print_r($value);
-			echo "
\n"; - } - else - { - echo "$sIndent$sFunction: $value
\n"; - } - } - private static $m_oConfig = null; protected static $m_aModulesParameters = array(); @@ -238,13 +191,6 @@ abstract class MetaModel private static $m_bSkipCheckExtKeys = false; private static $m_bUseAPCCache = false; - private static $m_iQueryCacheTTL = 3600; - - private static $m_bQueryCacheEnabled = false; - private static $m_bTraceQueries = false; - private static $m_bIndentQueries = false; - private static $m_bOptimizeQueries = false; - private static $m_aQueriesLog = array(); private static $m_bLogIssue = false; private static $m_bLogNotification = false; @@ -483,62 +429,6 @@ abstract class MetaModel return $oNameExpr; } - /** - * Get the friendly name for the class and its subclasses (if finalclass = 'subclass' ...) - * Simplifies the final expression by grouping classes having the same name expression - * Used when querying a parent class - */ - final static protected function GetExtendedNameExpression($sClass) - { - // 1st step - get all of the required expressions (instantiable classes) - // and group them using their OQL representation - // - $aFNExpressions = array(); // signature => array('expression' => oExp, 'classes' => array of classes) - foreach (self::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL) as $sSubClass) - { - if (($sSubClass != $sClass) && self::IsAbstract($sSubClass)) continue; - - $oSubClassName = self::GetNameExpression($sSubClass); - $sSignature = $oSubClassName->Render(); - if (!array_key_exists($sSignature, $aFNExpressions)) - { - $aFNExpressions[$sSignature] = array( - 'expression' => $oSubClassName, - 'classes' => array(), - ); - } - $aFNExpressions[$sSignature]['classes'][] = $sSubClass; - } - - // 2nd step - build the final name expression depending on the finalclass - // - if (count($aFNExpressions) == 1) - { - $aExpData = reset($aFNExpressions); - $oNameExpression = $aExpData['expression']; - } - else - { - $oNameExpression = null; - foreach ($aFNExpressions as $sSignature => $aExpData) - { - $oClassListExpr = ListExpression::FromScalars($aExpData['classes']); - $oClassExpr = new FieldExpression('finalclass', $sClass); - $oClassInList = new BinaryExpression($oClassExpr, 'IN', $oClassListExpr); - - if (is_null($oNameExpression)) - { - $oNameExpression = $aExpData['expression']; - } - else - { - $oNameExpression = new FunctionExpression('IF', array($oClassInList, $aExpData['expression'], $oNameExpression)); - } - } - } - return $oNameExpression; - } - final static public function GetStateAttributeCode($sClass) { self::_check_subclass($sClass); @@ -2371,6 +2261,57 @@ abstract class MetaModel return end(self::$m_aParentClasses[$sClass]); } } + public static function GetLowestCommonAncestor($aClasses) + { + $sAncestor = null; + foreach($aClasses as $sClass) + { + if (is_null($sAncestor)) + { + // first loop + $sAncestor = $sClass; + } + elseif ($sClass == $sAncestor) + { + // remains the same + } + elseif (self::GetRootClass($sClass) != self::GetRootClass($sAncestor)) + { + $sAncestor = null; + break; + } + else + { + $sAncestor = self::LowestCommonAncestor($sAncestor, $sClass); + } + } + return $sAncestor; + } + /** + * Note: assumes that class A and B have a common ancestor + */ + protected static function LowestCommonAncestor($sClassA, $sClassB) + { + if ($sClassA == $sClassB) + { + $sRet = $sClassA; + } + elseif (is_subclass_of($sClassA, $sClassB)) + { + $sRet = $sClassB; + } + elseif (is_subclass_of($sClassB, $sClassA)) + { + $sRet = $sClassA; + } + else + { + // Recurse + $sRet = self::LowestCommonAncestor($sClassA, self::GetParentClass($sClassB)); + } + return $sRet; + } + /** * Tells if a class contains a hierarchical key, and if so what is its AttCode * @return mixed String = sAttCode or false if the class is not part of a hierarchy @@ -2491,8 +2432,6 @@ abstract class MetaModel return $oReflection->isAbstract(); } - protected static $m_aQueryStructCache = array(); - public static function PrepareQueryArguments($aArgs) { // Translate any object into scalars @@ -2531,411 +2470,7 @@ abstract class MetaModel return $aScalarArgs; } - public static function MakeGroupByQuery(DBObjectSearch $oFilter, $aArgs, $aGroupByExpr, $bExcludeNullValues = false) - { - $aAttToLoad = array(); - - if ($bExcludeNullValues) - { - // Null values are not handled (though external keys set to 0 are allowed) - $oQueryFilter = $oFilter->DeepClone(); - foreach ($aGroupByExpr as $oGroupByExp) - { - $oNull = new FunctionExpression('ISNULL', array($oGroupByExp)); - $oNotNull = new BinaryExpression($oNull, '!=', new TrueExpression()); - $oQueryFilter->AddConditionExpression($oNotNull); - } - } - else - { - $oQueryFilter = $oFilter; - } - - $oSelect = self::MakeSelectStructure($oQueryFilter, array(), $aArgs, $aAttToLoad, null, 0, 0, false, $aGroupByExpr); - - $aScalarArgs = array_merge(self::PrepareQueryArguments($aArgs), $oFilter->GetInternalParams()); - try - { - $bBeautifulSQL = self::$m_bTraceQueries || self::$m_bDebugQuery || self::$m_bIndentQueries; - $sRes = $oSelect->RenderGroupBy($aScalarArgs, $bBeautifulSQL); - } - catch (MissingQueryArgument $e) - { - // Add some information... - $e->addInfo('OQL', $oFilter->ToOQL()); - throw $e; - } - self::AddQueryTraceGroupBy($oFilter, $aArgs, $aGroupByExpr, $sRes); - return $sRes; - } - - - /** - * @param hash $aOrderBy Array of '[.]attcode' => bAscending - */ - 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 - // - $sClass = $oFilter->GetClass(); - $sClassAlias = $oFilter->GetClassAlias(); - $aOrderSpec = array(); - foreach ($aOrderBy as $sFieldAlias => $bAscending) - { - if (!is_bool($bAscending)) - { - throw new CoreException("Wrong direction in ORDER BY spec, found '$bAscending' and expecting a boolean value"); - } - - $iDotPos = strpos($sFieldAlias, '.'); - if ($iDotPos === false) - { - $sAttClass = $sClass; - $sAttClassAlias = $sClassAlias; - $sAttCode = $sFieldAlias; - } - else - { - $sAttClassAlias = substr($sFieldAlias, 0, $iDotPos); - $sAttClass = $oFilter->GetClassName($sAttClassAlias); - $sAttCode = substr($sFieldAlias, $iDotPos + 1); - } - - if ($sAttCode != 'id') - { - MyHelpers::CheckValueInArray('field name in ORDER BY spec', $sAttCode, self::GetAttributesList($sAttClass)); - - $oAttDef = self::GetAttributeDef($sAttClass, $sAttCode); - foreach($oAttDef->GetOrderBySQLExpressions($sAttClassAlias) as $sSQLExpression) - { - $aOrderSpec[$sSQLExpression] = $bAscending; - } - } - else - { - $aOrderSpec['`'.$sAttClassAlias.$sAttCode.'`'] = $bAscending; - } - - // Make sure that the columns used for sorting are present in the loaded columns - if (!is_null($aAttToLoad) && !isset($aAttToLoad[$sAttClassAlias][$sAttCode])) - { - $aAttToLoad[$sAttClassAlias][$sAttCode] = MetaModel::GetAttributeDef($sAttClass, $sAttCode); - } - } - - $oSelect = self::MakeSelectStructure($oFilter, $aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount); - - $aScalarArgs = array_merge(self::PrepareQueryArguments($aArgs), $oFilter->GetInternalParams()); - try - { - $bBeautifulSQL = self::$m_bTraceQueries || self::$m_bDebugQuery || self::$m_bIndentQueries; - $sRes = $oSelect->RenderSelect($aOrderSpec, $aScalarArgs, $iLimitCount, $iLimitStart, $bGetCount, $bBeautifulSQL); - if ($sClassAlias == '_itop_') - { - echo $sRes."
\n"; - } - } - catch (MissingQueryArgument $e) - { - // Add some information... - $e->addInfo('OQL', $oFilter->ToOQL()); - throw $e; - } - self::AddQueryTraceSelect($oFilter, $aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount, $sRes); - 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 - // - if (!$oFilter->IsAllDataAllowed() && !$oFilter->IsDataFiltered()) - { - $oVisibleObjects = UserRights::GetSelectFilter($oFilter->GetClass(), $oFilter->GetModifierProperties('UserRightsGetSelectFilter')); - if ($oVisibleObjects === false) - { - // Make sure this is a valid search object, saying NO for all - $oVisibleObjects = DBObjectSearch::FromEmptySet($oFilter->GetClass()); - } - if (is_object($oVisibleObjects)) - { - $oFilter->MergeWith($oVisibleObjects); - $oFilter->SetDataFiltered(); - } - else - { - // should be true at this point, meaning that no additional filtering - // is required - } - } - - // Compute query modifiers properties (can be set in the search itself, by the context, etc.) - // - $aModifierProperties = self::MakeModifierProperties($oFilter); - - // Create a unique cache id - // - if (self::$m_bQueryCacheEnabled || self::$m_bTraceQueries) - { - // Need to identify the query - $sOqlQuery = $oFilter->ToOql(); - - if (count($aModifierProperties)) - { - array_multisort($aModifierProperties); - $sModifierProperties = json_encode($aModifierProperties); - } - else - { - $sModifierProperties = ''; - } - - $sRawId = $sOqlQuery.$sModifierProperties; - if (!is_null($aAttToLoad)) - { - $sRawId .= json_encode($aAttToLoad); - } - if (!is_null($aGroupByExpr)) - { - foreach($aGroupByExpr as $sAlias => $oExpr) - { - $sRawId .= 'g:'.$sAlias.'!'.$oExpr->Render(); - } - } - $sRawId .= $bGetCount; - $sOqlId = md5($sRawId); - } - else - { - $sOqlQuery = "SELECTING... ".$oFilter->GetClass(); - $sOqlId = "query id ? n/a"; - } - - - // Query caching - // - if (self::$m_bQueryCacheEnabled) - { - // Warning: using directly the query string as the key to the hash array can FAIL if the string - // is long and the differences are only near the end... so it's safer (but not bullet proof?) - // to use a hash (like md5) of the string as the key ! - // - // Example of two queries that were found as similar by the hash array: - // SELECT SLT JOIN lnkSLTToSLA AS L1 ON L1.slt_id=SLT.id JOIN SLA ON L1.sla_id = SLA.id JOIN lnkContractToSLA AS L2 ON L2.sla_id = SLA.id JOIN CustomerContract ON L2.contract_id = CustomerContract.id WHERE SLT.ticket_priority = 1 AND SLA.service_id = 3 AND SLT.metric = 'TTO' AND CustomerContract.customer_id = 2 - // and - // SELECT SLT JOIN lnkSLTToSLA AS L1 ON L1.slt_id=SLT.id JOIN SLA ON L1.sla_id = SLA.id JOIN lnkContractToSLA AS L2 ON L2.sla_id = SLA.id JOIN CustomerContract ON L2.contract_id = CustomerContract.id WHERE SLT.ticket_priority = 1 AND SLA.service_id = 3 AND SLT.metric = 'TTR' AND CustomerContract.customer_id = 2 - // the only difference is R instead or O at position 285 (TTR instead of TTO)... - // - if (array_key_exists($sOqlId, self::$m_aQueryStructCache)) - { - // hit! - $oSelect = unserialize(serialize(self::$m_aQueryStructCache[$sOqlId])); - // Note: cloning is not enough because the subtree is made of objects - } - elseif (self::$m_bUseAPCCache) - { - // Note: For versions of APC older than 3.0.17, fetch() accepts only one parameter - // - $sOqlAPCCacheId = 'itop-'.MetaModel::GetEnvironmentId().'-query-cache-'.$sOqlId; - $oKPI = new ExecutionKPI(); - $result = apc_fetch($sOqlAPCCacheId); - $oKPI->ComputeStats('Query APC (fetch)', $sOqlQuery); - - if (is_object($result)) - { - $oSelect = $result; - self::$m_aQueryStructCache[$sOqlId] = $oSelect; - } - } - } - - if (!isset($oSelect)) - { - $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()); - } - - if (self::$m_bOptimizeQueries) - { - if ($bGetCount) - { - // Simplify the query if just getting the count - $oSelect->SetSelect(array()); - } - $oBuild->m_oQBExpressions->GetMandatoryTables($aMandatoryTables); - $oSelect->OptimizeJoins($aMandatoryTables); - } - - $oKPI->ComputeStats('MakeQuery (select)', $sOqlQuery); - - if (self::$m_bQueryCacheEnabled) - { - if (self::$m_bUseAPCCache) - { - $oKPI = new ExecutionKPI(); - apc_store($sOqlAPCCacheId, $oSelect, self::$m_iQueryCacheTTL); - $oKPI->ComputeStats('Query APC (store)', $sOqlQuery); - } - - self::$m_aQueryStructCache[$sOqlId] = $oSelect->DeepClone(); - } - } - - // Join to an additional table, if required... - // - if ($aExtendedDataSpec != null) - { - $sTableAlias = '_extended_data_'; - $aExtendedFields = array(); - foreach($aExtendedDataSpec['fields'] as $sColumn) - { - $sColRef = $oFilter->GetClassAlias().'_extdata_'.$sColumn; - $aExtendedFields[$sColRef] = new FieldExpressionResolved($sColumn, $sTableAlias); - } - $oSelectExt = new SQLQuery($aExtendedDataSpec['table'], $sTableAlias, $aExtendedFields); - $oSelect->AddInnerJoin($oSelectExt, 'id', $aExtendedDataSpec['join_key'] /*, $sTableAlias*/); - } - - return $oSelect; - } - - protected static function AddQueryTraceSelect($oFilter, $aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount, $sSql) - { - if (self::$m_bTraceQueries) - { - $aQueryData = array( - 'type' => 'select', - 'filter' => $oFilter, - 'order_by' => $aOrderBy, - 'args' => $aArgs, - 'att_to_load' => $aAttToLoad, - 'extended_data_spec' => $aExtendedDataSpec, - 'limit_count' => $iLimitCount, - 'limit_start' => $iLimitStart, - 'is_count' => $bGetCount - ); - $sOql = $oFilter->ToOQL(true, $aArgs); - self::AddQueryTrace($aQueryData, $sOql, $sSql); - } - } - - protected static function AddQueryTraceGroupBy($oFilter, $aArgs, $aGroupByExpr, $sSql) - { - if (self::$m_bTraceQueries) - { - $aQueryData = array( - 'type' => 'group_by', - 'filter' => $oFilter, - 'args' => $aArgs, - 'group_by_expr' => $aGroupByExpr - ); - $sOql = $oFilter->ToOQL(true, $aArgs); - self::AddQueryTrace($aQueryData, $sOql, $sSql); - } - } - - protected static function AddQueryTrace($aQueryData, $sOql, $sSql) - { - if (self::$m_bTraceQueries) - { - $sQueryId = md5(serialize($aQueryData)); - $sMySQLQueryId = md5($sSql); - if(!isset(self::$m_aQueriesLog[$sQueryId])) - { - self::$m_aQueriesLog[$sQueryId]['data'] = serialize($aQueryData); - self::$m_aQueriesLog[$sQueryId]['oql'] = $sOql; - self::$m_aQueriesLog[$sQueryId]['hits'] = 1; - } - else - { - self::$m_aQueriesLog[$sQueryId]['hits']++; - } - if(!isset(self::$m_aQueriesLog[$sQueryId]['queries'][$sMySQLQueryId])) - { - self::$m_aQueriesLog[$sQueryId]['queries'][$sMySQLQueryId]['sql'] = $sSql; - self::$m_aQueriesLog[$sQueryId]['queries'][$sMySQLQueryId]['count'] = 1; - $iTableCount = count(CMDBSource::ExplainQuery($sSql)); - self::$m_aQueriesLog[$sQueryId]['queries'][$sMySQLQueryId]['table_count'] = $iTableCount; - } - else - { - self::$m_aQueriesLog[$sQueryId]['queries'][$sMySQLQueryId]['count']++; - } - } - } - - public static function RecordQueryTrace() - { - if (!self::$m_bTraceQueries) return; - - $iOqlCount = count(self::$m_aQueriesLog); - $iSqlCount = 0; - foreach (self::$m_aQueriesLog as $sQueryId => $aOqlData) - { - $iSqlCount += $aOqlData['hits']; - } - $sHtml = "

Stats on SELECT queries: OQL=$iOqlCount, SQL=$iSqlCount

\n"; - foreach (self::$m_aQueriesLog as $sQueryId => $aOqlData) - { - $sOql = $aOqlData['oql']; - $sHits = $aOqlData['hits']; - - $sHtml .= "

$sHits hits for OQL query: $sOql

\n"; - $sHtml .= "
    \n"; - foreach($aOqlData['queries'] as $aSqlData) - { - $sQuery = $aSqlData['sql']; - $sSqlHits = $aSqlData['count']; - $iTableCount = $aSqlData['table_count']; - $sHtml .= "
  • $sSqlHits hits for SQL ($iTableCount tables):
    $sQuery
  • \n"; - } - $sHtml .= "
\n"; - } - - $sLogFile = 'queries.latest'; - file_put_contents(APPROOT.'data/'.$sLogFile.'.html', $sHtml); - - $sLog = " $aOqlData) - { - if (!array_key_exists($sQueryId, $aQueriesLog)) - { - $aQueriesLog[$sQueryId] = $aOqlData; - } - } - } - else - { - $aQueriesLog = self::$m_aQueriesLog; - } - $sLog = "SetCondition($oBuild->m_oQBExpressions->GetCondition()); - $oSelect->SetSelect($oBuild->m_oQBExpressions->GetSelect()); - $aScalarArgs = array_merge(self::PrepareQueryArguments($aArgs), $oFilter->GetInternalParams()); - return $oSelect->RenderDelete($aScalarArgs); - } - public static function MakeUpdateQuery(DBObjectSearch $oFilter, $aValues, $aArgs = array()) - { - // $aValues is an array of $sAttCode => $value - $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(), $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) - $sClass = $oFilter->GetFirstJoinedClass(); - $sClassAlias = $oFilter->GetFirstJoinedClassAlias(); - - $bIsOnQueriedClass = array_key_exists($sClassAlias, $oBuild->GetRootFilter()->GetSelectedClasses()); - - self::DbgTrace("Entering: ".$oFilter->ToOQL().", ".($bIsOnQueriedClass ? "MAIN" : "SECONDARY")); - - $sRootClass = self::GetRootClass($sClass); - $sKeyField = self::DBGetKey($sClass); - - if ($bIsOnQueriedClass) - { - // default to the whole list of attributes + the very std id/finalclass - $oBuild->m_oQBExpressions->AddSelect($sClassAlias.'id', new FieldExpression('id', $sClassAlias)); - if (is_null($aAttToLoad) || !array_key_exists($sClassAlias, $aAttToLoad)) - { - $aAttList = self::ListAttributeDefs($sClass); - } - else - { - $aAttList = $aAttToLoad[$sClassAlias]; - } - foreach ($aAttList as $sAttCode => $oAttDef) - { - if (!$oAttDef->IsScalar()) continue; - // keep because it can be used for sorting - if (!$oAttDef->LoadInObject()) continue; - - foreach ($oAttDef->GetSQLExpressions() as $sColId => $sSQLExpr) - { - $oBuild->m_oQBExpressions->AddSelect($sClassAlias.$sAttCode.$sColId, new FieldExpression($sAttCode.$sColId, $sClassAlias)); - } - } - - // Transform the full text condition into additional condition expression - $aFullText = $oFilter->GetCriteria_FullText(); - if (count($aFullText) > 0) - { - $aFullTextFields = array(); - foreach (self::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) - { - if (!$oAttDef->IsScalar()) continue; - if ($oAttDef->IsExternalKey()) continue; - $aFullTextFields[] = new FieldExpression($sAttCode, $sClassAlias); - } - $oTextFields = new CharConcatWSExpression(' ', $aFullTextFields); - - foreach($aFullText as $sFTNeedle) - { - $oNewCond = new BinaryExpression($oTextFields, 'LIKE', new ScalarExpression("%$sFTNeedle%")); - $oBuild->m_oQBExpressions->AddCondition($oNewCond); - } - } - } -//echo "

oQBExpr ".__LINE__.":

\n".print_r($oBuild->m_oQBExpressions, true)."

\n"; - $aExpectedAtts = array(); // array of (attcode => fieldexpression) -//echo "

".__LINE__.": GetUnresolvedFields($sClassAlias, ...)

\n"; - $oBuild->m_oQBExpressions->GetUnresolvedFields($sClassAlias, $aExpectedAtts); - - // Compute a clear view of required joins (from the current class) - // Build the list of external keys: - // -> ext keys required by an explicit join - // -> ext keys mentionned in a 'pointing to' condition - // -> ext keys required for an external field - // -> ext keys required for a friendly name - // - $aExtKeys = array(); // array of sTableClass => array of (sAttCode (keys) => array of (sAttCode (fields)=> oAttDef)) - // - // Optimization: could be partially computed once for all (cached) ? - // - - if ($bIsOnQueriedClass) - { - // Get all Ext keys for the queried class (??) - foreach(self::GetKeysList($sClass) as $sKeyAttCode) - { - $sKeyTableClass = self::$m_aAttribOrigins[$sClass][$sKeyAttCode]; - $aExtKeys[$sKeyTableClass][$sKeyAttCode] = array(); - } - } - // Get all Ext keys used by the filter - foreach ($oFilter->GetCriteria_PointingTo() as $sKeyAttCode => $aPointingTo) - { - if (array_key_exists(TREE_OPERATOR_EQUALS, $aPointingTo)) - { - $sKeyTableClass = self::$m_aAttribOrigins[$sClass][$sKeyAttCode]; - $aExtKeys[$sKeyTableClass][$sKeyAttCode] = array(); - } - } - - $aFNJoinAlias = array(); // array of (subclass => alias) - if (array_key_exists('friendlyname', $aExpectedAtts)) - { - // To optimize: detect a restriction on child classes in the condition expression - // e.g. SELECT FunctionalCI WHERE finalclass IN ('Server', 'VirtualMachine') - $oNameExpression = self::GetExtendedNameExpression($sClass); - - $aNameFields = array(); - $oNameExpression->GetUnresolvedFields('', $aNameFields); - $aTranslateNameFields = array(); - foreach($aNameFields as $sSubClass => $aFields) - { - foreach($aFields as $sAttCode => $oField) - { - $oAttDef = self::GetAttributeDef($sSubClass, $sAttCode); - if ($oAttDef->IsExternalKey()) - { - $sClassOfAttribute = self::$m_aAttribOrigins[$sSubClass][$sAttCode]; - $aExtKeys[$sClassOfAttribute][$sAttCode] = array(); - } - elseif ($oAttDef->IsExternalField() || ($oAttDef instanceof AttributeFriendlyName)) - { - $sKeyAttCode = $oAttDef->GetKeyAttCode(); - $sClassOfAttribute = self::$m_aAttribOrigins[$sSubClass][$sKeyAttCode]; - $aExtKeys[$sClassOfAttribute][$sKeyAttCode][$sAttCode] = $oAttDef; - } - else - { - $sClassOfAttribute = self::GetAttributeOrigin($sSubClass, $sAttCode); - } - - if (self::IsParentClass($sClassOfAttribute, $sClass)) - { - // The attribute is part of the standard query - // - $sAliasForAttribute = $sClassAlias; - } - else - { - // The attribute will be available from an additional outer join - // For each subclass (table) one single join is enough - // - if (!array_key_exists($sClassOfAttribute, $aFNJoinAlias)) - { - $sAliasForAttribute = $oBuild->GenerateClassAlias($sClassAlias.'_fn_'.$sClassOfAttribute, $sClassOfAttribute); - $aFNJoinAlias[$sClassOfAttribute] = $sAliasForAttribute; - } - else - { - $sAliasForAttribute = $aFNJoinAlias[$sClassOfAttribute]; - } - } - - $aTranslateNameFields[$sSubClass][$sAttCode] = new FieldExpression($sAttCode, $sAliasForAttribute); - } - } - $oNameExpression = $oNameExpression->Translate($aTranslateNameFields, false); - - $aTranslateNow = array(); - $aTranslateNow[$sClassAlias]['friendlyname'] = $oNameExpression; - $oBuild->m_oQBExpressions->Translate($aTranslateNow, false); - } - - // Add the ext fields used in the select (eventually adds an external key) - foreach(self::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) - { - if ($oAttDef->IsExternalField() || ($oAttDef instanceof AttributeFriendlyName)) - { - if (array_key_exists($sAttCode, $aExpectedAtts)) - { - $sKeyAttCode = $oAttDef->GetKeyAttCode(); - if ($sKeyAttCode != 'id') - { - // Add the external attribute - $sKeyTableClass = self::$m_aAttribOrigins[$sClass][$sKeyAttCode]; - $aExtKeys[$sKeyTableClass][$sKeyAttCode][$sAttCode] = $oAttDef; - } - } - } - } - - // First query built upon on the leaf (ie current) class - // - self::DbgTrace("Main (=leaf) class, call MakeQuerySingleTable()"); - if (self::HasTable($sClass)) - { - $oSelectBase = self::MakeQuerySingleTable($oBuild, $oFilter, $sClass, $aExtKeys, $aValues); - } - else - { - $oSelectBase = null; - - // As the join will not filter on the expected classes, we have to specify it explicitely - $sExpectedClasses = implode("', '", self::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL)); - $oFinalClassRestriction = Expression::FromOQL("`$sClassAlias`.finalclass IN ('$sExpectedClasses')"); - $oBuild->m_oQBExpressions->AddCondition($oFinalClassRestriction); - } - - // Then we join the queries of the eventual parent classes (compound model) - foreach(self::EnumParentClasses($sClass) as $sParentClass) - { - if (!self::HasTable($sParentClass)) continue; - - self::DbgTrace("Parent class: $sParentClass... let's call MakeQuerySingleTable()"); - $oSelectParentTable = self::MakeQuerySingleTable($oBuild, $oFilter, $sParentClass, $aExtKeys, $aValues); - if (is_null($oSelectBase)) - { - $oSelectBase = $oSelectParentTable; - } - else - { - $oSelectBase->AddInnerJoin($oSelectParentTable, $sKeyField, self::DBGetKey($sParentClass)); - } - } - - // Filter on objects referencing me - foreach ($oFilter->GetCriteria_ReferencedBy() as $sForeignClass => $aKeysAndFilters) - { - foreach ($aKeysAndFilters as $sForeignKeyAttCode => $oForeignFilter) - { - $oForeignKeyAttDef = self::GetAttributeDef($sForeignClass, $sForeignKeyAttCode); - - self::DbgTrace("Referenced by foreign key: $sForeignKeyAttCode... let's call MakeQuery()"); - //self::DbgTrace($oForeignFilter); - //self::DbgTrace($oForeignFilter->ToOQL()); - //self::DbgTrace($oSelectForeign); - //self::DbgTrace($oSelectForeign->RenderSelect(array())); - - $sForeignClassAlias = $oForeignFilter->GetFirstJoinedClassAlias(); - $oBuild->m_oQBExpressions->PushJoinField(new FieldExpression($sForeignKeyAttCode, $sForeignClassAlias)); - - if ($oForeignKeyAttDef instanceof AttributeObjectKey) - { - $sClassAttCode = $oForeignKeyAttDef->Get('class_attcode'); - - // Add the condition: `$sForeignClassAlias`.$sClassAttCode IN (subclasses of $sClass') - $oClassListExpr = ListExpression::FromScalars(self::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL)); - $oClassExpr = new FieldExpression($sClassAttCode, $sForeignClassAlias); - $oClassRestriction = new BinaryExpression($oClassExpr, 'IN', $oClassListExpr); - $oBuild->m_oQBExpressions->AddCondition($oClassRestriction); - } - - $oSelectForeign = self::MakeQuery($oBuild, $oForeignFilter, $aAttToLoad); - - $oJoinExpr = $oBuild->m_oQBExpressions->PopJoinField(); - $sForeignKeyTable = $oJoinExpr->GetParent(); - $sForeignKeyColumn = $oJoinExpr->GetName(); - $oSelectBase->AddInnerJoin($oSelectForeign, $sKeyField, $sForeignKeyColumn, $sForeignKeyTable); - } - } - - // Filter on related objects - // - foreach ($oFilter->GetCriteria_RelatedTo() as $aCritInfo) - { - $oSubFilter = $aCritInfo['flt']; - $sRelCode = $aCritInfo['relcode']; - $iMaxDepth = $aCritInfo['maxdepth']; - - // Get the starting point objects - $oStartSet = new CMDBObjectSet($oSubFilter); - - // Get the objects related to those objects... recursively... - $aRelatedObjs = $oStartSet->GetRelatedObjects($sRelCode, $iMaxDepth); - $aRestriction = array_key_exists($sRootClass, $aRelatedObjs) ? $aRelatedObjs[$sRootClass] : array(); - - // #@# todo - related objects and expressions... - // Create condition - if (count($aRestriction) > 0) - { - $oSelectBase->AddCondition($sKeyField.' IN ('.implode(', ', CMDBSource::Quote(array_keys($aRestriction), true)).')'); - } - else - { - // Quick N'dirty -> generate an empty set - $oSelectBase->AddCondition('false'); - } - } - - // Additional JOINS for Friendly names - // - foreach ($aFNJoinAlias as $sSubClass => $sSubClassAlias) - { - $oSubClassFilter = new DBObjectSearch($sSubClass, $sSubClassAlias); - $oSelectFN = self::MakeQuerySingleTable($oBuild, $oSubClassFilter, $sSubClass, $aExtKeys, array()); - $oSelectBase->AddLeftJoin($oSelectFN, $sKeyField, self::DBGetKey($sSubClass)); - } - - // That's all... cross fingers and we'll get some working query - - //MyHelpers::var_dump_html($oSelectBase, true); - //MyHelpers::var_dump_html($oSelectBase->RenderSelect(), true); - if (self::$m_bDebugQuery) $oSelectBase->DisplayHtml(); - return $oSelectBase; - } - - protected static function MakeQuerySingleTable(&$oBuild, $oFilter, $sTableClass, $aExtKeys, $aValues) - { - // $aExtKeys is an array of sTableClass => array of (sAttCode (keys) => array of sAttCode (fields)) -//echo "MAKEQUERY($sTableClass)-liste des clefs externes($sTableClass):
".print_r($aExtKeys, true)."

\n"; - - // Prepare the query for a single table (compound objects) - // Ignores the items (attributes/filters) that are not on the target table - // Perform an (inner or left) join for every external key (and specify the expected fields) - // - // Returns an SQLQuery - // - $sTargetClass = $oFilter->GetFirstJoinedClass(); - $sTargetAlias = $oFilter->GetFirstJoinedClassAlias(); - $sTable = self::DBGetTable($sTableClass); - $sTableAlias = $oBuild->GenerateTableAlias($sTargetAlias.'_'.$sTable, $sTable); - - $aTranslation = array(); - $aExpectedAtts = array(); - $oBuild->m_oQBExpressions->GetUnresolvedFields($sTargetAlias, $aExpectedAtts); - - $bIsOnQueriedClass = array_key_exists($sTargetAlias, $oBuild->GetRootFilter()->GetSelectedClasses()); - - self::DbgTrace("Entering: tableclass=$sTableClass, filter=".$oFilter->ToOQL().", ".($bIsOnQueriedClass ? "MAIN" : "SECONDARY")); - - // 1 - SELECT and UPDATE - // - // Note: no need for any values nor fields for foreign Classes (ie not the queried Class) - // - $aUpdateValues = array(); - - - // 1/a - Get the key and friendly name - // - // We need one pkey to be the key, let's take the first one available - $oSelectedIdField = null; - $oIdField = new FieldExpressionResolved(self::DBGetKey($sTableClass), $sTableAlias); - $aTranslation[$sTargetAlias]['id'] = $oIdField; - - if ($bIsOnQueriedClass) - { - // Add this field to the list of queried fields (required for the COUNT to work fine) - $oSelectedIdField = $oIdField; - } - - // 1/b - Get the other attributes - // - foreach(self::ListAttributeDefs($sTableClass) as $sAttCode=>$oAttDef) - { - // Skip this attribute if not defined in this table - if (self::$m_aAttribOrigins[$sTargetClass][$sAttCode] != $sTableClass) continue; - - // Skip this attribute if not made of SQL columns - if (count($oAttDef->GetSQLExpressions()) == 0) continue; - - // Update... - // - if ($bIsOnQueriedClass && array_key_exists($sAttCode, $aValues)) - { - assert ($oAttDef->IsDirectField()); - foreach ($oAttDef->GetSQLValues($aValues[$sAttCode]) as $sColumn => $sValue) - { - $aUpdateValues[$sColumn] = $sValue; - } - } - } - - // 2 - The SQL query, for this table only - // - $oSelectBase = new SQLQuery($sTable, $sTableAlias, array(), $bIsOnQueriedClass, $aUpdateValues, $oSelectedIdField); - - // 3 - Resolve expected expressions (translation table: alias.attcode => table.column) - // - foreach(self::ListAttributeDefs($sTableClass) as $sAttCode=>$oAttDef) - { - // Skip this attribute if not defined in this table - if (self::$m_aAttribOrigins[$sTargetClass][$sAttCode] != $sTableClass) continue; - - // Select... - // - if ($oAttDef->IsExternalField()) - { - // skip, this will be handled in the joined tables (done hereabove) - } - else - { -//echo "

MakeQuerySingleTable: Field $sAttCode is part of the table $sTable (named: $sTableAlias)

"; - // standard field, or external key - // add it to the output - foreach ($oAttDef->GetSQLExpressions() as $sColId => $sSQLExpr) - { - if (array_key_exists($sAttCode.$sColId, $aExpectedAtts)) - { - $oFieldSQLExp = new FieldExpressionResolved($sSQLExpr, $sTableAlias); - foreach (MetaModel::EnumPlugins('iQueryModifier') as $sPluginClass => $oQueryModifier) - { - $oFieldSQLExp = $oQueryModifier->GetFieldExpression($oBuild, $sTargetClass, $sAttCode, $sColId, $oFieldSQLExp, $oSelectBase); - } - $aTranslation[$sTargetAlias][$sAttCode.$sColId] = $oFieldSQLExp; - } - } - } - } - -//echo "MAKEQUERY- Classe $sTableClass
\n"; - // 4 - The external keys -> joins... - // - $aAllPointingTo = $oFilter->GetCriteria_PointingTo(); - - if (array_key_exists($sTableClass, $aExtKeys)) - { - foreach ($aExtKeys[$sTableClass] as $sKeyAttCode => $aExtFields) - { - $oKeyAttDef = self::GetAttributeDef($sTableClass, $sKeyAttCode); - - $aPointingTo = $oFilter->GetCriteria_PointingTo($sKeyAttCode); -//echo "MAKEQUERY-Cle '$sKeyAttCode'
\n"; - if (!array_key_exists(TREE_OPERATOR_EQUALS, $aPointingTo)) - { -//echo "MAKEQUERY-Ajoutons l'operateur TREE_OPERATOR_EQUALS pour $sKeyAttCode
\n"; - // The join was not explicitely defined in the filter, - // we need to do it now - $sKeyClass = $oKeyAttDef->GetTargetClass(); - $sKeyClassAlias = $oBuild->GenerateClassAlias($sKeyClass.'_'.$sKeyAttCode, $sKeyClass); - $oExtFilter = new DBObjectSearch($sKeyClass, $sKeyClassAlias); - - $aAllPointingTo[$sKeyAttCode][TREE_OPERATOR_EQUALS][$sKeyClassAlias] = $oExtFilter; - } - } - } -//echo "MAKEQUERY-liste des clefs de jointure:
".print_r(array_keys($aAllPointingTo), true)."

\n"; - - foreach ($aAllPointingTo as $sKeyAttCode => $aPointingTo) - { - foreach($aPointingTo as $iOperatorCode => $aFilter) - { - foreach($aFilter as $oExtFilter) - { - if (!MetaModel::IsValidAttCode($sTableClass, $sKeyAttCode)) continue; // Not defined in the class, skip it - // The aliases should not conflict because normalization occured while building the filter - $oKeyAttDef = self::GetAttributeDef($sTableClass, $sKeyAttCode); - $sKeyClass = $oExtFilter->GetFirstJoinedClass(); - $sKeyClassAlias = $oExtFilter->GetFirstJoinedClassAlias(); - -//echo "MAKEQUERY-$sTableClass::$sKeyAttCode Foreach PointingTo($iOperatorCode) $sKeyClass (alias:$sKeyClassAlias)
\n"; - - // Note: there is no search condition in $oExtFilter, because normalization did merge the condition onto the top of the filter tree - -//echo "MAKEQUERY-array_key_exists($sTableClass, \$aExtKeys)
\n"; - if ($iOperatorCode == TREE_OPERATOR_EQUALS) - { - if (array_key_exists($sTableClass, $aExtKeys) && array_key_exists($sKeyAttCode, $aExtKeys[$sTableClass])) - { - // Specify expected attributes for the target class query - // ... and use the current alias ! - $aTranslateNow = array(); // Translation for external fields - must be performed before the join is done (recursion...) - foreach($aExtKeys[$sTableClass][$sKeyAttCode] as $sAttCode => $oAtt) - { -//echo "MAKEQUERY aExtKeys[$sTableClass][$sKeyAttCode] => $sAttCode-oAtt:
".print_r($oAtt, true)."

\n"; - if ($oAtt instanceof AttributeFriendlyName) - { - // Note: for a given ext key, there is one single attribute "friendly name" - $aTranslateNow[$sTargetAlias][$sAttCode] = new FieldExpression('friendlyname', $sKeyClassAlias); -//echo "

aTranslateNow[$sTargetAlias][$sAttCode] = new FieldExpression('friendlyname', $sKeyClassAlias);

\n"; - } - else - { - $sExtAttCode = $oAtt->GetExtAttCode(); - // Translate mainclass.extfield => remoteclassalias.remotefieldcode - $oRemoteAttDef = self::GetAttributeDef($sKeyClass, $sExtAttCode); - foreach ($oRemoteAttDef->GetSQLExpressions() as $sColID => $sRemoteAttExpr) - { - $aTranslateNow[$sTargetAlias][$sAttCode.$sColId] = new FieldExpression($sExtAttCode, $sKeyClassAlias); -//echo "

aTranslateNow[$sTargetAlias][$sAttCode.$sColId] = new FieldExpression($sExtAttCode, $sKeyClassAlias);

\n"; - } -//echo "

ExtAttr2: $sTargetAlias.$sAttCode to $sKeyClassAlias.$sRemoteAttExpr (class: $sKeyClass)

\n"; - } - } - - if ($oKeyAttDef instanceof AttributeObjectKey) - { - // Add the condition: `$sTargetAlias`.$sClassAttCode IN (subclasses of $sKeyClass') - $sClassAttCode = $oKeyAttDef->Get('class_attcode'); - $oClassAttDef = self::GetAttributeDef($sTargetClass, $sClassAttCode); - foreach ($oClassAttDef->GetSQLExpressions() as $sColID => $sSQLExpr) - { - $aTranslateNow[$sTargetAlias][$sClassAttCode.$sColId] = new FieldExpressionResolved($sSQLExpr, $sTableAlias); - } - - $oClassListExpr = ListExpression::FromScalars(self::EnumChildClasses($sKeyClass, ENUM_CHILD_CLASSES_ALL)); - $oClassExpr = new FieldExpression($sClassAttCode, $sTargetAlias); - $oClassRestriction = new BinaryExpression($oClassExpr, 'IN', $oClassListExpr); - $oBuild->m_oQBExpressions->AddCondition($oClassRestriction); - } - - // Translate prior to recursing - // -//echo "

oQBExpr ".__LINE__.":

\n".print_r($oBuild->m_oQBExpressions, true)."\n".print_r($aTranslateNow, true)."

\n"; - $oBuild->m_oQBExpressions->Translate($aTranslateNow, false); -//echo "

oQBExpr ".__LINE__.":

\n".print_r($oBuild->m_oQBExpressions, true)."

\n"; - -//echo "

External key $sKeyAttCode (class: $sKeyClass), call MakeQuery()/p>\n"; - self::DbgTrace("External key $sKeyAttCode (class: $sKeyClass), call MakeQuery()"); - $oBuild->m_oQBExpressions->PushJoinField(new FieldExpression('id', $sKeyClassAlias)); - -//echo "

Recursive MakeQuery ".__LINE__.":

\n".print_r($oBuild->GetRootFilter()->GetSelectedClasses(), true)."

\n"; - $oSelectExtKey = self::MakeQuery($oBuild, $oExtFilter); - - $oJoinExpr = $oBuild->m_oQBExpressions->PopJoinField(); - $sExternalKeyTable = $oJoinExpr->GetParent(); - $sExternalKeyField = $oJoinExpr->GetName(); - - $aCols = $oKeyAttDef->GetSQLExpressions(); // Workaround a PHP bug: sometimes issuing a Notice if invoking current(somefunc()) - $sLocalKeyField = current($aCols); // get the first column for an external key - - self::DbgTrace("External key $sKeyAttCode, Join on $sLocalKeyField = $sExternalKeyField"); - if ($oKeyAttDef->IsNullAllowed()) - { - $oSelectBase->AddLeftJoin($oSelectExtKey, $sLocalKeyField, $sExternalKeyField, $sExternalKeyTable); - } - else - { - $oSelectBase->AddInnerJoin($oSelectExtKey, $sLocalKeyField, $sExternalKeyField, $sExternalKeyTable); - } - } - } - elseif(self::$m_aAttribOrigins[$sKeyClass][$sKeyAttCode] == $sTableClass) - { - $oBuild->m_oQBExpressions->PushJoinField(new FieldExpression($sKeyAttCode, $sKeyClassAlias)); - $oSelectExtKey = self::MakeQuery($oBuild, $oExtFilter); - $oJoinExpr = $oBuild->m_oQBExpressions->PopJoinField(); -//echo "MAKEQUERY-PopJoinField pour $sKeyAttCode, $sKeyClassAlias:
".print_r($oJoinExpr, true)."

\n"; - $sExternalKeyTable = $oJoinExpr->GetParent(); - $sExternalKeyField = $oJoinExpr->GetName(); - $sLeftIndex = $sExternalKeyField.'_left'; // TODO use GetSQLLeft() - $sRightIndex = $sExternalKeyField.'_right'; // TODO use GetSQLRight() - - $LocalKeyLeft = $oKeyAttDef->GetSQLLeft(); - $LocalKeyRight = $oKeyAttDef->GetSQLRight(); -//echo "MAKEQUERY-LocalKeyLeft pour $sKeyAttCode => $LocalKeyLeft
\n"; - - $oSelectBase->AddInnerJoinTree($oSelectExtKey, $LocalKeyLeft, $LocalKeyRight, $sLeftIndex, $sRightIndex, $sExternalKeyTable, $iOperatorCode); - } - } - } - } - - // Translate the selected columns - // -//echo "

oQBExpr ".__LINE__.":

\n".print_r($oBuild->m_oQBExpressions, true)."

\n"; - $oBuild->m_oQBExpressions->Translate($aTranslation, false); -//echo "

oQBExpr ".__LINE__.":

\n".print_r($oBuild->m_oQBExpressions, true)."

\n"; - - //MyHelpers::var_dump_html($oSelectBase->RenderSelect()); - return $oSelectBase; - } /** * Special processing for the hierarchical keys stored as nested sets @@ -4628,7 +3596,7 @@ abstract class MetaModel // $oFilter = new DBObjectSearch($sClass, ''); $oFilter->AllowAllData(); - $sSQL = self::MakeSelectQuery($oFilter); + $sSQL = $oFilter->MakeSelectQuery(); $aErrors[$sClass]['*'][] = "Redeclare view '$sView' (systematic - to support an eventual change in the friendly name computation)"; $aSugFix[$sClass]['*'][] = "ALTER VIEW `$sView` AS $sSQL"; } @@ -4639,7 +3607,7 @@ abstract class MetaModel // $oFilter = new DBObjectSearch($sClass, ''); $oFilter->AllowAllData(); - $sSQL = self::MakeSelectQuery($oFilter); + $sSQL = $oFilter->MakeSelectQuery(); $aErrors[$sClass]['*'][] = "Missing view for class: $sClass"; $aSugFix[$sClass]['*'][] = "DROP VIEW IF EXISTS `$sView`"; $aSugFix[$sClass]['*'][] = "CREATE VIEW `$sView` AS $sSQL"; @@ -5113,11 +4081,6 @@ abstract class MetaModel ExecutionKPI::EnableMemory(self::$m_oConfig->Get('log_kpi_memory')); ExecutionKPI::SetAllowedUser(self::$m_oConfig->Get('log_kpi_user_id')); - self::$m_bTraceQueries = self::$m_oConfig->GetLogQueries(); - self::$m_bIndentQueries = self::$m_oConfig->Get('query_indentation_enabled'); - self::$m_bQueryCacheEnabled = self::$m_oConfig->GetQueryCacheEnabled(); - - self::$m_bOptimizeQueries = self::$m_oConfig->Get('query_optimization_enabled'); self::$m_bSkipCheckToWrite = self::$m_oConfig->Get('skip_check_to_write'); self::$m_bSkipCheckExtKeys = self::$m_oConfig->Get('skip_check_ext_keys'); @@ -5125,7 +4088,11 @@ abstract class MetaModel && self::$m_oConfig->Get('apc_cache.enabled') && function_exists('apc_fetch') && function_exists('apc_store'); - self::$m_iQueryCacheTTL = self::$m_oConfig->Get('apc_cache.query_ttl'); + + DBSearch::EnableQueryCache(self::$m_oConfig->GetQueryCacheEnabled(), self::$m_bUseAPCCache, self::$m_oConfig->Get('apc_cache.query_ttl')); + DBSearch::EnableQueryTrace(self::$m_oConfig->GetLogQueries()); + DBSearch::EnableQueryIndentation(self::$m_oConfig->Get('query_indentation_enabled')); + DBSearch::EnableOptimizeQuery(self::$m_oConfig->Get('query_optimization_enabled')); // PHP timezone first... // @@ -5411,7 +4378,7 @@ abstract class MetaModel $oFilter->AllowAllData(); } - $sSQL = self::MakeSelectQuery($oFilter); + $sSQL = $oFilter->MakeSelectQuery(); self::$aQueryCacheGetObject[$sQuerySign] = $sSQL; self::$aQueryCacheGetObjectHits[$sQuerySign] = 0; } @@ -5584,7 +4551,7 @@ abstract class MetaModel */ public static function BulkDelete(DBObjectSearch $oFilter) { - $sSQL = self::MakeDeleteQuery($oFilter); + $sSQL = $oFilter->MakeDeleteQuery(); if (!self::DBIsReadOnly()) { CMDBSource::Query($sSQL); @@ -5594,7 +4561,7 @@ abstract class MetaModel public static function BulkUpdate(DBObjectSearch $oFilter, array $aValues) { // $aValues is an array of $sAttCode => $value - $sSQL = self::MakeUpdateQuery($oFilter, $aValues); + $sSQL = $oFilter->MakeUpdateQuery($aValues); if (!self::DBIsReadOnly()) { CMDBSource::Query($sSQL); diff --git a/core/oql/oql-lexer.php b/core/oql/oql-lexer.php index 7049b5bb4..23d9c7b6e 100644 --- a/core/oql/oql-lexer.php +++ b/core/oql/oql-lexer.php @@ -1,6 +1,6 @@ token = OQLParser::SELECT; + $this->token = OQLParser::UNION; } function yy_r1_2($yy_subpatterns) { - $this->token = OQLParser::FROM; + $this->token = OQLParser::SELECT; } function yy_r1_3($yy_subpatterns) { - $this->token = OQLParser::AS_ALIAS; + $this->token = OQLParser::FROM; } function yy_r1_4($yy_subpatterns) { - $this->token = OQLParser::WHERE; + $this->token = OQLParser::AS_ALIAS; } function yy_r1_5($yy_subpatterns) { - $this->token = OQLParser::JOIN; + $this->token = OQLParser::WHERE; } function yy_r1_6($yy_subpatterns) { - $this->token = OQLParser::ON; + $this->token = OQLParser::JOIN; } function yy_r1_7($yy_subpatterns) { - $this->token = OQLParser::MATH_DIV; + $this->token = OQLParser::ON; } function yy_r1_8($yy_subpatterns) { - $this->token = OQLParser::MATH_MULT; + $this->token = OQLParser::MATH_DIV; } function yy_r1_9($yy_subpatterns) { - $this->token = OQLParser::MATH_PLUS; + $this->token = OQLParser::MATH_MULT; } function yy_r1_10($yy_subpatterns) { - $this->token = OQLParser::MATH_MINUS; + $this->token = OQLParser::MATH_PLUS; } function yy_r1_11($yy_subpatterns) { - $this->token = OQLParser::LOG_AND; + $this->token = OQLParser::MATH_MINUS; } function yy_r1_12($yy_subpatterns) { - $this->token = OQLParser::LOG_OR; + $this->token = OQLParser::LOG_AND; } function yy_r1_13($yy_subpatterns) { - $this->token = OQLParser::BITWISE_OR; + $this->token = OQLParser::LOG_OR; } function yy_r1_14($yy_subpatterns) { - $this->token = OQLParser::BITWISE_AND; + $this->token = OQLParser::BITWISE_OR; } function yy_r1_15($yy_subpatterns) { - $this->token = OQLParser::BITWISE_XOR; + $this->token = OQLParser::BITWISE_AND; } function yy_r1_16($yy_subpatterns) { - $this->token = OQLParser::BITWISE_LEFT_SHIFT; + $this->token = OQLParser::BITWISE_XOR; } function yy_r1_17($yy_subpatterns) { - $this->token = OQLParser::BITWISE_RIGHT_SHIFT; + $this->token = OQLParser::BITWISE_LEFT_SHIFT; } function yy_r1_18($yy_subpatterns) { - $this->token = OQLParser::COMA; + $this->token = OQLParser::BITWISE_RIGHT_SHIFT; } function yy_r1_19($yy_subpatterns) { - $this->token = OQLParser::PAR_OPEN; + $this->token = OQLParser::COMA; } function yy_r1_20($yy_subpatterns) { - $this->token = OQLParser::PAR_CLOSE; + $this->token = OQLParser::PAR_OPEN; } function yy_r1_21($yy_subpatterns) { - $this->token = OQLParser::REGEXP; + $this->token = OQLParser::PAR_CLOSE; } function yy_r1_22($yy_subpatterns) { - $this->token = OQLParser::EQ; + $this->token = OQLParser::REGEXP; } function yy_r1_23($yy_subpatterns) { - $this->token = OQLParser::NOT_EQ; + $this->token = OQLParser::EQ; } function yy_r1_24($yy_subpatterns) { - $this->token = OQLParser::GT; + $this->token = OQLParser::NOT_EQ; } function yy_r1_25($yy_subpatterns) { - $this->token = OQLParser::LT; + $this->token = OQLParser::GT; } function yy_r1_26($yy_subpatterns) { - $this->token = OQLParser::GE; + $this->token = OQLParser::LT; } function yy_r1_27($yy_subpatterns) { - $this->token = OQLParser::LE; + $this->token = OQLParser::GE; } function yy_r1_28($yy_subpatterns) { - $this->token = OQLParser::LIKE; + $this->token = OQLParser::LE; } function yy_r1_29($yy_subpatterns) { - $this->token = OQLParser::NOT_LIKE; + $this->token = OQLParser::LIKE; } function yy_r1_30($yy_subpatterns) { - $this->token = OQLParser::IN; + $this->token = OQLParser::NOT_LIKE; } function yy_r1_31($yy_subpatterns) { - $this->token = OQLParser::NOT_IN; + $this->token = OQLParser::IN; } function yy_r1_32($yy_subpatterns) { - $this->token = OQLParser::INTERVAL; + $this->token = OQLParser::NOT_IN; } function yy_r1_33($yy_subpatterns) { - $this->token = OQLParser::F_IF; + $this->token = OQLParser::INTERVAL; } function yy_r1_34($yy_subpatterns) { - $this->token = OQLParser::F_ELT; + $this->token = OQLParser::F_IF; } function yy_r1_35($yy_subpatterns) { - $this->token = OQLParser::F_COALESCE; + $this->token = OQLParser::F_ELT; } function yy_r1_36($yy_subpatterns) { - $this->token = OQLParser::F_ISNULL; + $this->token = OQLParser::F_COALESCE; } function yy_r1_37($yy_subpatterns) { - $this->token = OQLParser::F_CONCAT; + $this->token = OQLParser::F_ISNULL; } function yy_r1_38($yy_subpatterns) { - $this->token = OQLParser::F_SUBSTR; + $this->token = OQLParser::F_CONCAT; } function yy_r1_39($yy_subpatterns) { - $this->token = OQLParser::F_TRIM; + $this->token = OQLParser::F_SUBSTR; } function yy_r1_40($yy_subpatterns) { - $this->token = OQLParser::F_DATE; + $this->token = OQLParser::F_TRIM; } function yy_r1_41($yy_subpatterns) { - $this->token = OQLParser::F_DATE_FORMAT; + $this->token = OQLParser::F_DATE; } function yy_r1_42($yy_subpatterns) { - $this->token = OQLParser::F_CURRENT_DATE; + $this->token = OQLParser::F_DATE_FORMAT; } function yy_r1_43($yy_subpatterns) { - $this->token = OQLParser::F_NOW; + $this->token = OQLParser::F_CURRENT_DATE; } function yy_r1_44($yy_subpatterns) { - $this->token = OQLParser::F_TIME; + $this->token = OQLParser::F_NOW; } function yy_r1_45($yy_subpatterns) { - $this->token = OQLParser::F_TO_DAYS; + $this->token = OQLParser::F_TIME; } function yy_r1_46($yy_subpatterns) { - $this->token = OQLParser::F_FROM_DAYS; + $this->token = OQLParser::F_TO_DAYS; } function yy_r1_47($yy_subpatterns) { - $this->token = OQLParser::F_YEAR; + $this->token = OQLParser::F_FROM_DAYS; } function yy_r1_48($yy_subpatterns) { - $this->token = OQLParser::F_MONTH; + $this->token = OQLParser::F_YEAR; } function yy_r1_49($yy_subpatterns) { - $this->token = OQLParser::F_DAY; + $this->token = OQLParser::F_MONTH; } function yy_r1_50($yy_subpatterns) { - $this->token = OQLParser::F_HOUR; + $this->token = OQLParser::F_DAY; } function yy_r1_51($yy_subpatterns) { - $this->token = OQLParser::F_MINUTE; + $this->token = OQLParser::F_HOUR; } function yy_r1_52($yy_subpatterns) { - $this->token = OQLParser::F_SECOND; + $this->token = OQLParser::F_MINUTE; } function yy_r1_53($yy_subpatterns) { - $this->token = OQLParser::F_DATE_ADD; + $this->token = OQLParser::F_SECOND; } function yy_r1_54($yy_subpatterns) { - $this->token = OQLParser::F_DATE_SUB; + $this->token = OQLParser::F_DATE_ADD; } function yy_r1_55($yy_subpatterns) { - $this->token = OQLParser::F_ROUND; + $this->token = OQLParser::F_DATE_SUB; } function yy_r1_56($yy_subpatterns) { - $this->token = OQLParser::F_FLOOR; + $this->token = OQLParser::F_ROUND; } function yy_r1_57($yy_subpatterns) { - $this->token = OQLParser::F_INET_ATON; + $this->token = OQLParser::F_FLOOR; } function yy_r1_58($yy_subpatterns) { - $this->token = OQLParser::F_INET_NTOA; + $this->token = OQLParser::F_INET_ATON; } function yy_r1_59($yy_subpatterns) { - $this->token = OQLParser::BELOW; + $this->token = OQLParser::F_INET_NTOA; } function yy_r1_60($yy_subpatterns) { - $this->token = OQLParser::BELOW_STRICT; + $this->token = OQLParser::BELOW; } function yy_r1_61($yy_subpatterns) { - $this->token = OQLParser::NOT_BELOW; + $this->token = OQLParser::BELOW_STRICT; } function yy_r1_62($yy_subpatterns) { - $this->token = OQLParser::NOT_BELOW_STRICT; + $this->token = OQLParser::NOT_BELOW; } function yy_r1_63($yy_subpatterns) { - $this->token = OQLParser::ABOVE; + $this->token = OQLParser::NOT_BELOW_STRICT; } function yy_r1_64($yy_subpatterns) { - $this->token = OQLParser::ABOVE_STRICT; + $this->token = OQLParser::ABOVE; } function yy_r1_65($yy_subpatterns) { - $this->token = OQLParser::NOT_ABOVE; + $this->token = OQLParser::ABOVE_STRICT; } function yy_r1_66($yy_subpatterns) { - $this->token = OQLParser::NOT_ABOVE_STRICT; + $this->token = OQLParser::NOT_ABOVE; } function yy_r1_67($yy_subpatterns) { - $this->token = OQLParser::HEXVAL; + $this->token = OQLParser::NOT_ABOVE_STRICT; } function yy_r1_68($yy_subpatterns) { - $this->token = OQLParser::NUMVAL; + $this->token = OQLParser::HEXVAL; } function yy_r1_69($yy_subpatterns) { - $this->token = OQLParser::STRVAL; + $this->token = OQLParser::NUMVAL; } function yy_r1_70($yy_subpatterns) { - $this->token = OQLParser::NAME; + $this->token = OQLParser::STRVAL; } function yy_r1_71($yy_subpatterns) { - $this->token = OQLParser::VARNAME; + $this->token = OQLParser::NAME; } function yy_r1_72($yy_subpatterns) + { + + $this->token = OQLParser::VARNAME; + } + function yy_r1_73($yy_subpatterns) { $this->token = OQLParser::DOT; diff --git a/core/oql/oql-lexer.plex b/core/oql/oql-lexer.plex index 64ca65fce..fc0101b42 100644 --- a/core/oql/oql-lexer.plex +++ b/core/oql/oql-lexer.plex @@ -1,6 +1,6 @@ line %matchlongest 1 whitespace = /[ \t\n\r]+/ +union = "UNION" select = "SELECT" from = "FROM" as_alias = "AS" @@ -176,6 +177,9 @@ dot = "." whitespace { return false; } +union { + $this->token = OQLParser::UNION; +} select { $this->token = OQLParser::SELECT; } diff --git a/core/oql/oql-parser.php b/core/oql/oql-parser.php index f5587048e..e884c96cc 100644 --- a/core/oql/oql-parser.php +++ b/core/oql/oql-parser.php @@ -111,81 +111,82 @@ class OQLParserRaw#line 102 "..\oql-parser.php" ** ** Each symbol here is a terminal symbol in the grammar. */ - const SELECT = 1; - const AS_ALIAS = 2; - const FROM = 3; - const COMA = 4; - const WHERE = 5; - const JOIN = 6; - const ON = 7; - const EQ = 8; - const BELOW = 9; - const BELOW_STRICT = 10; - const NOT_BELOW = 11; - const NOT_BELOW_STRICT = 12; - const ABOVE = 13; - const ABOVE_STRICT = 14; - const NOT_ABOVE = 15; - const NOT_ABOVE_STRICT = 16; - const PAR_OPEN = 17; - const PAR_CLOSE = 18; - const INTERVAL = 19; - const F_SECOND = 20; - const F_MINUTE = 21; - const F_HOUR = 22; - const F_DAY = 23; - const F_MONTH = 24; - const F_YEAR = 25; - const DOT = 26; - const VARNAME = 27; - const NAME = 28; - const NUMVAL = 29; - const MATH_MINUS = 30; - const HEXVAL = 31; - const STRVAL = 32; - const REGEXP = 33; - const NOT_EQ = 34; - const LOG_AND = 35; - const LOG_OR = 36; - const MATH_DIV = 37; - const MATH_MULT = 38; - const MATH_PLUS = 39; - const GT = 40; - const LT = 41; - const GE = 42; - const LE = 43; - const LIKE = 44; - const NOT_LIKE = 45; - const BITWISE_LEFT_SHIFT = 46; - const BITWISE_RIGHT_SHIFT = 47; - const BITWISE_AND = 48; - const BITWISE_OR = 49; - const BITWISE_XOR = 50; - const IN = 51; - const NOT_IN = 52; - const F_IF = 53; - const F_ELT = 54; - const F_COALESCE = 55; - const F_ISNULL = 56; - const F_CONCAT = 57; - const F_SUBSTR = 58; - const F_TRIM = 59; - const F_DATE = 60; - const F_DATE_FORMAT = 61; - const F_CURRENT_DATE = 62; - const F_NOW = 63; - const F_TIME = 64; - const F_TO_DAYS = 65; - const F_FROM_DAYS = 66; - const F_DATE_ADD = 67; - const F_DATE_SUB = 68; - const F_ROUND = 69; - const F_FLOOR = 70; - const F_INET_ATON = 71; - const F_INET_NTOA = 72; - const YY_NO_ACTION = 283; - const YY_ACCEPT_ACTION = 282; - const YY_ERROR_ACTION = 281; + const UNION = 1; + const SELECT = 2; + const AS_ALIAS = 3; + const FROM = 4; + const COMA = 5; + const WHERE = 6; + const JOIN = 7; + const ON = 8; + const EQ = 9; + const BELOW = 10; + const BELOW_STRICT = 11; + const NOT_BELOW = 12; + const NOT_BELOW_STRICT = 13; + const ABOVE = 14; + const ABOVE_STRICT = 15; + const NOT_ABOVE = 16; + const NOT_ABOVE_STRICT = 17; + const PAR_OPEN = 18; + const PAR_CLOSE = 19; + const INTERVAL = 20; + const F_SECOND = 21; + const F_MINUTE = 22; + const F_HOUR = 23; + const F_DAY = 24; + const F_MONTH = 25; + const F_YEAR = 26; + const DOT = 27; + const VARNAME = 28; + const NAME = 29; + const NUMVAL = 30; + const MATH_MINUS = 31; + const HEXVAL = 32; + const STRVAL = 33; + const REGEXP = 34; + const NOT_EQ = 35; + const LOG_AND = 36; + const LOG_OR = 37; + const MATH_DIV = 38; + const MATH_MULT = 39; + const MATH_PLUS = 40; + const GT = 41; + const LT = 42; + const GE = 43; + const LE = 44; + const LIKE = 45; + const NOT_LIKE = 46; + const BITWISE_LEFT_SHIFT = 47; + const BITWISE_RIGHT_SHIFT = 48; + const BITWISE_AND = 49; + const BITWISE_OR = 50; + const BITWISE_XOR = 51; + const IN = 52; + const NOT_IN = 53; + const F_IF = 54; + const F_ELT = 55; + const F_COALESCE = 56; + const F_ISNULL = 57; + const F_CONCAT = 58; + const F_SUBSTR = 59; + const F_TRIM = 60; + const F_DATE = 61; + const F_DATE_FORMAT = 62; + const F_CURRENT_DATE = 63; + const F_NOW = 64; + const F_TIME = 65; + const F_TO_DAYS = 66; + const F_FROM_DAYS = 67; + const F_DATE_ADD = 68; + const F_DATE_SUB = 69; + const F_ROUND = 70; + const F_FLOOR = 71; + const F_INET_ATON = 72; + const F_INET_NTOA = 73; + const YY_NO_ACTION = 290; + const YY_ACCEPT_ACTION = 289; + const YY_ERROR_ACTION = 288; /* Next are that tables used to determine what action to take based on the ** current state and lookahead token. These tables are used to implement @@ -237,207 +238,211 @@ class OQLParserRaw#line 102 "..\oql-parser.php" ** shifting non-terminals after a reduce. ** self::$yy_default Default action for each state. */ - const YY_SZ_ACTTAB = 525; + const YY_SZ_ACTTAB = 549; static public $yy_action = array( - /* 0 */ 6, 41, 7, 71, 28, 39, 155, 150, 149, 122, - /* 10 */ 96, 99, 97, 61, 103, 104, 74, 38, 170, 170, - /* 20 */ 6, 39, 128, 126, 43, 46, 155, 150, 149, 100, - /* 30 */ 96, 99, 97, 61, 103, 104, 110, 111, 109, 108, - /* 40 */ 105, 106, 107, 129, 130, 153, 154, 152, 151, 148, - /* 50 */ 156, 161, 162, 160, 159, 157, 110, 111, 109, 108, - /* 60 */ 105, 106, 107, 129, 130, 153, 154, 152, 151, 148, - /* 70 */ 156, 161, 162, 160, 159, 157, 6, 30, 44, 99, - /* 80 */ 59, 68, 155, 150, 149, 82, 96, 99, 97, 61, - /* 90 */ 103, 104, 21, 22, 23, 24, 27, 25, 26, 20, - /* 100 */ 19, 146, 136, 123, 124, 50, 70, 70, 42, 57, - /* 110 */ 137, 135, 110, 111, 109, 108, 105, 106, 107, 129, - /* 120 */ 130, 153, 154, 152, 151, 148, 156, 161, 162, 160, - /* 130 */ 159, 157, 282, 134, 119, 62, 4, 121, 70, 80, - /* 140 */ 120, 114, 32, 48, 117, 115, 64, 53, 54, 75, - /* 150 */ 18, 42, 15, 8, 37, 122, 89, 13, 36, 98, - /* 160 */ 113, 112, 101, 102, 60, 62, 1, 127, 128, 126, - /* 170 */ 40, 114, 33, 48, 117, 115, 64, 58, 70, 62, - /* 180 */ 18, 223, 15, 11, 37, 76, 9, 70, 86, 39, - /* 190 */ 113, 112, 101, 102, 60, 62, 125, 158, 147, 70, - /* 200 */ 73, 114, 33, 48, 117, 115, 64, 49, 60, 3, - /* 210 */ 18, 62, 15, 62, 37, 10, 88, 55, 67, 81, - /* 220 */ 113, 112, 101, 102, 60, 232, 45, 118, 62, 95, - /* 230 */ 131, 12, 5, 232, 114, 32, 48, 117, 115, 64, - /* 240 */ 60, 52, 60, 18, 42, 15, 77, 37, 83, 232, - /* 250 */ 2, 42, 62, 113, 112, 101, 102, 60, 114, 34, - /* 260 */ 48, 117, 115, 64, 116, 63, 62, 18, 232, 15, - /* 270 */ 8, 37, 79, 51, 56, 232, 42, 113, 112, 101, - /* 280 */ 102, 60, 62, 232, 127, 232, 232, 232, 114, 29, - /* 290 */ 48, 117, 115, 64, 70, 60, 232, 18, 62, 15, - /* 300 */ 232, 37, 232, 72, 55, 232, 62, 113, 112, 101, - /* 310 */ 102, 60, 114, 16, 48, 117, 115, 64, 232, 232, - /* 320 */ 232, 18, 232, 15, 232, 37, 232, 60, 232, 232, - /* 330 */ 62, 113, 112, 101, 102, 60, 114, 31, 48, 117, - /* 340 */ 115, 64, 232, 232, 62, 18, 232, 15, 232, 37, - /* 350 */ 85, 232, 232, 232, 232, 113, 112, 101, 102, 60, - /* 360 */ 62, 232, 232, 232, 232, 232, 114, 232, 48, 117, - /* 370 */ 115, 64, 232, 60, 232, 18, 62, 15, 232, 35, - /* 380 */ 232, 232, 66, 232, 62, 113, 112, 101, 102, 60, - /* 390 */ 114, 232, 48, 117, 115, 64, 232, 232, 232, 18, - /* 400 */ 232, 14, 232, 232, 232, 60, 232, 232, 62, 113, - /* 410 */ 112, 101, 102, 60, 114, 62, 48, 117, 115, 64, - /* 420 */ 133, 94, 232, 17, 232, 232, 232, 232, 232, 232, - /* 430 */ 232, 232, 232, 113, 112, 101, 102, 60, 232, 232, - /* 440 */ 232, 232, 144, 232, 60, 132, 138, 232, 232, 232, - /* 450 */ 232, 139, 145, 143, 142, 140, 141, 163, 232, 232, - /* 460 */ 232, 232, 62, 232, 232, 232, 232, 232, 114, 232, - /* 470 */ 47, 117, 115, 64, 232, 232, 78, 90, 91, 93, - /* 480 */ 92, 87, 232, 232, 232, 232, 232, 113, 112, 101, - /* 490 */ 102, 60, 122, 62, 62, 62, 232, 232, 232, 69, - /* 500 */ 65, 84, 232, 232, 232, 128, 126, 232, 232, 232, - /* 510 */ 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, - /* 520 */ 232, 232, 60, 60, 60, + /* 0 */ 7, 30, 6, 66, 60, 4, 153, 152, 155, 98, + /* 10 */ 107, 106, 105, 65, 101, 102, 25, 22, 23, 21, + /* 20 */ 19, 27, 26, 28, 24, 162, 151, 43, 177, 177, + /* 30 */ 80, 41, 62, 85, 150, 140, 103, 108, 109, 114, + /* 40 */ 115, 113, 112, 110, 111, 133, 134, 157, 158, 156, + /* 50 */ 154, 159, 160, 165, 166, 164, 20, 121, 125, 3, + /* 60 */ 68, 70, 69, 75, 99, 93, 142, 66, 76, 64, + /* 70 */ 119, 120, 7, 79, 50, 122, 121, 42, 153, 152, + /* 80 */ 155, 139, 107, 106, 105, 65, 101, 102, 149, 119, + /* 90 */ 120, 137, 143, 121, 132, 130, 62, 148, 147, 100, + /* 100 */ 146, 144, 145, 167, 61, 116, 119, 120, 103, 108, + /* 110 */ 109, 114, 115, 113, 112, 110, 111, 133, 134, 157, + /* 120 */ 158, 156, 154, 159, 160, 165, 166, 164, 7, 106, + /* 130 */ 11, 66, 9, 80, 153, 152, 155, 81, 107, 106, + /* 140 */ 105, 65, 101, 102, 163, 161, 72, 8, 10, 88, + /* 150 */ 78, 1, 46, 38, 44, 104, 54, 51, 41, 42, + /* 160 */ 62, 118, 135, 136, 103, 108, 109, 114, 115, 113, + /* 170 */ 112, 110, 111, 133, 134, 157, 158, 156, 154, 159, + /* 180 */ 160, 165, 166, 164, 289, 138, 67, 86, 66, 39, + /* 190 */ 87, 58, 37, 45, 126, 34, 48, 131, 124, 63, + /* 200 */ 13, 40, 66, 17, 230, 15, 12, 36, 97, 2, + /* 210 */ 47, 55, 41, 129, 127, 128, 117, 62, 66, 80, + /* 220 */ 80, 80, 80, 123, 126, 31, 48, 131, 124, 63, + /* 230 */ 59, 62, 237, 17, 91, 15, 20, 36, 73, 80, + /* 240 */ 237, 92, 237, 129, 127, 128, 117, 62, 66, 237, + /* 250 */ 94, 74, 237, 42, 126, 31, 48, 131, 124, 63, + /* 260 */ 237, 237, 66, 17, 66, 15, 66, 36, 83, 95, + /* 270 */ 56, 77, 82, 129, 127, 128, 117, 62, 141, 66, + /* 280 */ 237, 237, 237, 237, 237, 126, 34, 48, 131, 124, + /* 290 */ 63, 62, 237, 62, 17, 62, 15, 52, 36, 5, + /* 300 */ 42, 237, 237, 237, 129, 127, 128, 117, 62, 66, + /* 310 */ 237, 8, 237, 71, 237, 126, 32, 48, 131, 124, + /* 320 */ 63, 237, 237, 66, 17, 118, 15, 53, 36, 89, + /* 330 */ 42, 57, 237, 237, 129, 127, 128, 117, 62, 66, + /* 340 */ 237, 237, 237, 237, 237, 126, 16, 48, 131, 124, + /* 350 */ 63, 237, 62, 66, 17, 237, 15, 237, 36, 90, + /* 360 */ 237, 237, 237, 237, 129, 127, 128, 117, 62, 66, + /* 370 */ 237, 237, 237, 237, 237, 126, 29, 48, 131, 124, + /* 380 */ 63, 237, 62, 237, 17, 237, 15, 237, 36, 237, + /* 390 */ 237, 237, 237, 237, 129, 127, 128, 117, 62, 66, + /* 400 */ 237, 237, 237, 237, 237, 126, 33, 48, 131, 124, + /* 410 */ 63, 237, 237, 237, 17, 237, 15, 237, 36, 237, + /* 420 */ 237, 237, 237, 237, 129, 127, 128, 117, 62, 66, + /* 430 */ 237, 237, 237, 237, 237, 126, 237, 48, 131, 124, + /* 440 */ 63, 237, 237, 237, 17, 237, 15, 237, 35, 237, + /* 450 */ 237, 237, 237, 237, 129, 127, 128, 117, 62, 66, + /* 460 */ 237, 237, 237, 237, 237, 126, 237, 48, 131, 124, + /* 470 */ 63, 237, 237, 237, 17, 237, 14, 66, 237, 237, + /* 480 */ 237, 237, 84, 56, 129, 127, 128, 117, 62, 66, + /* 490 */ 237, 237, 237, 237, 237, 126, 237, 48, 131, 124, + /* 500 */ 63, 237, 237, 237, 18, 66, 62, 237, 237, 237, + /* 510 */ 237, 96, 237, 237, 129, 127, 128, 117, 62, 66, + /* 520 */ 237, 237, 237, 237, 237, 126, 237, 49, 131, 124, + /* 530 */ 63, 237, 237, 237, 62, 237, 237, 237, 237, 237, + /* 540 */ 237, 237, 237, 237, 129, 127, 128, 117, 62, ); static public $yy_lookahead = array( - /* 0 */ 17, 2, 19, 35, 1, 6, 23, 24, 25, 36, - /* 10 */ 27, 28, 29, 30, 31, 32, 48, 2, 3, 4, - /* 20 */ 17, 6, 49, 50, 3, 4, 23, 24, 25, 106, - /* 30 */ 27, 28, 29, 30, 31, 32, 53, 54, 55, 56, - /* 40 */ 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, - /* 50 */ 67, 68, 69, 70, 71, 72, 53, 54, 55, 56, - /* 60 */ 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, - /* 70 */ 67, 68, 69, 70, 71, 72, 17, 77, 77, 28, - /* 80 */ 80, 79, 23, 24, 25, 79, 27, 28, 29, 30, - /* 90 */ 31, 32, 8, 9, 10, 11, 12, 13, 14, 15, - /* 100 */ 16, 37, 38, 51, 52, 78, 106, 106, 81, 77, - /* 110 */ 46, 47, 53, 54, 55, 56, 57, 58, 59, 60, - /* 120 */ 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, - /* 130 */ 71, 72, 74, 75, 76, 77, 5, 18, 106, 79, - /* 140 */ 79, 83, 84, 85, 86, 87, 88, 26, 78, 77, - /* 150 */ 92, 81, 94, 98, 96, 36, 101, 7, 77, 29, - /* 160 */ 102, 103, 104, 105, 106, 77, 17, 112, 49, 50, - /* 170 */ 77, 83, 84, 85, 86, 87, 88, 89, 106, 77, - /* 180 */ 92, 26, 94, 93, 96, 83, 97, 106, 100, 6, - /* 190 */ 102, 103, 104, 105, 106, 77, 91, 107, 108, 106, - /* 200 */ 111, 83, 84, 85, 86, 87, 88, 90, 106, 17, - /* 210 */ 92, 77, 94, 77, 96, 95, 82, 83, 100, 83, - /* 220 */ 102, 103, 104, 105, 106, 113, 2, 76, 77, 109, - /* 230 */ 110, 7, 4, 113, 83, 84, 85, 86, 87, 88, - /* 240 */ 106, 78, 106, 92, 81, 94, 18, 96, 78, 113, - /* 250 */ 4, 81, 77, 102, 103, 104, 105, 106, 83, 84, - /* 260 */ 85, 86, 87, 88, 18, 77, 77, 92, 113, 94, - /* 270 */ 98, 96, 83, 78, 99, 113, 81, 102, 103, 104, - /* 280 */ 105, 106, 77, 113, 112, 113, 113, 113, 83, 84, - /* 290 */ 85, 86, 87, 88, 106, 106, 113, 92, 77, 94, - /* 300 */ 113, 96, 113, 82, 83, 113, 77, 102, 103, 104, - /* 310 */ 105, 106, 83, 84, 85, 86, 87, 88, 113, 113, - /* 320 */ 113, 92, 113, 94, 113, 96, 113, 106, 113, 113, - /* 330 */ 77, 102, 103, 104, 105, 106, 83, 84, 85, 86, - /* 340 */ 87, 88, 113, 113, 77, 92, 113, 94, 113, 96, - /* 350 */ 83, 113, 113, 113, 113, 102, 103, 104, 105, 106, - /* 360 */ 77, 113, 113, 113, 113, 113, 83, 113, 85, 86, - /* 370 */ 87, 88, 113, 106, 113, 92, 77, 94, 113, 96, - /* 380 */ 113, 113, 83, 113, 77, 102, 103, 104, 105, 106, - /* 390 */ 83, 113, 85, 86, 87, 88, 113, 113, 113, 92, - /* 400 */ 113, 94, 113, 113, 113, 106, 113, 113, 77, 102, - /* 410 */ 103, 104, 105, 106, 83, 77, 85, 86, 87, 88, - /* 420 */ 8, 83, 113, 92, 113, 113, 113, 113, 113, 113, - /* 430 */ 113, 113, 113, 102, 103, 104, 105, 106, 113, 113, - /* 440 */ 113, 113, 30, 113, 106, 33, 34, 113, 113, 113, - /* 450 */ 113, 39, 40, 41, 42, 43, 44, 45, 113, 113, - /* 460 */ 113, 113, 77, 113, 113, 113, 113, 113, 83, 113, - /* 470 */ 85, 86, 87, 88, 113, 113, 20, 21, 22, 23, - /* 480 */ 24, 25, 113, 113, 113, 113, 113, 102, 103, 104, - /* 490 */ 105, 106, 36, 77, 77, 77, 113, 113, 113, 83, - /* 500 */ 83, 83, 113, 113, 113, 49, 50, 113, 113, 113, - /* 510 */ 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, - /* 520 */ 113, 113, 106, 106, 106, + /* 0 */ 18, 79, 20, 79, 82, 6, 24, 25, 26, 85, + /* 10 */ 28, 29, 30, 31, 32, 33, 9, 10, 11, 12, + /* 20 */ 13, 14, 15, 16, 17, 38, 39, 3, 4, 5, + /* 30 */ 108, 7, 108, 81, 47, 48, 54, 55, 56, 57, + /* 40 */ 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, + /* 50 */ 68, 69, 70, 71, 72, 73, 2, 37, 93, 18, + /* 60 */ 21, 22, 23, 24, 25, 26, 9, 79, 76, 77, + /* 70 */ 50, 51, 18, 85, 80, 19, 37, 83, 24, 25, + /* 80 */ 26, 81, 28, 29, 30, 31, 32, 33, 31, 50, + /* 90 */ 51, 34, 35, 37, 52, 53, 108, 40, 41, 42, + /* 100 */ 43, 44, 45, 46, 79, 108, 50, 51, 54, 55, + /* 110 */ 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, + /* 120 */ 66, 67, 68, 69, 70, 71, 72, 73, 18, 29, + /* 130 */ 95, 79, 99, 108, 24, 25, 26, 85, 28, 29, + /* 140 */ 30, 31, 32, 33, 109, 110, 113, 100, 97, 81, + /* 150 */ 103, 18, 4, 5, 3, 30, 80, 27, 7, 83, + /* 160 */ 108, 114, 111, 112, 54, 55, 56, 57, 58, 59, + /* 170 */ 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, + /* 180 */ 70, 71, 72, 73, 75, 76, 77, 78, 79, 1, + /* 190 */ 79, 79, 79, 79, 85, 86, 87, 88, 89, 90, + /* 200 */ 8, 3, 79, 94, 27, 96, 8, 98, 85, 5, + /* 210 */ 79, 92, 7, 104, 105, 106, 107, 108, 79, 108, + /* 220 */ 108, 108, 108, 19, 85, 86, 87, 88, 89, 90, + /* 230 */ 91, 108, 115, 94, 81, 96, 2, 98, 36, 108, + /* 240 */ 115, 102, 115, 104, 105, 106, 107, 108, 79, 115, + /* 250 */ 80, 49, 115, 83, 85, 86, 87, 88, 89, 90, + /* 260 */ 115, 115, 79, 94, 79, 96, 79, 98, 85, 84, + /* 270 */ 85, 102, 85, 104, 105, 106, 107, 108, 78, 79, + /* 280 */ 115, 115, 115, 115, 115, 85, 86, 87, 88, 89, + /* 290 */ 90, 108, 115, 108, 94, 108, 96, 80, 98, 5, + /* 300 */ 83, 115, 115, 115, 104, 105, 106, 107, 108, 79, + /* 310 */ 115, 100, 115, 19, 115, 85, 86, 87, 88, 89, + /* 320 */ 90, 115, 115, 79, 94, 114, 96, 80, 98, 85, + /* 330 */ 83, 101, 115, 115, 104, 105, 106, 107, 108, 79, + /* 340 */ 115, 115, 115, 115, 115, 85, 86, 87, 88, 89, + /* 350 */ 90, 115, 108, 79, 94, 115, 96, 115, 98, 85, + /* 360 */ 115, 115, 115, 115, 104, 105, 106, 107, 108, 79, + /* 370 */ 115, 115, 115, 115, 115, 85, 86, 87, 88, 89, + /* 380 */ 90, 115, 108, 115, 94, 115, 96, 115, 98, 115, + /* 390 */ 115, 115, 115, 115, 104, 105, 106, 107, 108, 79, + /* 400 */ 115, 115, 115, 115, 115, 85, 86, 87, 88, 89, + /* 410 */ 90, 115, 115, 115, 94, 115, 96, 115, 98, 115, + /* 420 */ 115, 115, 115, 115, 104, 105, 106, 107, 108, 79, + /* 430 */ 115, 115, 115, 115, 115, 85, 115, 87, 88, 89, + /* 440 */ 90, 115, 115, 115, 94, 115, 96, 115, 98, 115, + /* 450 */ 115, 115, 115, 115, 104, 105, 106, 107, 108, 79, + /* 460 */ 115, 115, 115, 115, 115, 85, 115, 87, 88, 89, + /* 470 */ 90, 115, 115, 115, 94, 115, 96, 79, 115, 115, + /* 480 */ 115, 115, 84, 85, 104, 105, 106, 107, 108, 79, + /* 490 */ 115, 115, 115, 115, 115, 85, 115, 87, 88, 89, + /* 500 */ 90, 115, 115, 115, 94, 79, 108, 115, 115, 115, + /* 510 */ 115, 85, 115, 115, 104, 105, 106, 107, 108, 79, + /* 520 */ 115, 115, 115, 115, 115, 85, 115, 87, 88, 89, + /* 530 */ 90, 115, 115, 115, 108, 115, 115, 115, 115, 115, + /* 540 */ 115, 115, 115, 115, 104, 105, 106, 107, 108, ); - const YY_SHIFT_USE_DFLT = -33; - const YY_SHIFT_MAX = 64; + const YY_SHIFT_USE_DFLT = -19; + const YY_SHIFT_MAX = 67; static public $yy_shift_ofst = array( - /* 0 */ 3, -17, -17, 59, 59, 59, 59, 59, 59, 59, - /* 10 */ 59, 59, 51, 51, 412, 412, 456, 64, 64, 51, - /* 20 */ 51, 51, 51, 51, 51, 51, 51, 51, 51, 119, - /* 30 */ 15, -27, -27, -27, -27, -32, -1, -32, 51, 51, - /* 40 */ 183, 51, 183, 51, 183, 51, 51, 52, 52, 192, - /* 50 */ 131, 131, 131, 51, 131, 84, 228, 224, 246, 21, - /* 60 */ 155, 130, 121, 150, 149, + /* 0 */ 54, -18, -18, 110, 110, 110, 110, 110, 110, 110, + /* 10 */ 110, 110, 100, 100, 57, 57, 39, -13, -13, 100, + /* 20 */ 100, 100, 100, 100, 100, 100, 100, 100, 100, 56, + /* 30 */ 24, 20, 20, 20, 20, 202, 202, 151, 100, 234, + /* 40 */ 100, 100, 205, 100, 100, 205, 100, 205, 42, 42, + /* 50 */ -1, 100, -1, -1, -1, 41, 7, 294, 198, 204, + /* 60 */ 148, 192, 177, 133, 188, 125, 130, 188, ); - const YY_REDUCE_USE_DFLT = -78; - const YY_REDUCE_MAX = 54; + const YY_REDUCE_USE_DFLT = -79; + const YY_REDUCE_MAX = 55; static public $yy_reduce_ofst = array( - /* 0 */ 58, 88, 118, 175, 151, 253, 205, 229, 283, 307, - /* 10 */ 331, 385, 221, 134, 120, 120, 55, 90, 90, 416, - /* 20 */ 417, 418, 267, 189, 136, 102, 299, 338, 0, 172, - /* 30 */ 195, 172, 172, 172, 172, 89, 27, 89, 1, 32, - /* 40 */ 70, 93, 170, 81, 163, 188, 72, 117, 117, 105, - /* 50 */ 60, 61, 6, -77, 2, + /* 0 */ 109, 139, 169, 230, 200, 320, 260, 290, 350, 380, + /* 10 */ 410, 440, 398, 185, 51, 51, 47, 35, 35, 274, + /* 20 */ -78, 426, -12, 123, 52, -76, 183, 244, 187, 211, + /* 30 */ 247, 211, 211, 211, 211, 33, 33, 76, 111, -8, + /* 40 */ 25, 112, 170, 131, 114, 217, 113, -6, 119, 119, + /* 50 */ 153, -3, 68, 0, -48, -35, ); static public $yyExpectedTokens = array( - /* 0 */ array(1, 17, 23, 24, 25, 27, 28, 29, 30, 31, 32, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, ), - /* 1 */ array(17, 19, 23, 24, 25, 27, 28, 29, 30, 31, 32, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, ), - /* 2 */ array(17, 19, 23, 24, 25, 27, 28, 29, 30, 31, 32, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, ), - /* 3 */ array(17, 23, 24, 25, 27, 28, 29, 30, 31, 32, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, ), - /* 4 */ array(17, 23, 24, 25, 27, 28, 29, 30, 31, 32, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, ), - /* 5 */ array(17, 23, 24, 25, 27, 28, 29, 30, 31, 32, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, ), - /* 6 */ array(17, 23, 24, 25, 27, 28, 29, 30, 31, 32, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, ), - /* 7 */ array(17, 23, 24, 25, 27, 28, 29, 30, 31, 32, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, ), - /* 8 */ array(17, 23, 24, 25, 27, 28, 29, 30, 31, 32, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, ), - /* 9 */ array(17, 23, 24, 25, 27, 28, 29, 30, 31, 32, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, ), - /* 10 */ array(17, 23, 24, 25, 27, 28, 29, 30, 31, 32, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, ), - /* 11 */ array(17, 23, 24, 25, 27, 28, 29, 30, 31, 32, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, ), - /* 12 */ array(28, ), - /* 13 */ array(28, ), - /* 14 */ array(8, 30, 33, 34, 39, 40, 41, 42, 43, 44, 45, ), - /* 15 */ array(8, 30, 33, 34, 39, 40, 41, 42, 43, 44, 45, ), - /* 16 */ array(20, 21, 22, 23, 24, 25, 36, 49, 50, ), - /* 17 */ array(37, 38, 46, 47, ), - /* 18 */ array(37, 38, 46, 47, ), - /* 19 */ array(28, ), - /* 20 */ array(28, ), - /* 21 */ array(28, ), - /* 22 */ array(28, ), - /* 23 */ array(28, ), - /* 24 */ array(28, ), - /* 25 */ array(28, ), - /* 26 */ array(28, ), - /* 27 */ array(28, ), - /* 28 */ array(28, ), - /* 29 */ array(18, 36, 49, 50, ), - /* 30 */ array(2, 3, 4, 6, ), - /* 31 */ array(36, 49, 50, ), - /* 32 */ array(36, 49, 50, ), - /* 33 */ array(36, 49, 50, ), - /* 34 */ array(36, 49, 50, ), - /* 35 */ array(35, 48, ), - /* 36 */ array(2, 6, ), - /* 37 */ array(35, 48, ), - /* 38 */ array(28, ), - /* 39 */ array(28, ), - /* 40 */ array(6, ), - /* 41 */ array(28, ), - /* 42 */ array(6, ), - /* 43 */ array(28, ), - /* 44 */ array(6, ), - /* 45 */ array(28, ), - /* 46 */ array(28, ), - /* 47 */ array(51, 52, ), - /* 48 */ array(51, 52, ), - /* 49 */ array(17, ), - /* 50 */ array(5, ), - /* 51 */ array(5, ), - /* 52 */ array(5, ), - /* 53 */ array(28, ), - /* 54 */ array(5, ), - /* 55 */ array(8, 9, 10, 11, 12, 13, 14, 15, 16, ), - /* 56 */ array(4, 18, ), - /* 57 */ array(2, 7, ), - /* 58 */ array(4, 18, ), - /* 59 */ array(3, 4, ), - /* 60 */ array(26, ), - /* 61 */ array(29, ), - /* 62 */ array(26, ), - /* 63 */ array(7, ), - /* 64 */ array(17, ), - /* 65 */ array(), - /* 66 */ array(), - /* 67 */ array(), + /* 0 */ array(2, 18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), + /* 1 */ array(18, 20, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), + /* 2 */ array(18, 20, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), + /* 3 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), + /* 4 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), + /* 5 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), + /* 6 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), + /* 7 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), + /* 8 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), + /* 9 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), + /* 10 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), + /* 11 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), + /* 12 */ array(29, ), + /* 13 */ array(29, ), + /* 14 */ array(9, 31, 34, 35, 40, 41, 42, 43, 44, 45, 46, ), + /* 15 */ array(9, 31, 34, 35, 40, 41, 42, 43, 44, 45, 46, ), + /* 16 */ array(21, 22, 23, 24, 25, 26, 37, 50, 51, ), + /* 17 */ array(38, 39, 47, 48, ), + /* 18 */ array(38, 39, 47, 48, ), + /* 19 */ array(29, ), + /* 20 */ array(29, ), + /* 21 */ array(29, ), + /* 22 */ array(29, ), + /* 23 */ array(29, ), + /* 24 */ array(29, ), + /* 25 */ array(29, ), + /* 26 */ array(29, ), + /* 27 */ array(29, ), + /* 28 */ array(29, ), + /* 29 */ array(19, 37, 50, 51, ), + /* 30 */ array(3, 4, 5, 7, ), + /* 31 */ array(37, 50, 51, ), + /* 32 */ array(37, 50, 51, ), + /* 33 */ array(37, 50, 51, ), + /* 34 */ array(37, 50, 51, ), + /* 35 */ array(36, 49, ), + /* 36 */ array(36, 49, ), + /* 37 */ array(3, 7, ), + /* 38 */ array(29, ), + /* 39 */ array(2, ), + /* 40 */ array(29, ), + /* 41 */ array(29, ), + /* 42 */ array(7, ), + /* 43 */ array(29, ), + /* 44 */ array(29, ), + /* 45 */ array(7, ), + /* 46 */ array(29, ), + /* 47 */ array(7, ), + /* 48 */ array(52, 53, ), + /* 49 */ array(52, 53, ), + /* 50 */ array(6, ), + /* 51 */ array(29, ), + /* 52 */ array(6, ), + /* 53 */ array(6, ), + /* 54 */ array(6, ), + /* 55 */ array(18, ), + /* 56 */ array(9, 10, 11, 12, 13, 14, 15, 16, 17, ), + /* 57 */ array(5, 19, ), + /* 58 */ array(3, 8, ), + /* 59 */ array(5, 19, ), + /* 60 */ array(4, 5, ), + /* 61 */ array(8, ), + /* 62 */ array(27, ), + /* 63 */ array(18, ), + /* 64 */ array(1, ), + /* 65 */ array(30, ), + /* 66 */ array(27, ), + /* 67 */ array(1, ), /* 68 */ array(), /* 69 */ array(), /* 70 */ array(), @@ -534,25 +539,29 @@ static public $yy_action = array( /* 161 */ array(), /* 162 */ array(), /* 163 */ array(), + /* 164 */ array(), + /* 165 */ array(), + /* 166 */ array(), + /* 167 */ array(), ); static public $yy_default = array( - /* 0 */ 281, 206, 281, 281, 281, 281, 281, 281, 281, 281, - /* 10 */ 281, 281, 281, 281, 200, 199, 281, 198, 197, 281, - /* 20 */ 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, - /* 30 */ 176, 205, 188, 209, 204, 202, 176, 201, 281, 281, - /* 40 */ 176, 281, 175, 281, 176, 281, 281, 196, 195, 281, - /* 50 */ 173, 173, 173, 281, 173, 281, 281, 281, 281, 281, - /* 60 */ 221, 281, 281, 281, 281, 186, 185, 208, 169, 187, - /* 70 */ 223, 237, 178, 238, 253, 171, 184, 203, 211, 181, - /* 80 */ 168, 182, 167, 174, 179, 180, 207, 216, 177, 210, - /* 90 */ 212, 213, 215, 214, 183, 232, 224, 226, 227, 225, - /* 100 */ 222, 219, 220, 228, 229, 262, 263, 264, 261, 260, - /* 110 */ 258, 259, 218, 217, 190, 191, 192, 189, 172, 165, - /* 120 */ 166, 193, 239, 256, 257, 194, 255, 240, 254, 265, - /* 130 */ 266, 233, 234, 235, 164, 252, 242, 251, 236, 243, - /* 140 */ 248, 249, 247, 246, 244, 245, 241, 231, 271, 272, - /* 150 */ 273, 270, 269, 267, 268, 274, 275, 280, 230, 279, - /* 160 */ 278, 276, 277, 250, + /* 0 */ 288, 213, 288, 288, 288, 288, 288, 288, 288, 288, + /* 10 */ 288, 288, 288, 288, 207, 206, 288, 204, 205, 288, + /* 20 */ 288, 288, 288, 288, 288, 288, 288, 288, 288, 288, + /* 30 */ 183, 216, 211, 212, 195, 209, 208, 183, 288, 288, + /* 40 */ 288, 288, 182, 288, 288, 183, 288, 183, 202, 203, + /* 50 */ 180, 288, 180, 180, 180, 288, 288, 288, 288, 288, + /* 60 */ 288, 288, 228, 288, 171, 288, 288, 169, 218, 220, + /* 70 */ 219, 210, 245, 244, 260, 221, 172, 215, 217, 187, + /* 80 */ 230, 194, 193, 192, 185, 175, 170, 178, 176, 191, + /* 90 */ 190, 174, 214, 223, 181, 184, 189, 188, 186, 222, + /* 100 */ 253, 235, 236, 265, 234, 233, 232, 231, 266, 267, + /* 110 */ 272, 273, 271, 270, 268, 269, 229, 227, 247, 261, + /* 120 */ 262, 246, 200, 199, 198, 201, 197, 225, 226, 224, + /* 130 */ 264, 196, 263, 274, 275, 239, 240, 241, 168, 173, + /* 140 */ 259, 179, 242, 243, 255, 256, 254, 252, 250, 251, + /* 150 */ 258, 249, 280, 281, 282, 279, 278, 276, 277, 283, + /* 160 */ 284, 238, 248, 237, 287, 285, 286, 257, ); /* The next thing included is series of defines which control ** various aspects of the generated parser. @@ -569,11 +578,11 @@ static public $yy_action = array( ** self::YYERRORSYMBOL is the code number of the error symbol. If not ** defined, then do no error processing. */ - const YYNOCODE = 114; + const YYNOCODE = 116; const YYSTACKDEPTH = 100; - const YYNSTATE = 164; - const YYNRULE = 117; - const YYERRORSYMBOL = 73; + const YYNSTATE = 168; + const YYNRULE = 120; + const YYERRORSYMBOL = 74; const YYERRSYMDT = 'yy0'; const YYFALLBACK = 0; /** The next table maps tokens into fallback tokens. If a construct @@ -655,35 +664,35 @@ static public $yy_action = array( * @var array */ static public $yyTokenName = array( - '$', 'SELECT', 'AS_ALIAS', 'FROM', - 'COMA', 'WHERE', 'JOIN', 'ON', - 'EQ', 'BELOW', 'BELOW_STRICT', 'NOT_BELOW', - 'NOT_BELOW_STRICT', 'ABOVE', 'ABOVE_STRICT', 'NOT_ABOVE', - 'NOT_ABOVE_STRICT', 'PAR_OPEN', 'PAR_CLOSE', 'INTERVAL', - 'F_SECOND', 'F_MINUTE', 'F_HOUR', 'F_DAY', - 'F_MONTH', 'F_YEAR', 'DOT', 'VARNAME', - 'NAME', 'NUMVAL', 'MATH_MINUS', 'HEXVAL', - 'STRVAL', 'REGEXP', 'NOT_EQ', 'LOG_AND', - 'LOG_OR', 'MATH_DIV', 'MATH_MULT', 'MATH_PLUS', - 'GT', 'LT', 'GE', 'LE', - 'LIKE', 'NOT_LIKE', 'BITWISE_LEFT_SHIFT', 'BITWISE_RIGHT_SHIFT', - 'BITWISE_AND', 'BITWISE_OR', 'BITWISE_XOR', 'IN', - 'NOT_IN', 'F_IF', 'F_ELT', 'F_COALESCE', - 'F_ISNULL', 'F_CONCAT', 'F_SUBSTR', 'F_TRIM', - 'F_DATE', 'F_DATE_FORMAT', 'F_CURRENT_DATE', 'F_NOW', - 'F_TIME', 'F_TO_DAYS', 'F_FROM_DAYS', 'F_DATE_ADD', - 'F_DATE_SUB', 'F_ROUND', 'F_FLOOR', 'F_INET_ATON', - 'F_INET_NTOA', 'error', 'result', 'query', - 'condition', 'class_name', 'join_statement', 'where_statement', - 'class_list', 'join_item', 'join_condition', 'field_id', - 'expression_prio4', 'expression_basic', 'scalar', 'var_name', - 'func_name', 'arg_list', 'list_operator', 'list', - 'expression_prio1', 'operator1', 'expression_prio2', 'operator2', - 'expression_prio3', 'operator3', 'operator4', 'list_items', - 'argument', 'interval_unit', 'num_scalar', 'str_scalar', - 'num_value', 'str_value', 'name', 'num_operator1', - 'bitwise_operator1', 'num_operator2', 'str_operator', 'bitwise_operator3', - 'bitwise_operator4', + '$', 'UNION', 'SELECT', 'AS_ALIAS', + 'FROM', 'COMA', 'WHERE', 'JOIN', + 'ON', 'EQ', 'BELOW', 'BELOW_STRICT', + 'NOT_BELOW', 'NOT_BELOW_STRICT', 'ABOVE', 'ABOVE_STRICT', + 'NOT_ABOVE', 'NOT_ABOVE_STRICT', 'PAR_OPEN', 'PAR_CLOSE', + 'INTERVAL', 'F_SECOND', 'F_MINUTE', 'F_HOUR', + 'F_DAY', 'F_MONTH', 'F_YEAR', 'DOT', + 'VARNAME', 'NAME', 'NUMVAL', 'MATH_MINUS', + 'HEXVAL', 'STRVAL', 'REGEXP', 'NOT_EQ', + 'LOG_AND', 'LOG_OR', 'MATH_DIV', 'MATH_MULT', + 'MATH_PLUS', 'GT', 'LT', 'GE', + 'LE', 'LIKE', 'NOT_LIKE', 'BITWISE_LEFT_SHIFT', + 'BITWISE_RIGHT_SHIFT', 'BITWISE_AND', 'BITWISE_OR', 'BITWISE_XOR', + 'IN', 'NOT_IN', 'F_IF', 'F_ELT', + 'F_COALESCE', 'F_ISNULL', 'F_CONCAT', 'F_SUBSTR', + 'F_TRIM', 'F_DATE', 'F_DATE_FORMAT', 'F_CURRENT_DATE', + 'F_NOW', 'F_TIME', 'F_TO_DAYS', 'F_FROM_DAYS', + 'F_DATE_ADD', 'F_DATE_SUB', 'F_ROUND', 'F_FLOOR', + 'F_INET_ATON', 'F_INET_NTOA', 'error', 'result', + 'union', 'query', 'condition', 'class_name', + 'join_statement', 'where_statement', 'class_list', 'join_item', + 'join_condition', 'field_id', 'expression_prio4', 'expression_basic', + 'scalar', 'var_name', 'func_name', 'arg_list', + 'list_operator', 'list', 'expression_prio1', 'operator1', + 'expression_prio2', 'operator2', 'expression_prio3', 'operator3', + 'operator4', 'list_items', 'argument', 'interval_unit', + 'num_scalar', 'str_scalar', 'num_value', 'str_value', + 'name', 'num_operator1', 'bitwise_operator1', 'num_operator2', + 'str_operator', 'bitwise_operator3', 'bitwise_operator4', ); /** @@ -691,123 +700,126 @@ static public $yy_action = array( * @var array */ static public $yyRuleName = array( - /* 0 */ "result ::= query", - /* 1 */ "result ::= condition", - /* 2 */ "query ::= SELECT class_name join_statement where_statement", - /* 3 */ "query ::= SELECT class_name AS_ALIAS class_name join_statement where_statement", - /* 4 */ "query ::= SELECT class_list FROM class_name join_statement where_statement", - /* 5 */ "query ::= SELECT class_list FROM class_name AS_ALIAS class_name join_statement where_statement", - /* 6 */ "class_list ::= class_name", - /* 7 */ "class_list ::= class_list COMA class_name", - /* 8 */ "where_statement ::= WHERE condition", - /* 9 */ "where_statement ::=", - /* 10 */ "join_statement ::= join_item join_statement", - /* 11 */ "join_statement ::= join_item", - /* 12 */ "join_statement ::=", - /* 13 */ "join_item ::= JOIN class_name AS_ALIAS class_name ON join_condition", - /* 14 */ "join_item ::= JOIN class_name ON join_condition", - /* 15 */ "join_condition ::= field_id EQ field_id", - /* 16 */ "join_condition ::= field_id BELOW field_id", - /* 17 */ "join_condition ::= field_id BELOW_STRICT field_id", - /* 18 */ "join_condition ::= field_id NOT_BELOW field_id", - /* 19 */ "join_condition ::= field_id NOT_BELOW_STRICT field_id", - /* 20 */ "join_condition ::= field_id ABOVE field_id", - /* 21 */ "join_condition ::= field_id ABOVE_STRICT field_id", - /* 22 */ "join_condition ::= field_id NOT_ABOVE field_id", - /* 23 */ "join_condition ::= field_id NOT_ABOVE_STRICT field_id", - /* 24 */ "condition ::= expression_prio4", - /* 25 */ "expression_basic ::= scalar", - /* 26 */ "expression_basic ::= field_id", - /* 27 */ "expression_basic ::= var_name", - /* 28 */ "expression_basic ::= func_name PAR_OPEN arg_list PAR_CLOSE", - /* 29 */ "expression_basic ::= PAR_OPEN expression_prio4 PAR_CLOSE", - /* 30 */ "expression_basic ::= expression_basic list_operator list", - /* 31 */ "expression_prio1 ::= expression_basic", - /* 32 */ "expression_prio1 ::= expression_prio1 operator1 expression_basic", - /* 33 */ "expression_prio2 ::= expression_prio1", - /* 34 */ "expression_prio2 ::= expression_prio2 operator2 expression_prio1", - /* 35 */ "expression_prio3 ::= expression_prio2", - /* 36 */ "expression_prio3 ::= expression_prio3 operator3 expression_prio2", - /* 37 */ "expression_prio4 ::= expression_prio3", - /* 38 */ "expression_prio4 ::= expression_prio4 operator4 expression_prio3", - /* 39 */ "list ::= PAR_OPEN list_items PAR_CLOSE", - /* 40 */ "list_items ::= expression_prio4", - /* 41 */ "list_items ::= list_items COMA expression_prio4", - /* 42 */ "arg_list ::=", - /* 43 */ "arg_list ::= argument", - /* 44 */ "arg_list ::= arg_list COMA argument", - /* 45 */ "argument ::= expression_prio4", - /* 46 */ "argument ::= INTERVAL expression_prio4 interval_unit", - /* 47 */ "interval_unit ::= F_SECOND", - /* 48 */ "interval_unit ::= F_MINUTE", - /* 49 */ "interval_unit ::= F_HOUR", - /* 50 */ "interval_unit ::= F_DAY", - /* 51 */ "interval_unit ::= F_MONTH", - /* 52 */ "interval_unit ::= F_YEAR", - /* 53 */ "scalar ::= num_scalar", - /* 54 */ "scalar ::= str_scalar", - /* 55 */ "num_scalar ::= num_value", - /* 56 */ "str_scalar ::= str_value", - /* 57 */ "field_id ::= name", - /* 58 */ "field_id ::= class_name DOT name", - /* 59 */ "class_name ::= name", - /* 60 */ "var_name ::= VARNAME", - /* 61 */ "name ::= NAME", - /* 62 */ "num_value ::= NUMVAL", - /* 63 */ "num_value ::= MATH_MINUS NUMVAL", - /* 64 */ "num_value ::= HEXVAL", - /* 65 */ "str_value ::= STRVAL", - /* 66 */ "operator1 ::= num_operator1", - /* 67 */ "operator1 ::= bitwise_operator1", - /* 68 */ "operator2 ::= num_operator2", - /* 69 */ "operator2 ::= str_operator", - /* 70 */ "operator2 ::= REGEXP", - /* 71 */ "operator2 ::= EQ", - /* 72 */ "operator2 ::= NOT_EQ", - /* 73 */ "operator3 ::= LOG_AND", - /* 74 */ "operator3 ::= bitwise_operator3", - /* 75 */ "operator4 ::= LOG_OR", - /* 76 */ "operator4 ::= bitwise_operator4", - /* 77 */ "num_operator1 ::= MATH_DIV", - /* 78 */ "num_operator1 ::= MATH_MULT", - /* 79 */ "num_operator2 ::= MATH_PLUS", - /* 80 */ "num_operator2 ::= MATH_MINUS", - /* 81 */ "num_operator2 ::= GT", - /* 82 */ "num_operator2 ::= LT", - /* 83 */ "num_operator2 ::= GE", - /* 84 */ "num_operator2 ::= LE", - /* 85 */ "str_operator ::= LIKE", - /* 86 */ "str_operator ::= NOT_LIKE", - /* 87 */ "bitwise_operator1 ::= BITWISE_LEFT_SHIFT", - /* 88 */ "bitwise_operator1 ::= BITWISE_RIGHT_SHIFT", - /* 89 */ "bitwise_operator3 ::= BITWISE_AND", - /* 90 */ "bitwise_operator4 ::= BITWISE_OR", - /* 91 */ "bitwise_operator4 ::= BITWISE_XOR", - /* 92 */ "list_operator ::= IN", - /* 93 */ "list_operator ::= NOT_IN", - /* 94 */ "func_name ::= F_IF", - /* 95 */ "func_name ::= F_ELT", - /* 96 */ "func_name ::= F_COALESCE", - /* 97 */ "func_name ::= F_ISNULL", - /* 98 */ "func_name ::= F_CONCAT", - /* 99 */ "func_name ::= F_SUBSTR", - /* 100 */ "func_name ::= F_TRIM", - /* 101 */ "func_name ::= F_DATE", - /* 102 */ "func_name ::= F_DATE_FORMAT", - /* 103 */ "func_name ::= F_CURRENT_DATE", - /* 104 */ "func_name ::= F_NOW", - /* 105 */ "func_name ::= F_TIME", - /* 106 */ "func_name ::= F_TO_DAYS", - /* 107 */ "func_name ::= F_FROM_DAYS", - /* 108 */ "func_name ::= F_YEAR", - /* 109 */ "func_name ::= F_MONTH", - /* 110 */ "func_name ::= F_DAY", - /* 111 */ "func_name ::= F_DATE_ADD", - /* 112 */ "func_name ::= F_DATE_SUB", - /* 113 */ "func_name ::= F_ROUND", - /* 114 */ "func_name ::= F_FLOOR", - /* 115 */ "func_name ::= F_INET_ATON", - /* 116 */ "func_name ::= F_INET_NTOA", + /* 0 */ "result ::= union", + /* 1 */ "result ::= query", + /* 2 */ "result ::= condition", + /* 3 */ "union ::= query UNION query", + /* 4 */ "union ::= query UNION union", + /* 5 */ "query ::= SELECT class_name join_statement where_statement", + /* 6 */ "query ::= SELECT class_name AS_ALIAS class_name join_statement where_statement", + /* 7 */ "query ::= SELECT class_list FROM class_name join_statement where_statement", + /* 8 */ "query ::= SELECT class_list FROM class_name AS_ALIAS class_name join_statement where_statement", + /* 9 */ "class_list ::= class_name", + /* 10 */ "class_list ::= class_list COMA class_name", + /* 11 */ "where_statement ::= WHERE condition", + /* 12 */ "where_statement ::=", + /* 13 */ "join_statement ::= join_item join_statement", + /* 14 */ "join_statement ::= join_item", + /* 15 */ "join_statement ::=", + /* 16 */ "join_item ::= JOIN class_name AS_ALIAS class_name ON join_condition", + /* 17 */ "join_item ::= JOIN class_name ON join_condition", + /* 18 */ "join_condition ::= field_id EQ field_id", + /* 19 */ "join_condition ::= field_id BELOW field_id", + /* 20 */ "join_condition ::= field_id BELOW_STRICT field_id", + /* 21 */ "join_condition ::= field_id NOT_BELOW field_id", + /* 22 */ "join_condition ::= field_id NOT_BELOW_STRICT field_id", + /* 23 */ "join_condition ::= field_id ABOVE field_id", + /* 24 */ "join_condition ::= field_id ABOVE_STRICT field_id", + /* 25 */ "join_condition ::= field_id NOT_ABOVE field_id", + /* 26 */ "join_condition ::= field_id NOT_ABOVE_STRICT field_id", + /* 27 */ "condition ::= expression_prio4", + /* 28 */ "expression_basic ::= scalar", + /* 29 */ "expression_basic ::= field_id", + /* 30 */ "expression_basic ::= var_name", + /* 31 */ "expression_basic ::= func_name PAR_OPEN arg_list PAR_CLOSE", + /* 32 */ "expression_basic ::= PAR_OPEN expression_prio4 PAR_CLOSE", + /* 33 */ "expression_basic ::= expression_basic list_operator list", + /* 34 */ "expression_prio1 ::= expression_basic", + /* 35 */ "expression_prio1 ::= expression_prio1 operator1 expression_basic", + /* 36 */ "expression_prio2 ::= expression_prio1", + /* 37 */ "expression_prio2 ::= expression_prio2 operator2 expression_prio1", + /* 38 */ "expression_prio3 ::= expression_prio2", + /* 39 */ "expression_prio3 ::= expression_prio3 operator3 expression_prio2", + /* 40 */ "expression_prio4 ::= expression_prio3", + /* 41 */ "expression_prio4 ::= expression_prio4 operator4 expression_prio3", + /* 42 */ "list ::= PAR_OPEN list_items PAR_CLOSE", + /* 43 */ "list_items ::= expression_prio4", + /* 44 */ "list_items ::= list_items COMA expression_prio4", + /* 45 */ "arg_list ::=", + /* 46 */ "arg_list ::= argument", + /* 47 */ "arg_list ::= arg_list COMA argument", + /* 48 */ "argument ::= expression_prio4", + /* 49 */ "argument ::= INTERVAL expression_prio4 interval_unit", + /* 50 */ "interval_unit ::= F_SECOND", + /* 51 */ "interval_unit ::= F_MINUTE", + /* 52 */ "interval_unit ::= F_HOUR", + /* 53 */ "interval_unit ::= F_DAY", + /* 54 */ "interval_unit ::= F_MONTH", + /* 55 */ "interval_unit ::= F_YEAR", + /* 56 */ "scalar ::= num_scalar", + /* 57 */ "scalar ::= str_scalar", + /* 58 */ "num_scalar ::= num_value", + /* 59 */ "str_scalar ::= str_value", + /* 60 */ "field_id ::= name", + /* 61 */ "field_id ::= class_name DOT name", + /* 62 */ "class_name ::= name", + /* 63 */ "var_name ::= VARNAME", + /* 64 */ "name ::= NAME", + /* 65 */ "num_value ::= NUMVAL", + /* 66 */ "num_value ::= MATH_MINUS NUMVAL", + /* 67 */ "num_value ::= HEXVAL", + /* 68 */ "str_value ::= STRVAL", + /* 69 */ "operator1 ::= num_operator1", + /* 70 */ "operator1 ::= bitwise_operator1", + /* 71 */ "operator2 ::= num_operator2", + /* 72 */ "operator2 ::= str_operator", + /* 73 */ "operator2 ::= REGEXP", + /* 74 */ "operator2 ::= EQ", + /* 75 */ "operator2 ::= NOT_EQ", + /* 76 */ "operator3 ::= LOG_AND", + /* 77 */ "operator3 ::= bitwise_operator3", + /* 78 */ "operator4 ::= LOG_OR", + /* 79 */ "operator4 ::= bitwise_operator4", + /* 80 */ "num_operator1 ::= MATH_DIV", + /* 81 */ "num_operator1 ::= MATH_MULT", + /* 82 */ "num_operator2 ::= MATH_PLUS", + /* 83 */ "num_operator2 ::= MATH_MINUS", + /* 84 */ "num_operator2 ::= GT", + /* 85 */ "num_operator2 ::= LT", + /* 86 */ "num_operator2 ::= GE", + /* 87 */ "num_operator2 ::= LE", + /* 88 */ "str_operator ::= LIKE", + /* 89 */ "str_operator ::= NOT_LIKE", + /* 90 */ "bitwise_operator1 ::= BITWISE_LEFT_SHIFT", + /* 91 */ "bitwise_operator1 ::= BITWISE_RIGHT_SHIFT", + /* 92 */ "bitwise_operator3 ::= BITWISE_AND", + /* 93 */ "bitwise_operator4 ::= BITWISE_OR", + /* 94 */ "bitwise_operator4 ::= BITWISE_XOR", + /* 95 */ "list_operator ::= IN", + /* 96 */ "list_operator ::= NOT_IN", + /* 97 */ "func_name ::= F_IF", + /* 98 */ "func_name ::= F_ELT", + /* 99 */ "func_name ::= F_COALESCE", + /* 100 */ "func_name ::= F_ISNULL", + /* 101 */ "func_name ::= F_CONCAT", + /* 102 */ "func_name ::= F_SUBSTR", + /* 103 */ "func_name ::= F_TRIM", + /* 104 */ "func_name ::= F_DATE", + /* 105 */ "func_name ::= F_DATE_FORMAT", + /* 106 */ "func_name ::= F_CURRENT_DATE", + /* 107 */ "func_name ::= F_NOW", + /* 108 */ "func_name ::= F_TIME", + /* 109 */ "func_name ::= F_TO_DAYS", + /* 110 */ "func_name ::= F_FROM_DAYS", + /* 111 */ "func_name ::= F_YEAR", + /* 112 */ "func_name ::= F_MONTH", + /* 113 */ "func_name ::= F_DAY", + /* 114 */ "func_name ::= F_DATE_ADD", + /* 115 */ "func_name ::= F_DATE_SUB", + /* 116 */ "func_name ::= F_ROUND", + /* 117 */ "func_name ::= F_FLOOR", + /* 118 */ "func_name ::= F_INET_ATON", + /* 119 */ "func_name ::= F_INET_NTOA", ); /** @@ -1172,123 +1184,126 @@ static public $yy_action = array( * */ static public $yyRuleInfo = array( - array( 'lhs' => 74, 'rhs' => 1 ), - array( 'lhs' => 74, 'rhs' => 1 ), - array( 'lhs' => 75, 'rhs' => 4 ), - array( 'lhs' => 75, 'rhs' => 6 ), - array( 'lhs' => 75, 'rhs' => 6 ), - array( 'lhs' => 75, 'rhs' => 8 ), + array( 'lhs' => 75, 'rhs' => 1 ), + array( 'lhs' => 75, 'rhs' => 1 ), + array( 'lhs' => 75, 'rhs' => 1 ), + array( 'lhs' => 76, 'rhs' => 3 ), + array( 'lhs' => 76, 'rhs' => 3 ), + array( 'lhs' => 77, 'rhs' => 4 ), + array( 'lhs' => 77, 'rhs' => 6 ), + array( 'lhs' => 77, 'rhs' => 6 ), + array( 'lhs' => 77, 'rhs' => 8 ), + array( 'lhs' => 82, 'rhs' => 1 ), + array( 'lhs' => 82, 'rhs' => 3 ), + array( 'lhs' => 81, 'rhs' => 2 ), + array( 'lhs' => 81, 'rhs' => 0 ), + array( 'lhs' => 80, 'rhs' => 2 ), array( 'lhs' => 80, 'rhs' => 1 ), - array( 'lhs' => 80, 'rhs' => 3 ), - array( 'lhs' => 79, 'rhs' => 2 ), - array( 'lhs' => 79, 'rhs' => 0 ), - array( 'lhs' => 78, 'rhs' => 2 ), + array( 'lhs' => 80, 'rhs' => 0 ), + array( 'lhs' => 83, 'rhs' => 6 ), + array( 'lhs' => 83, 'rhs' => 4 ), + array( 'lhs' => 84, 'rhs' => 3 ), + array( 'lhs' => 84, 'rhs' => 3 ), + array( 'lhs' => 84, 'rhs' => 3 ), + array( 'lhs' => 84, 'rhs' => 3 ), + array( 'lhs' => 84, 'rhs' => 3 ), + array( 'lhs' => 84, 'rhs' => 3 ), + array( 'lhs' => 84, 'rhs' => 3 ), + array( 'lhs' => 84, 'rhs' => 3 ), + array( 'lhs' => 84, 'rhs' => 3 ), array( 'lhs' => 78, 'rhs' => 1 ), - array( 'lhs' => 78, 'rhs' => 0 ), - array( 'lhs' => 81, 'rhs' => 6 ), - array( 'lhs' => 81, 'rhs' => 4 ), - array( 'lhs' => 82, 'rhs' => 3 ), - array( 'lhs' => 82, 'rhs' => 3 ), - array( 'lhs' => 82, 'rhs' => 3 ), - array( 'lhs' => 82, 'rhs' => 3 ), - array( 'lhs' => 82, 'rhs' => 3 ), - array( 'lhs' => 82, 'rhs' => 3 ), - array( 'lhs' => 82, 'rhs' => 3 ), - array( 'lhs' => 82, 'rhs' => 3 ), - array( 'lhs' => 82, 'rhs' => 3 ), - array( 'lhs' => 76, 'rhs' => 1 ), - array( 'lhs' => 85, 'rhs' => 1 ), - array( 'lhs' => 85, 'rhs' => 1 ), - array( 'lhs' => 85, 'rhs' => 1 ), - array( 'lhs' => 85, 'rhs' => 4 ), - array( 'lhs' => 85, 'rhs' => 3 ), - array( 'lhs' => 85, 'rhs' => 3 ), - array( 'lhs' => 92, 'rhs' => 1 ), - array( 'lhs' => 92, 'rhs' => 3 ), + array( 'lhs' => 87, 'rhs' => 1 ), + array( 'lhs' => 87, 'rhs' => 1 ), + array( 'lhs' => 87, 'rhs' => 1 ), + array( 'lhs' => 87, 'rhs' => 4 ), + array( 'lhs' => 87, 'rhs' => 3 ), + array( 'lhs' => 87, 'rhs' => 3 ), array( 'lhs' => 94, 'rhs' => 1 ), array( 'lhs' => 94, 'rhs' => 3 ), array( 'lhs' => 96, 'rhs' => 1 ), array( 'lhs' => 96, 'rhs' => 3 ), - array( 'lhs' => 84, 'rhs' => 1 ), - array( 'lhs' => 84, 'rhs' => 3 ), + array( 'lhs' => 98, 'rhs' => 1 ), + array( 'lhs' => 98, 'rhs' => 3 ), + array( 'lhs' => 86, 'rhs' => 1 ), + array( 'lhs' => 86, 'rhs' => 3 ), + array( 'lhs' => 93, 'rhs' => 3 ), + array( 'lhs' => 101, 'rhs' => 1 ), + array( 'lhs' => 101, 'rhs' => 3 ), + array( 'lhs' => 91, 'rhs' => 0 ), + array( 'lhs' => 91, 'rhs' => 1 ), array( 'lhs' => 91, 'rhs' => 3 ), - array( 'lhs' => 99, 'rhs' => 1 ), - array( 'lhs' => 99, 'rhs' => 3 ), - array( 'lhs' => 89, 'rhs' => 0 ), - array( 'lhs' => 89, 'rhs' => 1 ), - array( 'lhs' => 89, 'rhs' => 3 ), - array( 'lhs' => 100, 'rhs' => 1 ), - array( 'lhs' => 100, 'rhs' => 3 ), - array( 'lhs' => 101, 'rhs' => 1 ), - array( 'lhs' => 101, 'rhs' => 1 ), - array( 'lhs' => 101, 'rhs' => 1 ), - array( 'lhs' => 101, 'rhs' => 1 ), - array( 'lhs' => 101, 'rhs' => 1 ), - array( 'lhs' => 101, 'rhs' => 1 ), - array( 'lhs' => 86, 'rhs' => 1 ), - array( 'lhs' => 86, 'rhs' => 1 ), array( 'lhs' => 102, 'rhs' => 1 ), + array( 'lhs' => 102, 'rhs' => 3 ), array( 'lhs' => 103, 'rhs' => 1 ), - array( 'lhs' => 83, 'rhs' => 1 ), - array( 'lhs' => 83, 'rhs' => 3 ), - array( 'lhs' => 77, 'rhs' => 1 ), - array( 'lhs' => 87, 'rhs' => 1 ), - array( 'lhs' => 106, 'rhs' => 1 ), - array( 'lhs' => 104, 'rhs' => 1 ), - array( 'lhs' => 104, 'rhs' => 2 ), + array( 'lhs' => 103, 'rhs' => 1 ), + array( 'lhs' => 103, 'rhs' => 1 ), + array( 'lhs' => 103, 'rhs' => 1 ), + array( 'lhs' => 103, 'rhs' => 1 ), + array( 'lhs' => 103, 'rhs' => 1 ), + array( 'lhs' => 88, 'rhs' => 1 ), + array( 'lhs' => 88, 'rhs' => 1 ), array( 'lhs' => 104, 'rhs' => 1 ), array( 'lhs' => 105, 'rhs' => 1 ), - array( 'lhs' => 93, 'rhs' => 1 ), - array( 'lhs' => 93, 'rhs' => 1 ), - array( 'lhs' => 95, 'rhs' => 1 ), - array( 'lhs' => 95, 'rhs' => 1 ), - array( 'lhs' => 95, 'rhs' => 1 ), + array( 'lhs' => 85, 'rhs' => 1 ), + array( 'lhs' => 85, 'rhs' => 3 ), + array( 'lhs' => 79, 'rhs' => 1 ), + array( 'lhs' => 89, 'rhs' => 1 ), + array( 'lhs' => 108, 'rhs' => 1 ), + array( 'lhs' => 106, 'rhs' => 1 ), + array( 'lhs' => 106, 'rhs' => 2 ), + array( 'lhs' => 106, 'rhs' => 1 ), + array( 'lhs' => 107, 'rhs' => 1 ), array( 'lhs' => 95, 'rhs' => 1 ), array( 'lhs' => 95, 'rhs' => 1 ), array( 'lhs' => 97, 'rhs' => 1 ), array( 'lhs' => 97, 'rhs' => 1 ), - array( 'lhs' => 98, 'rhs' => 1 ), - array( 'lhs' => 98, 'rhs' => 1 ), - array( 'lhs' => 107, 'rhs' => 1 ), - array( 'lhs' => 107, 'rhs' => 1 ), + array( 'lhs' => 97, 'rhs' => 1 ), + array( 'lhs' => 97, 'rhs' => 1 ), + array( 'lhs' => 97, 'rhs' => 1 ), + array( 'lhs' => 99, 'rhs' => 1 ), + array( 'lhs' => 99, 'rhs' => 1 ), + array( 'lhs' => 100, 'rhs' => 1 ), + array( 'lhs' => 100, 'rhs' => 1 ), array( 'lhs' => 109, 'rhs' => 1 ), array( 'lhs' => 109, 'rhs' => 1 ), - array( 'lhs' => 109, 'rhs' => 1 ), - array( 'lhs' => 109, 'rhs' => 1 ), - array( 'lhs' => 109, 'rhs' => 1 ), - array( 'lhs' => 109, 'rhs' => 1 ), - array( 'lhs' => 110, 'rhs' => 1 ), - array( 'lhs' => 110, 'rhs' => 1 ), - array( 'lhs' => 108, 'rhs' => 1 ), - array( 'lhs' => 108, 'rhs' => 1 ), + array( 'lhs' => 111, 'rhs' => 1 ), + array( 'lhs' => 111, 'rhs' => 1 ), + array( 'lhs' => 111, 'rhs' => 1 ), + array( 'lhs' => 111, 'rhs' => 1 ), + array( 'lhs' => 111, 'rhs' => 1 ), array( 'lhs' => 111, 'rhs' => 1 ), array( 'lhs' => 112, 'rhs' => 1 ), array( 'lhs' => 112, 'rhs' => 1 ), + array( 'lhs' => 110, 'rhs' => 1 ), + array( 'lhs' => 110, 'rhs' => 1 ), + array( 'lhs' => 113, 'rhs' => 1 ), + array( 'lhs' => 114, 'rhs' => 1 ), + array( 'lhs' => 114, 'rhs' => 1 ), + array( 'lhs' => 92, 'rhs' => 1 ), + array( 'lhs' => 92, 'rhs' => 1 ), + array( 'lhs' => 90, 'rhs' => 1 ), + array( 'lhs' => 90, 'rhs' => 1 ), + array( 'lhs' => 90, 'rhs' => 1 ), + array( 'lhs' => 90, 'rhs' => 1 ), + array( 'lhs' => 90, 'rhs' => 1 ), + array( 'lhs' => 90, 'rhs' => 1 ), + array( 'lhs' => 90, 'rhs' => 1 ), + array( 'lhs' => 90, 'rhs' => 1 ), + array( 'lhs' => 90, 'rhs' => 1 ), + array( 'lhs' => 90, 'rhs' => 1 ), + array( 'lhs' => 90, 'rhs' => 1 ), + array( 'lhs' => 90, 'rhs' => 1 ), + array( 'lhs' => 90, 'rhs' => 1 ), + array( 'lhs' => 90, 'rhs' => 1 ), + array( 'lhs' => 90, 'rhs' => 1 ), + array( 'lhs' => 90, 'rhs' => 1 ), + array( 'lhs' => 90, 'rhs' => 1 ), + array( 'lhs' => 90, 'rhs' => 1 ), + array( 'lhs' => 90, 'rhs' => 1 ), + array( 'lhs' => 90, 'rhs' => 1 ), + array( 'lhs' => 90, 'rhs' => 1 ), array( 'lhs' => 90, 'rhs' => 1 ), array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 88, 'rhs' => 1 ), - array( 'lhs' => 88, 'rhs' => 1 ), - array( 'lhs' => 88, 'rhs' => 1 ), - array( 'lhs' => 88, 'rhs' => 1 ), - array( 'lhs' => 88, 'rhs' => 1 ), - array( 'lhs' => 88, 'rhs' => 1 ), - array( 'lhs' => 88, 'rhs' => 1 ), - array( 'lhs' => 88, 'rhs' => 1 ), - array( 'lhs' => 88, 'rhs' => 1 ), - array( 'lhs' => 88, 'rhs' => 1 ), - array( 'lhs' => 88, 'rhs' => 1 ), - array( 'lhs' => 88, 'rhs' => 1 ), - array( 'lhs' => 88, 'rhs' => 1 ), - array( 'lhs' => 88, 'rhs' => 1 ), - array( 'lhs' => 88, 'rhs' => 1 ), - array( 'lhs' => 88, 'rhs' => 1 ), - array( 'lhs' => 88, 'rhs' => 1 ), - array( 'lhs' => 88, 'rhs' => 1 ), - array( 'lhs' => 88, 'rhs' => 1 ), - array( 'lhs' => 88, 'rhs' => 1 ), - array( 'lhs' => 88, 'rhs' => 1 ), - array( 'lhs' => 88, 'rhs' => 1 ), - array( 'lhs' => 88, 'rhs' => 1 ), ); /** @@ -1300,24 +1315,24 @@ static public $yy_action = array( static public $yyReduceMap = array( 0 => 0, 1 => 0, - 2 => 2, + 2 => 0, 3 => 3, - 4 => 4, + 4 => 3, 5 => 5, 6 => 6, - 40 => 6, - 43 => 6, 7 => 7, - 41 => 7, - 44 => 7, 8 => 8, 9 => 9, - 12 => 9, + 43 => 9, + 46 => 9, 10 => 10, + 44 => 10, + 47 => 10, 11 => 11, + 12 => 12, + 15 => 12, 13 => 13, 14 => 14, - 15 => 15, 16 => 16, 17 => 17, 18 => 18, @@ -1327,94 +1342,97 @@ static public $yy_action = array( 22 => 22, 23 => 23, 24 => 24, - 25 => 24, - 26 => 24, - 27 => 24, - 31 => 24, - 33 => 24, - 35 => 24, - 37 => 24, - 45 => 24, - 47 => 24, - 48 => 24, - 49 => 24, - 50 => 24, - 51 => 24, - 52 => 24, - 53 => 24, - 54 => 24, - 28 => 28, - 29 => 29, - 30 => 30, - 32 => 30, - 34 => 30, - 36 => 30, - 38 => 30, - 39 => 39, + 25 => 25, + 26 => 26, + 27 => 27, + 28 => 27, + 29 => 27, + 30 => 27, + 34 => 27, + 36 => 27, + 38 => 27, + 40 => 27, + 48 => 27, + 50 => 27, + 51 => 27, + 52 => 27, + 53 => 27, + 54 => 27, + 55 => 27, + 56 => 27, + 57 => 27, + 31 => 31, + 32 => 32, + 33 => 33, + 35 => 33, + 37 => 33, + 39 => 33, + 41 => 33, 42 => 42, - 46 => 46, - 55 => 55, - 56 => 55, - 57 => 57, + 45 => 45, + 49 => 49, 58 => 58, - 59 => 59, - 94 => 59, - 95 => 59, - 96 => 59, - 97 => 59, - 98 => 59, - 99 => 59, - 100 => 59, - 101 => 59, - 102 => 59, - 103 => 59, - 104 => 59, - 105 => 59, - 106 => 59, - 107 => 59, - 108 => 59, - 109 => 59, - 110 => 59, - 111 => 59, - 112 => 59, - 113 => 59, - 114 => 59, - 115 => 59, - 116 => 59, + 59 => 58, 60 => 60, 61 => 61, 62 => 62, + 97 => 62, + 98 => 62, + 99 => 62, + 100 => 62, + 101 => 62, + 102 => 62, + 103 => 62, + 104 => 62, + 105 => 62, + 106 => 62, + 107 => 62, + 108 => 62, + 109 => 62, + 110 => 62, + 111 => 62, + 112 => 62, + 113 => 62, + 114 => 62, + 115 => 62, + 116 => 62, + 117 => 62, + 118 => 62, + 119 => 62, 63 => 63, 64 => 64, 65 => 65, 66 => 66, - 67 => 66, - 68 => 66, - 69 => 66, - 70 => 66, - 71 => 66, - 72 => 66, - 73 => 66, - 74 => 66, - 75 => 66, - 76 => 66, - 77 => 66, - 78 => 66, - 79 => 66, - 80 => 66, - 81 => 66, - 82 => 66, - 83 => 66, - 84 => 66, - 85 => 66, - 86 => 66, - 87 => 66, - 88 => 66, - 89 => 66, - 90 => 66, - 91 => 66, - 92 => 66, - 93 => 66, + 67 => 67, + 68 => 68, + 69 => 69, + 70 => 69, + 71 => 69, + 72 => 69, + 73 => 69, + 74 => 69, + 75 => 69, + 76 => 69, + 77 => 69, + 78 => 69, + 79 => 69, + 80 => 69, + 81 => 69, + 82 => 69, + 83 => 69, + 84 => 69, + 85 => 69, + 86 => 69, + 87 => 69, + 88 => 69, + 89 => 69, + 90 => 69, + 91 => 69, + 92 => 69, + 93 => 69, + 94 => 69, + 95 => 69, + 96 => 69, ); /* Beginning here are the reduction cases. A typical example ** follows: @@ -1424,138 +1442,143 @@ static public $yy_action = array( */ #line 29 "..\oql-parser.y" function yy_r0(){ $this->my_result = $this->yystack[$this->yyidx + 0]->minor; } -#line 1431 "..\oql-parser.php" -#line 32 "..\oql-parser.y" - function yy_r2(){ +#line 1449 "..\oql-parser.php" +#line 33 "..\oql-parser.y" + function yy_r3(){ + $this->_retvalue = new OqlUnionQuery($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor); + } +#line 1454 "..\oql-parser.php" +#line 40 "..\oql-parser.y" + function yy_r5(){ $this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, array($this->yystack[$this->yyidx + -2]->minor)); } -#line 1436 "..\oql-parser.php" -#line 35 "..\oql-parser.y" - function yy_r3(){ +#line 1459 "..\oql-parser.php" +#line 43 "..\oql-parser.y" + function yy_r6(){ $this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, array($this->yystack[$this->yyidx + -2]->minor)); } -#line 1441 "..\oql-parser.php" -#line 39 "..\oql-parser.y" - function yy_r4(){ +#line 1464 "..\oql-parser.php" +#line 47 "..\oql-parser.y" + function yy_r7(){ $this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + -4]->minor); } -#line 1446 "..\oql-parser.php" -#line 42 "..\oql-parser.y" - function yy_r5(){ +#line 1469 "..\oql-parser.php" +#line 50 "..\oql-parser.y" + function yy_r8(){ $this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + -6]->minor); } -#line 1451 "..\oql-parser.php" -#line 47 "..\oql-parser.y" - function yy_r6(){ +#line 1474 "..\oql-parser.php" +#line 55 "..\oql-parser.y" + function yy_r9(){ $this->_retvalue = array($this->yystack[$this->yyidx + 0]->minor); } -#line 1456 "..\oql-parser.php" -#line 50 "..\oql-parser.y" - function yy_r7(){ +#line 1479 "..\oql-parser.php" +#line 58 "..\oql-parser.y" + function yy_r10(){ array_push($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor); $this->_retvalue = $this->yystack[$this->yyidx + -2]->minor; } -#line 1462 "..\oql-parser.php" -#line 55 "..\oql-parser.y" - function yy_r8(){ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; } -#line 1465 "..\oql-parser.php" -#line 56 "..\oql-parser.y" - function yy_r9(){ $this->_retvalue = null; } -#line 1468 "..\oql-parser.php" -#line 58 "..\oql-parser.y" - function yy_r10(){ +#line 1485 "..\oql-parser.php" +#line 63 "..\oql-parser.y" + function yy_r11(){ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; } +#line 1488 "..\oql-parser.php" +#line 64 "..\oql-parser.y" + function yy_r12(){ $this->_retvalue = null; } +#line 1491 "..\oql-parser.php" +#line 66 "..\oql-parser.y" + function yy_r13(){ // insert the join statement on top of the existing list array_unshift($this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor); // and return the updated array $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; } -#line 1476 "..\oql-parser.php" -#line 64 "..\oql-parser.y" - function yy_r11(){ +#line 1499 "..\oql-parser.php" +#line 72 "..\oql-parser.y" + function yy_r14(){ $this->_retvalue = Array($this->yystack[$this->yyidx + 0]->minor); } -#line 1481 "..\oql-parser.php" -#line 70 "..\oql-parser.y" - function yy_r13(){ +#line 1504 "..\oql-parser.php" +#line 78 "..\oql-parser.y" + function yy_r16(){ // create an array with one single item $this->_retvalue = new OqlJoinSpec($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor); } -#line 1487 "..\oql-parser.php" -#line 75 "..\oql-parser.y" - function yy_r14(){ +#line 1510 "..\oql-parser.php" +#line 83 "..\oql-parser.y" + function yy_r17(){ // create an array with one single item $this->_retvalue = new OqlJoinSpec($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor); } -#line 1493 "..\oql-parser.php" -#line 80 "..\oql-parser.y" - function yy_r15(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, '=', $this->yystack[$this->yyidx + 0]->minor); } -#line 1496 "..\oql-parser.php" -#line 81 "..\oql-parser.y" - function yy_r16(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'BELOW', $this->yystack[$this->yyidx + 0]->minor); } -#line 1499 "..\oql-parser.php" -#line 82 "..\oql-parser.y" - function yy_r17(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'BELOW_STRICT', $this->yystack[$this->yyidx + 0]->minor); } -#line 1502 "..\oql-parser.php" -#line 83 "..\oql-parser.y" - function yy_r18(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_BELOW', $this->yystack[$this->yyidx + 0]->minor); } -#line 1505 "..\oql-parser.php" -#line 84 "..\oql-parser.y" - function yy_r19(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_BELOW_STRICT', $this->yystack[$this->yyidx + 0]->minor); } -#line 1508 "..\oql-parser.php" -#line 85 "..\oql-parser.y" - function yy_r20(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'ABOVE', $this->yystack[$this->yyidx + 0]->minor); } -#line 1511 "..\oql-parser.php" -#line 86 "..\oql-parser.y" - function yy_r21(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'ABOVE_STRICT', $this->yystack[$this->yyidx + 0]->minor); } -#line 1514 "..\oql-parser.php" -#line 87 "..\oql-parser.y" - function yy_r22(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_ABOVE', $this->yystack[$this->yyidx + 0]->minor); } -#line 1517 "..\oql-parser.php" +#line 1516 "..\oql-parser.php" #line 88 "..\oql-parser.y" - function yy_r23(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_ABOVE_STRICT', $this->yystack[$this->yyidx + 0]->minor); } -#line 1520 "..\oql-parser.php" + function yy_r18(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, '=', $this->yystack[$this->yyidx + 0]->minor); } +#line 1519 "..\oql-parser.php" +#line 89 "..\oql-parser.y" + function yy_r19(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'BELOW', $this->yystack[$this->yyidx + 0]->minor); } +#line 1522 "..\oql-parser.php" #line 90 "..\oql-parser.y" - function yy_r24(){ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; } -#line 1523 "..\oql-parser.php" + function yy_r20(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'BELOW_STRICT', $this->yystack[$this->yyidx + 0]->minor); } +#line 1525 "..\oql-parser.php" +#line 91 "..\oql-parser.y" + function yy_r21(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_BELOW', $this->yystack[$this->yyidx + 0]->minor); } +#line 1528 "..\oql-parser.php" +#line 92 "..\oql-parser.y" + function yy_r22(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_BELOW_STRICT', $this->yystack[$this->yyidx + 0]->minor); } +#line 1531 "..\oql-parser.php" +#line 93 "..\oql-parser.y" + function yy_r23(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'ABOVE', $this->yystack[$this->yyidx + 0]->minor); } +#line 1534 "..\oql-parser.php" +#line 94 "..\oql-parser.y" + function yy_r24(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'ABOVE_STRICT', $this->yystack[$this->yyidx + 0]->minor); } +#line 1537 "..\oql-parser.php" #line 95 "..\oql-parser.y" - function yy_r28(){ $this->_retvalue = new FunctionOqlExpression($this->yystack[$this->yyidx + -3]->minor, $this->yystack[$this->yyidx + -1]->minor); } -#line 1526 "..\oql-parser.php" + function yy_r25(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_ABOVE', $this->yystack[$this->yyidx + 0]->minor); } +#line 1540 "..\oql-parser.php" #line 96 "..\oql-parser.y" - function yy_r29(){ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor; } -#line 1529 "..\oql-parser.php" -#line 97 "..\oql-parser.y" - function yy_r30(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); } -#line 1532 "..\oql-parser.php" -#line 112 "..\oql-parser.y" - function yy_r39(){ + function yy_r26(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_ABOVE_STRICT', $this->yystack[$this->yyidx + 0]->minor); } +#line 1543 "..\oql-parser.php" +#line 98 "..\oql-parser.y" + function yy_r27(){ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; } +#line 1546 "..\oql-parser.php" +#line 103 "..\oql-parser.y" + function yy_r31(){ $this->_retvalue = new FunctionOqlExpression($this->yystack[$this->yyidx + -3]->minor, $this->yystack[$this->yyidx + -1]->minor); } +#line 1549 "..\oql-parser.php" +#line 104 "..\oql-parser.y" + function yy_r32(){ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor; } +#line 1552 "..\oql-parser.php" +#line 105 "..\oql-parser.y" + function yy_r33(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); } +#line 1555 "..\oql-parser.php" +#line 120 "..\oql-parser.y" + function yy_r42(){ $this->_retvalue = new ListOqlExpression($this->yystack[$this->yyidx + -1]->minor); } -#line 1537 "..\oql-parser.php" -#line 123 "..\oql-parser.y" - function yy_r42(){ +#line 1560 "..\oql-parser.php" +#line 131 "..\oql-parser.y" + function yy_r45(){ $this->_retvalue = array(); } -#line 1542 "..\oql-parser.php" -#line 134 "..\oql-parser.y" - function yy_r46(){ $this->_retvalue = new IntervalOqlExpression($this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); } -#line 1545 "..\oql-parser.php" -#line 146 "..\oql-parser.y" - function yy_r55(){ $this->_retvalue = new ScalarOqlExpression($this->yystack[$this->yyidx + 0]->minor); } -#line 1548 "..\oql-parser.php" -#line 149 "..\oql-parser.y" - function yy_r57(){ $this->_retvalue = new FieldOqlExpression($this->yystack[$this->yyidx + 0]->minor); } -#line 1551 "..\oql-parser.php" -#line 150 "..\oql-parser.y" - function yy_r58(){ $this->_retvalue = new FieldOqlExpression($this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -2]->minor); } -#line 1554 "..\oql-parser.php" -#line 151 "..\oql-parser.y" - function yy_r59(){ $this->_retvalue=$this->yystack[$this->yyidx + 0]->minor; } -#line 1557 "..\oql-parser.php" +#line 1565 "..\oql-parser.php" +#line 142 "..\oql-parser.y" + function yy_r49(){ $this->_retvalue = new IntervalOqlExpression($this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); } +#line 1568 "..\oql-parser.php" #line 154 "..\oql-parser.y" - function yy_r60(){ $this->_retvalue = new VariableOqlExpression(substr($this->yystack[$this->yyidx + 0]->minor, 1)); } -#line 1560 "..\oql-parser.php" -#line 156 "..\oql-parser.y" - function yy_r61(){ + function yy_r58(){ $this->_retvalue = new ScalarOqlExpression($this->yystack[$this->yyidx + 0]->minor); } +#line 1571 "..\oql-parser.php" +#line 157 "..\oql-parser.y" + function yy_r60(){ $this->_retvalue = new FieldOqlExpression($this->yystack[$this->yyidx + 0]->minor); } +#line 1574 "..\oql-parser.php" +#line 158 "..\oql-parser.y" + function yy_r61(){ $this->_retvalue = new FieldOqlExpression($this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -2]->minor); } +#line 1577 "..\oql-parser.php" +#line 159 "..\oql-parser.y" + function yy_r62(){ $this->_retvalue=$this->yystack[$this->yyidx + 0]->minor; } +#line 1580 "..\oql-parser.php" +#line 162 "..\oql-parser.y" + function yy_r63(){ $this->_retvalue = new VariableOqlExpression(substr($this->yystack[$this->yyidx + 0]->minor, 1)); } +#line 1583 "..\oql-parser.php" +#line 164 "..\oql-parser.y" + function yy_r64(){ if ($this->yystack[$this->yyidx + 0]->minor[0] == '`') { $name = substr($this->yystack[$this->yyidx + 0]->minor, 1, strlen($this->yystack[$this->yyidx + 0]->minor) - 2); @@ -1566,22 +1589,22 @@ static public $yy_action = array( } $this->_retvalue = new OqlName($name, $this->m_iColPrev); } -#line 1573 "..\oql-parser.php" -#line 167 "..\oql-parser.y" - function yy_r62(){$this->_retvalue=(int)$this->yystack[$this->yyidx + 0]->minor; } -#line 1576 "..\oql-parser.php" -#line 168 "..\oql-parser.y" - function yy_r63(){$this->_retvalue=(int)-$this->yystack[$this->yyidx + 0]->minor; } -#line 1579 "..\oql-parser.php" -#line 169 "..\oql-parser.y" - function yy_r64(){$this->_retvalue=new OqlHexValue($this->yystack[$this->yyidx + 0]->minor); } -#line 1582 "..\oql-parser.php" -#line 170 "..\oql-parser.y" - function yy_r65(){$this->_retvalue=stripslashes(substr($this->yystack[$this->yyidx + 0]->minor, 1, strlen($this->yystack[$this->yyidx + 0]->minor) - 2)); } -#line 1585 "..\oql-parser.php" -#line 173 "..\oql-parser.y" - function yy_r66(){$this->_retvalue=$this->yystack[$this->yyidx + 0]->minor; } -#line 1588 "..\oql-parser.php" +#line 1596 "..\oql-parser.php" +#line 175 "..\oql-parser.y" + function yy_r65(){$this->_retvalue=(int)$this->yystack[$this->yyidx + 0]->minor; } +#line 1599 "..\oql-parser.php" +#line 176 "..\oql-parser.y" + function yy_r66(){$this->_retvalue=(int)-$this->yystack[$this->yyidx + 0]->minor; } +#line 1602 "..\oql-parser.php" +#line 177 "..\oql-parser.y" + function yy_r67(){$this->_retvalue=new OqlHexValue($this->yystack[$this->yyidx + 0]->minor); } +#line 1605 "..\oql-parser.php" +#line 178 "..\oql-parser.y" + function yy_r68(){$this->_retvalue=stripslashes(substr($this->yystack[$this->yyidx + 0]->minor, 1, strlen($this->yystack[$this->yyidx + 0]->minor) - 2)); } +#line 1608 "..\oql-parser.php" +#line 181 "..\oql-parser.y" + function yy_r69(){$this->_retvalue=$this->yystack[$this->yyidx + 0]->minor; } +#line 1611 "..\oql-parser.php" /** * placeholder for the left hand side in a reduce operation. @@ -1696,7 +1719,7 @@ static public $yy_action = array( #line 25 "..\oql-parser.y" throw new OQLParserException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol, $this->tokenName($yymajor), $TOKEN); -#line 1704 "..\oql-parser.php" +#line 1727 "..\oql-parser.php" } /** @@ -1863,7 +1886,7 @@ throw new OQLParserException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCo } while ($yymajor != self::YYNOCODE && $this->yyidx >= 0); } } -#line 231 "..\oql-parser.y" +#line 239 "..\oql-parser.y" class OQLParserException extends OQLException @@ -1928,4 +1951,4 @@ class OQLParser extends OQLParserRaw } } -#line 1937 "..\oql-parser.php" +#line 1960 "..\oql-parser.php" diff --git a/core/oql/oql-parser.y b/core/oql/oql-parser.y index 957468d35..03bb3fe52 100644 --- a/core/oql/oql-parser.y +++ b/core/oql/oql-parser.y @@ -26,9 +26,17 @@ TODO : solve the 2 remaining shift-reduce conflicts (JOIN) throw new OQLParserException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol, $this->tokenName($yymajor), $TOKEN); } +result ::= union(X). { $this->my_result = X; } result ::= query(X). { $this->my_result = X; } result ::= condition(X). { $this->my_result = X; } +union(A) ::= query(X) UNION query(Y). { + A = new OqlUnionQuery(X, Y); +} +union(A) ::= query(X) UNION union(Y). { + A = new OqlUnionQuery(X, Y); +} + query(A) ::= SELECT class_name(X) join_statement(J) where_statement(W). { A = new OqlObjectQuery(X, X, W, J, array(X)); } diff --git a/core/oql/oqlinterpreter.class.inc.php b/core/oql/oqlinterpreter.class.inc.php index d4119f583..02abc3de4 100644 --- a/core/oql/oqlinterpreter.class.inc.php +++ b/core/oql/oqlinterpreter.class.inc.php @@ -1,5 +1,5 @@ Parse(); - if (!$oRes instanceof OqlObjectQuery) + if (!$oRes instanceof OqlQuery) { throw new OQLException('Expecting an OQL query', $this->m_sQuery, 0, 0, get_class($oRes)); } diff --git a/core/oql/oqlquery.class.inc.php b/core/oql/oqlquery.class.inc.php index 946c75724..e3a765311 100644 --- a/core/oql/oqlquery.class.inc.php +++ b/core/oql/oqlquery.class.inc.php @@ -20,7 +20,7 @@ /** * Classes defined for lexical analyze (see oql-parser.y) * - * @copyright Copyright (C) 2010-2012 Combodo SARL + * @copyright Copyright (C) 2010-2015 Combodo SARL * @license http://opensource.org/licenses/AGPL-3.0 */ @@ -266,23 +266,18 @@ class IntervalOqlExpression extends IntervalExpression implements CheckableExpre abstract class OqlQuery { - protected $m_aJoins; // array of OqlJoinSpec - protected $m_oCondition; // condition tree (expressions) - - public function __construct($oCondition = null, $aJoins = null) + public function __construct() { - $this->m_aJoins = $aJoins; - $this->m_oCondition = $oCondition; } - public function GetJoins() - { - return $this->m_aJoins; - } - public function GetCondition() - { - return $this->m_oCondition; - } + /** + * Check the validity of the expression with regard to the data model + * and the query in which it is used + * + * @param ModelReflection $oModelReflection MetaModel to consider + * @throws OqlNormalizeException + */ + abstract public function Check(ModelReflection $oModelReflection, $sSourceQuery); } class OqlObjectQuery extends OqlQuery @@ -290,13 +285,18 @@ class OqlObjectQuery extends OqlQuery protected $m_aSelect; // array of selected classes protected $m_oClass; protected $m_oClassAlias; + protected $m_aJoins; // array of OqlJoinSpec + protected $m_oCondition; // condition tree (expressions) public function __construct($oClass, $oClassAlias, $oCondition = null, $aJoins = null, $aSelect = null) { $this->m_aSelect = $aSelect; $this->m_oClass = $oClass; $this->m_oClassAlias = $oClassAlias; - parent::__construct($oCondition, $aJoins); + $this->m_aJoins = $aJoins; + $this->m_oCondition = $oCondition; + + parent::__construct(); } public function GetSelectedClasses() @@ -321,6 +321,15 @@ class OqlObjectQuery extends OqlQuery return $this->m_oClassAlias; } + public function GetJoins() + { + return $this->m_aJoins; + } + public function GetCondition() + { + return $this->m_oCondition; + } + /** * Recursively check the validity of the expression with regard to the data model * and the query in which it is used @@ -459,7 +468,6 @@ class OqlObjectQuery extends OqlQuery // Check the select information // - $aSelected = array(); foreach ($this->GetSelectedClasses() as $oClassDetails) { $sClassToSelect = $oClassDetails->GetValue(); @@ -467,7 +475,6 @@ class OqlObjectQuery extends OqlQuery { throw new OqlNormalizeException('Unknown class [alias]', $sSourceQuery, $oClassDetails, array_keys($aAliases)); } - $aSelected[$sClassToSelect] = $aAliases[$sClassToSelect]; } // Check the condition tree @@ -477,6 +484,128 @@ class OqlObjectQuery extends OqlQuery $this->m_oCondition->Check($oModelReflection, $aAliases, $sSourceQuery); } } + + /** + * Make the relevant DBSearch instance (FromOQL) + */ + public function ToDBSearch($sQuery) + { + $sClass = $this->GetClass(); + $sClassAlias = $this->GetClassAlias(); + + $oSearch = new DBObjectSearch($sClass, $sClassAlias); + $oSearch->InitFromOqlQuery($this, $sQuery); + return $oSearch; + } } -?> +class OqlUnionQuery extends OqlQuery +{ + protected $aQueries; + + public function __construct(OqlObjectQuery $oLeftQuery, OqlQuery $oRightQueryOrUnion) + { + $this->aQueries[] = $oLeftQuery; + if ($oRightQueryOrUnion instanceof OqlUnionQuery) + { + foreach ($oRightQueryOrUnion->GetQueries() as $oSingleQuery) + { + $this->aQueries[] = $oSingleQuery; + } + } + else + { + $this->aQueries[] = $oRightQueryOrUnion; + } + } + + public function GetQueries() + { + return $this->aQueries; + } + + /** + * Check the validity of the expression with regard to the data model + * and the query in which it is used + * + * @param ModelReflection $oModelReflection MetaModel to consider + * @throws OqlNormalizeException + */ + public function Check(ModelReflection $oModelReflection, $sSourceQuery) + { + $aColumnToClasses = array(); + foreach ($this->aQueries as $iQuery => $oQuery) + { + $oQuery->Check($oModelReflection, $sSourceQuery); + + $aAliasToClass = array($oQuery->GetClassAlias() => $oQuery->GetClass()); + $aJoinSpecs = $oQuery->GetJoins(); + if (is_array($aJoinSpecs)) + { + foreach ($aJoinSpecs as $oJoinSpec) + { + $aAliasToClass[$oJoinSpec->GetClassAlias()] = $oJoinSpec->GetClass(); + } + } + + $aSelectedClasses = $oQuery->GetSelectedClasses(); + if ($iQuery != 0) + { + if (count($aSelectedClasses) < count($aColumnToClasses)) + { + $oLastClass = end($aSelectedClasses); + throw new OqlNormalizeException('Too few selected classes in the subquery', $sSourceQuery, $oLastClass); + } + if (count($aSelectedClasses) > count($aColumnToClasses)) + { + $oLastClass = end($aSelectedClasses); + throw new OqlNormalizeException('Too many selected classes in the subquery', $sSourceQuery, $oLastClass); + } + } + foreach ($aSelectedClasses as $iColumn => $oClassDetails) + { + $sAlias = $oClassDetails->GetValue(); + $sClass = $aAliasToClass[$sAlias]; + $aColumnToClasses[$iColumn][] = array( + 'alias' => $sAlias, + 'class' => $sClass, + 'class_name' => $oClassDetails, + ); + } + } + foreach ($aColumnToClasses as $iColumn => $aClasses) + { + foreach ($aClasses as $iQuery => $aData) + { + if ($iQuery == 0) + { + // Establish the reference + $sRootClass = MetaModel::GetRootClass($aData['class']); + } + else + { + if (MetaModel::GetRootClass($aData['class']) != $sRootClass) + { + $aSubclasses = MetaModel::EnumChildClasses($sRootClass, ENUM_CHILD_CLASSES_ALL); + throw new OqlNormalizeException('Incompatible classes: could not find a common ancestor', $sSourceQuery, $aData['class_name'], $aSubclasses); + } + } + } + } + } + + /** + * Make the relevant DBSearch instance (FromOQL) + */ + public function ToDBSearch($sQuery) + { + $aSearches = array(); + foreach ($this->aQueries as $oQuery) + { + $aSearches[] = $oQuery->ToDBSearch($sQuery); + } + + $oSearch = new DBUnionSearch($aSearches); + return $oSearch; + } +} \ No newline at end of file diff --git a/core/querybuildercontext.class.inc.php b/core/querybuildercontext.class.inc.php index b72c2bc32..fd46b6af2 100644 --- a/core/querybuildercontext.class.inc.php +++ b/core/querybuildercontext.class.inc.php @@ -1,5 +1,5 @@ MakeQuery/MakeQuerySingleTable * - * @copyright Copyright (C) 2010-2012 Combodo SARL + * @copyright Copyright (C) 2010-2015 Combodo SARL * @license http://opensource.org/licenses/AGPL-3.0 */ @@ -29,10 +29,11 @@ class QueryBuilderContext protected $m_aClassAliases; protected $m_aTableAliases; protected $m_aModifierProperties; + protected $m_aSelectedClasses; public $m_oQBExpressions; - public function __construct($oFilter, $aModifierProperties, $aGroupByExpr = null) + public function __construct($oFilter, $aModifierProperties, $aGroupByExpr = null, $aSelectedClasses = null) { $this->m_oRootFilter = $oFilter; $this->m_oQBExpressions = new QueryBuilderExpressions($oFilter, $aGroupByExpr); @@ -41,6 +42,15 @@ class QueryBuilderContext $this->m_aTableAliases = array(); $this->m_aModifierProperties = $aModifierProperties; + if (is_null($aSelectedClasses)) + { + $this->m_aSelectedClasses = $oFilter->GetSelectedClasses(); + } + else + { + // For the unions, the selected classes can be upper in the hierarchy (lowest common ancestor) + $this->m_aSelectedClasses = $aSelectedClasses; + } } public function GetRootFilter() @@ -69,6 +79,9 @@ class QueryBuilderContext return array(); } } -} -?> \ No newline at end of file + public function GetSelectedClass($sAlias) + { + return $this->m_aSelectedClasses[$sAlias]; + } +} diff --git a/core/sqlobjectquery.class.inc.php b/core/sqlobjectquery.class.inc.php new file mode 100644 index 000000000..2b43ee2c2 --- /dev/null +++ b/core/sqlobjectquery.class.inc.php @@ -0,0 +1,587 @@ + + + +/** + * SQLObjectQuery + * build a mySQL compatible SQL query + * + * @copyright Copyright (C) 2015 Combodo SARL + * @license http://opensource.org/licenses/AGPL-3.0 + */ + + +/** + * SQLObjectQuery + * build a mySQL compatible SQL query + * + * @package iTopORM + */ + + +class SQLObjectQuery extends SQLQuery +{ + private $m_SourceOQL = ''; + 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 + private $m_oSelectedIdField = null; + private $m_aJoinSelects = array(); + private $m_bBeautifulQuery = false; + + // Data set by PrepareRendering() + private $__aFrom; + private $__aFields; + private $__aGroupBy; + private $__aDelTables; + private $__aSetValues; + private $__aSelectedIdFields; + + + public function __construct($sTable, $sTableAlias, $aFields, $bToDelete = true, $aValues = array(), $oSelectedIdField = null) + { + parent::__construct(); + + // This check is not needed but for developping purposes + //if (!CMDBSource::IsTable($sTable)) + //{ + // throw new CoreException("Unknown table '$sTable'"); + //} + + // $aFields must be an array of "alias"=>"expr" + // $oConditionExpr must be a condition tree + // $aValues is an array of "alias"=>value + + $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; + $this->m_oSelectedIdField = $oSelectedIdField; + } + + public function GetTableAlias() + { + return $this->m_sTableAlias; + } + + public function DisplayHtml() + { + if (count($this->m_aFields) == 0) $sFields = ""; + else + { + $aFieldDesc = array(); + foreach ($this->m_aFields as $sAlias => $oExpression) + { + $aFieldDesc[] = $oExpression->Render()." as $sAlias"; + } + $sFields = " => ".implode(', ', $aFieldDesc); + } + echo "$this->m_sTable$sFields
\n"; + // #@# todo - display html of an expression tree + //$this->m_oConditionExpr->DisplayHtml() + if (count($this->m_aJoinSelects) > 0) + { + echo "Joined to...
\n"; + echo "
    \n"; + foreach ($this->m_aJoinSelects as $aJoinInfo) + { + $sJoinType = $aJoinInfo["jointype"]; + $oSQLQuery = $aJoinInfo["select"]; + if (isset($aJoinInfo["on_expression"])) + { + $sOnCondition = $aJoinInfo["on_expression"]->Render(); + + echo "
  • Join '$sJoinType', ON ($sOnCondition)".$oSQLQuery->DisplayHtml()."
  • \n"; + } + else + { + $sLeftField = $aJoinInfo["leftfield"]; + $sRightField = $aJoinInfo["rightfield"]; + $sRightTableAlias = $aJoinInfo["righttablealias"]; + + echo "
  • Join '$sJoinType', $sLeftField, $sRightTableAlias.$sRightField".$oSQLQuery->DisplayHtml()."
  • \n"; + } + } + echo "
"; + } + $this->PrepareRendering(); + echo "From ...
\n"; + echo "
\n";
+		print_r($this->__aFrom);
+		echo "
"; + } + + public function SetSelect($aExpressions) + { + $this->m_aFields = $aExpressions; + } + + public function AddSelect($sAlias, $oExpression) + { + $this->m_aFields[$sAlias] = $oExpression; + } + + public function SetGroupBy($aExpressions) + { + $this->m_aGroupBy = $aExpressions; + } + + public function SetCondition($oConditionExpr) + { + $this->m_oConditionExpr = $oConditionExpr; + } + + public function AddCondition($oConditionExpr) + { + if (is_null($this->m_oConditionExpr)) + { + $this->m_oConditionExpr = $oConditionExpr; + } + else + { + $this->m_oConditionExpr->LogAnd($oConditionExpr); + } + } + + private function AddJoin($sJoinType, $oSQLQuery, $sLeftField, $sRightField, $sRightTableAlias = '') + { + assert((get_class($oSQLQuery) == __CLASS__) || is_subclass_of($oSQLQuery, __CLASS__)); + // No need to check this here but for development purposes + //if (!CMDBSource::IsField($this->m_sTable, $sLeftField)) + //{ + // throw new CoreException("Unknown field '$sLeftField' in table '".$this->m_sTable); + //} + + if (empty($sRightTableAlias)) + { + $sRightTableAlias = $oSQLQuery->m_sTableAlias; + } +// #@# Could not be verified here because the namespace is unknown - do we need to check it there? +// +// if (!CMDBSource::IsField($sRightTable, $sRightField)) +// { +// throw new CoreException("Unknown field '$sRightField' in table '".$sRightTable."'"); +// } + $this->m_aJoinSelects[] = array( + "jointype" => $sJoinType, + "select" => $oSQLQuery, + "leftfield" => $sLeftField, + "rightfield" => $sRightField, + "righttablealias" => $sRightTableAlias + ); + } + public function AddInnerJoin($oSQLQuery, $sLeftField, $sRightField, $sRightTable = '') + { + $this->AddJoin("inner", $oSQLQuery, $sLeftField, $sRightField, $sRightTable); + } + public function AddInnerJoinTree($oSQLQuery, $sLeftFieldLeft, $sLeftFieldRight, $sRightFieldLeft, $sRightFieldRight, $sRightTableAlias = '', $iOperatorCode = TREE_OPERATOR_BELOW) + { + assert((get_class($oSQLQuery) == __CLASS__) || is_subclass_of($oSQLQuery, __CLASS__)); + if (empty($sRightTableAlias)) + { + $sRightTableAlias = $oSQLQuery->m_sTableAlias; + } + $this->m_aJoinSelects[] = array( + "jointype" => 'inner_tree', + "select" => $oSQLQuery, + "leftfield" => $sLeftFieldLeft, + "rightfield" => $sLeftFieldRight, + "rightfield_left" => $sRightFieldLeft, + "rightfield_right" => $sRightFieldRight, + "righttablealias" => $sRightTableAlias, + "tree_operator" => $iOperatorCode); + } + public function AddLeftJoin($oSQLQuery, $sLeftField, $sRightField) + { + return $this->AddJoin("left", $oSQLQuery, $sLeftField, $sRightField); + } + + public function AddInnerJoinEx(SQLQuery $oSQLQuery, Expression $oOnExpression) + { + $this->m_aJoinSelects[] = array( + "jointype" => 'inner', + "select" => $oSQLQuery, + "on_expression" => $oOnExpression + ); + } + + public function AddLeftJoinEx(SQLQuery $oSQLQuery, Expression $oOnExpression) + { + $this->m_aJoinSelects[] = array( + "jointype" => 'left', + "select" => $oSQLQuery, + "on_expression" => $oOnExpression + ); + } + + // Interface, build the SQL query + public function RenderDelete($aArgs = array()) + { + $this->PrepareRendering(); + + // Target: DELETE myAlias1, myAlias2 FROM t1 as myAlias1, t2 as myAlias2, t3 as topreserve WHERE ... + + $sDelete = self::ClauseDelete($this->__aDelTables); + $sFrom = self::ClauseFrom($this->__aFrom); + // #@# safety net to redo ? + /* + if ($this->m_oConditionExpr->IsAny()) + -- if (count($aConditions) == 0) -- + { + throw new CoreException("Building a request wich will delete every object of a given table -looks suspicious- please use truncate instead..."); + } + */ + if (is_null($this->m_oConditionExpr)) + { + // Delete all !!! + } + else + { + $sWhere = self::ClauseWhere($this->m_oConditionExpr, $aArgs); + return "DELETE $sDelete FROM $sFrom WHERE $sWhere"; + } + } + + /** + * Needed for the unions + */ + public function RenderSelectClause() + { + $this->PrepareRendering(); + $sSelect = self::ClauseSelect($this->__aFields); + return $sSelect; + } + + /** + * Needed for the unions + */ + public function RenderOrderByClause($aOrderBy) + { + $this->PrepareRendering(); + $sOrderBy = self::ClauseOrderBy($aOrderBy); + return $sOrderBy; + } + + // Interface, build the SQL query + public function RenderUpdate($aArgs = array()) + { + $this->PrepareRendering(); + $sFrom = self::ClauseFrom($this->__aFrom); + $sValues = self::ClauseValues($this->__aSetValues); + $sWhere = self::ClauseWhere($this->m_oConditionExpr, $aArgs); + return "UPDATE $sFrom SET $sValues WHERE $sWhere"; + } + + // Interface, build the SQL query + public function RenderSelect($aOrderBy = array(), $aArgs = array(), $iLimitCount = 0, $iLimitStart = 0, $bGetCount = false, $bBeautifulQuery = false) + { + $this->m_bBeautifulQuery = $bBeautifulQuery; + $sLineSep = $this->m_bBeautifulQuery ? "\n" : ''; + $sIndent = $this->m_bBeautifulQuery ? " " : null; + + $this->PrepareRendering(); + $sFrom = self::ClauseFrom($this->__aFrom, $sIndent); + $sWhere = self::ClauseWhere($this->m_oConditionExpr, $aArgs); + if ($bGetCount) + { + if (count($this->__aSelectedIdFields) > 0) + { + $aCountFields = array(); + foreach ($this->__aSelectedIdFields as $sFieldExpr) + { + $aCountFields[] = "COALESCE($sFieldExpr, 0)"; // Null values are excluded from the count + } + $sCountFields = implode(', ', $aCountFields); + $sSQL = "SELECT$sLineSep COUNT(DISTINCT $sCountFields) AS COUNT$sLineSep FROM $sFrom$sLineSep WHERE $sWhere"; + } + else + { + $sSQL = "SELECT$sLineSep COUNT(*) AS COUNT$sLineSep FROM $sFrom$sLineSep WHERE $sWhere"; + } + } + else + { + $sSelect = self::ClauseSelect($this->__aFields); + $sOrderBy = self::ClauseOrderBy($aOrderBy); + if (!empty($sOrderBy)) + { + $sOrderBy = "ORDER BY $sOrderBy$sLineSep"; + } + if ($iLimitCount > 0) + { + $sLimit = 'LIMIT '.$iLimitStart.', '.$iLimitCount; + } + else + { + $sLimit = ''; + } + $sSQL = "SELECT$sLineSep DISTINCT $sSelect$sLineSep FROM $sFrom$sLineSep WHERE $sWhere$sLineSep $sOrderBy $sLimit"; + } + return $sSQL; + } + + // Interface, build the SQL query + public function RenderGroupBy($aArgs = array(), $bBeautifulQuery = false) + { + $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"; + return $sSQL; + } + + // Purpose: prepare the query data, once for all + private function PrepareRendering() + { + if (is_null($this->__aFrom)) + { + $this->__aFrom = array(); + $this->__aFields = array(); + $this->__aGroupBy = array(); + $this->__aDelTables = array(); + $this->__aSetValues = array(); + $this->__aSelectedIdFields = array(); + + $this->PrepareSingleTable($this, $this->__aFrom, '', array('jointype' => 'first')); + } + } + + private function PrepareSingleTable(SQLObjectQuery $oRootQuery, &$aFrom, $sCallerAlias = '', $aJoinData) + { + $aTranslationTable[$this->m_sTable]['*'] = $this->m_sTableAlias; + + // Handle the various kinds of join (or first table in the list) + // + if (empty($aJoinData['righttablealias'])) + { + $sRightTableAlias = $this->m_sTableAlias; + } + else + { + $sRightTableAlias = $aJoinData['righttablealias']; + } + switch ($aJoinData['jointype']) + { + case "first": + $aFrom[$this->m_sTableAlias] = array("jointype"=>"first", "tablename"=>$this->m_sTable, "joincondition"=>""); + break; + case "inner": + case "left": + if (isset($aJoinData["on_expression"])) + { + $sJoinCond = $aJoinData["on_expression"]->Render(); + } + else + { + $sJoinCond = "`$sCallerAlias`.`{$aJoinData['leftfield']}` = `$sRightTableAlias`.`{$aJoinData['rightfield']}`"; + } + $aFrom[$this->m_sTableAlias] = array("jointype"=>$aJoinData['jointype'], "tablename"=>$this->m_sTable, "joincondition"=>"$sJoinCond"); + break; + case "inner_tree": + $sNodeLeft = "`$sCallerAlias`.`{$aJoinData['leftfield']}`"; + $sNodeRight = "`$sCallerAlias`.`{$aJoinData['rightfield']}`"; + $sRootLeft = "`$sRightTableAlias`.`{$aJoinData['rightfield_left']}`"; + $sRootRight = "`$sRightTableAlias`.`{$aJoinData['rightfield_right']}`"; + switch($aJoinData['tree_operator']) + { + case TREE_OPERATOR_BELOW: + $sJoinCond = "$sNodeLeft >= $sRootLeft AND $sNodeLeft <= $sRootRight"; + break; + + case TREE_OPERATOR_BELOW_STRICT: + $sJoinCond = "$sNodeLeft > $sRootLeft AND $sNodeLeft < $sRootRight"; + break; + + case TREE_OPERATOR_NOT_BELOW: // Complementary of 'BELOW' + $sJoinCond = "$sNodeLeft < $sRootLeft OR $sNodeLeft > $sRootRight"; + break; + + case TREE_OPERATOR_NOT_BELOW_STRICT: // Complementary of BELOW_STRICT + $sJoinCond = "$sNodeLeft <= $sRootLeft OR $sNodeLeft >= $sRootRight"; + break; + + case TREE_OPERATOR_ABOVE: + $sJoinCond = "$sNodeLeft <= $sRootLeft AND $sNodeRight >= $sRootRight"; + break; + + case TREE_OPERATOR_ABOVE_STRICT: + $sJoinCond = "$sNodeLeft < $sRootLeft AND $sNodeRight > $sRootRight"; + break; + + case TREE_OPERATOR_NOT_ABOVE: // Complementary of 'ABOVE' + $sJoinCond = "$sNodeLeft > $sRootLeft OR $sNodeRight < $sRootRight"; + break; + + case TREE_OPERATOR_NOT_ABOVE_STRICT: // Complementary of ABOVE_STRICT + $sJoinCond = "$sNodeLeft >= $sRootLeft OR $sNodeRight <= $sRootRight"; + break; + + } + $aFrom[$this->m_sTableAlias] = array("jointype"=>$aJoinData['jointype'], "tablename"=>$this->m_sTable, "joincondition"=>"$sJoinCond"); + break; + } + + // Given the alias, modify the fields and conditions + // before adding them into the current lists + // + foreach($this->m_aFields as $sAlias => $oExpression) + { + $oRootQuery->__aFields["`$sAlias`"] = $oExpression->Render(); + } + if ($this->m_aGroupBy) + { + foreach($this->m_aGroupBy as $sAlias => $oExpression) + { + $oRootQuery->__aGroupBy["`$sAlias`"] = $oExpression->Render(); + } + } + if ($this->m_bToDelete) + { + $oRootQuery->__aDelTables[] = "`{$this->m_sTableAlias}`"; + } + foreach($this->m_aValues as $sFieldName=>$value) + { + $oRootQuery->__aSetValues["`{$this->m_sTableAlias}`.`$sFieldName`"] = $value; // quoted further! + } + + if (!is_null($this->m_oSelectedIdField)) + { + $oRootQuery->__aSelectedIdFields[] = $this->m_oSelectedIdField->Render(); + } + + // loop on joins, to complete the list of tables/fields/conditions + // + $aTempFrom = array(); // temporary subset of 'from' specs, to be grouped in the final query + foreach ($this->m_aJoinSelects as $aJoinData) + { + $oRightSelect = $aJoinData["select"]; + + $sJoinTableAlias = $oRightSelect->PrepareSingleTable($oRootQuery, $aTempFrom, $this->m_sTableAlias, $aJoinData); + } + $aFrom[$this->m_sTableAlias]['subfrom'] = $aTempFrom; + + return $this->m_sTableAlias; + } + + public function OptimizeJoins($aUsedTables, $bTopCall = true) + { + if ($bTopCall) + { + // Top call: complete the list of tables absolutely required to perform the right query + $this->CollectUsedTables($aUsedTables); + } + + $aToDiscard = array(); + foreach ($this->m_aJoinSelects as $i => $aJoinInfo) + { + $oSQLQuery = $aJoinInfo["select"]; + $sTableAlias = $oSQLQuery->GetTableAlias(); + if ($oSQLQuery->OptimizeJoins($aUsedTables, false) && !array_key_exists($sTableAlias, $aUsedTables)) + { + $aToDiscard[] = $i; + } + } + foreach ($aToDiscard as $i) + { + unset($this->m_aJoinSelects[$i]); + } + + return (count($this->m_aJoinSelects) == 0); + } + + protected function CollectUsedTables(&$aTables) + { + $this->m_oConditionExpr->CollectUsedParents($aTables); + foreach($this->m_aFields as $sFieldAlias => $oField) + { + $oField->CollectUsedParents($aTables); + } + if ($this->m_aGroupBy) + { + foreach($this->m_aGroupBy as $sAlias => $oExpression) + { + $oExpression->CollectUsedParents($aTables); + } + } + if (!is_null($this->m_oSelectedIdField)) + { + $this->m_oSelectedIdField->CollectUsedParents($aTables); + } + + foreach ($this->m_aJoinSelects as $i => $aJoinInfo) + { + $oSQLQuery = $aJoinInfo["select"]; + if ($oSQLQuery->HasRequiredTables($aTables)) + { + // There is something required in the branch, then this node is a MUST + if (isset($aJoinInfo['righttablealias'])) + { + $aTables[$aJoinInfo['righttablealias']] = true; + } + if (isset($aJoinInfo["on_expression"])) + { + $sJoinCond = $aJoinInfo["on_expression"]->CollectUsedParents($aTables); + } + } + } + + return $aTables; + } + + // Is required in the JOIN, and therefore we must ensure that the join expression will be valid + protected function HasRequiredTables(&$aTables) + { + $bResult = false; + if (array_key_exists($this->m_sTableAlias, $aTables)) + { + $bResult = true; + } + foreach ($this->m_aJoinSelects as $i => $aJoinInfo) + { + $oSQLQuery = $aJoinInfo["select"]; + if ($oSQLQuery->HasRequiredTables($aTables)) + { + // There is something required in the branch, then this node is a MUST + if (isset($aJoinInfo['righttablealias'])) + { + $aTables[$aJoinInfo['righttablealias']] = true; + } + if (isset($aJoinInfo["on_expression"])) + { + $sJoinCond = $aJoinInfo["on_expression"]->CollectUsedParents($aTables); + } + $bResult = true; + } + } + // None of the tables is in the list of required tables + return $bResult; + } +} diff --git a/core/sqlquery.class.inc.php b/core/sqlquery.class.inc.php index e9dc9a85a..57b15e5bd 100644 --- a/core/sqlquery.class.inc.php +++ b/core/sqlquery.class.inc.php @@ -1,5 +1,5 @@ "expr" - // $oConditionExpr must be a condition tree - // $aValues is an array of "alias"=>value - - $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; - $this->m_oSelectedIdField = $oSelectedIdField; } /** @@ -80,11 +53,6 @@ class SQLQuery return unserialize(serialize($this)); } - public function GetTableAlias() - { - return $this->m_sTableAlias; - } - public function SetSourceOQL($sOQL) { $this->m_SourceOQL = $sOQL; @@ -95,295 +63,17 @@ class SQLQuery return $this->m_SourceOQL; } - public function DisplayHtml() - { - if (count($this->m_aFields) == 0) $sFields = ""; - else - { - $aFieldDesc = array(); - foreach ($this->m_aFields as $sAlias => $oExpression) - { - $aFieldDesc[] = $oExpression->Render()." as $sAlias"; - } - $sFields = " => ".implode(', ', $aFieldDesc); - } - echo "$this->m_sTable$sFields
\n"; - // #@# todo - display html of an expression tree - //$this->m_oConditionExpr->DisplayHtml() - if (count($this->m_aJoinSelects) > 0) - { - echo "Joined to...
\n"; - echo "
    \n"; - foreach ($this->m_aJoinSelects as $aJoinInfo) - { - $sJoinType = $aJoinInfo["jointype"]; - $oSQLQuery = $aJoinInfo["select"]; - if (isset($aJoinInfo["on_expression"])) - { - $sOnCondition = $aJoinInfo["on_expression"]->Render(); + abstract public function AddInnerJoin($oSQLQuery, $sLeftField, $sRightField, $sRightTable = ''); - echo "
  • Join '$sJoinType', ON ($sOnCondition)".$oSQLQuery->DisplayHtml()."
  • \n"; - } - else - { - $sLeftField = $aJoinInfo["leftfield"]; - $sRightField = $aJoinInfo["rightfield"]; - $sRightTableAlias = $aJoinInfo["righttablealias"]; - - echo "
  • Join '$sJoinType', $sLeftField, $sRightTableAlias.$sRightField".$oSQLQuery->DisplayHtml()."
  • \n"; - } - } - echo "
"; - } - $aFrom = array(); - $aFields = array(); - $aGroupBy = array(); - $oCondition = null; - $aDelTables = array(); - $aSetValues = array(); - $aSelectedIdFields = array(); - $this->privRender($aFrom, $aFields, $aGroupBy, $oCondition, $aDelTables, $aSetValues, $aSelectedIdFields); - echo "From ...
\n"; - echo "
\n";
-		print_r($aFrom);
-		echo "
"; - } + abstract public function DisplayHtml(); + 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); - public function SetSelect($aExpressions) - { - $this->m_aFields = $aExpressions; - } + abstract public function OptimizeJoins($aUsedTables, $bTopCall = true); - public function SetGroupBy($aExpressions) - { - $this->m_aGroupBy = $aExpressions; - } - - public function SetCondition($oConditionExpr) - { - $this->m_oConditionExpr = $oConditionExpr; - } - - public function AddCondition($oConditionExpr) - { - if (is_null($this->m_oConditionExpr)) - { - $this->m_oConditionExpr = $oConditionExpr; - } - else - { - $this->m_oConditionExpr->LogAnd($oConditionExpr); - } - } - - private function AddJoin($sJoinType, $oSQLQuery, $sLeftField, $sRightField, $sRightTableAlias = '') - { - assert((get_class($oSQLQuery) == __CLASS__) || is_subclass_of($oSQLQuery, __CLASS__)); - // No need to check this here but for development purposes - //if (!CMDBSource::IsField($this->m_sTable, $sLeftField)) - //{ - // throw new CoreException("Unknown field '$sLeftField' in table '".$this->m_sTable); - //} - - if (empty($sRightTableAlias)) - { - $sRightTableAlias = $oSQLQuery->m_sTableAlias; - } -// #@# Could not be verified here because the namespace is unknown - do we need to check it there? -// -// if (!CMDBSource::IsField($sRightTable, $sRightField)) -// { -// throw new CoreException("Unknown field '$sRightField' in table '".$sRightTable."'"); -// } - $this->m_aJoinSelects[] = array( - "jointype" => $sJoinType, - "select" => $oSQLQuery, - "leftfield" => $sLeftField, - "rightfield" => $sRightField, - "righttablealias" => $sRightTableAlias - ); - } - public function AddInnerJoin($oSQLQuery, $sLeftField, $sRightField, $sRightTable = '') - { - $this->AddJoin("inner", $oSQLQuery, $sLeftField, $sRightField, $sRightTable); - } - public function AddInnerJoinTree($oSQLQuery, $sLeftFieldLeft, $sLeftFieldRight, $sRightFieldLeft, $sRightFieldRight, $sRightTableAlias = '', $iOperatorCode = TREE_OPERATOR_BELOW) - { - assert((get_class($oSQLQuery) == __CLASS__) || is_subclass_of($oSQLQuery, __CLASS__)); - if (empty($sRightTableAlias)) - { - $sRightTableAlias = $oSQLQuery->m_sTableAlias; - } - $this->m_aJoinSelects[] = array( - "jointype" => 'inner_tree', - "select" => $oSQLQuery, - "leftfield" => $sLeftFieldLeft, - "rightfield" => $sLeftFieldRight, - "rightfield_left" => $sRightFieldLeft, - "rightfield_right" => $sRightFieldRight, - "righttablealias" => $sRightTableAlias, - "tree_operator" => $iOperatorCode); - } - public function AddLeftJoin($oSQLQuery, $sLeftField, $sRightField) - { - return $this->AddJoin("left", $oSQLQuery, $sLeftField, $sRightField); - } - - public function AddInnerJoinEx(SQLQuery $oSQLQuery, Expression $oOnExpression) - { - $this->m_aJoinSelects[] = array( - "jointype" => 'inner', - "select" => $oSQLQuery, - "on_expression" => $oOnExpression - ); - } - - public function AddLeftJoinEx(SQLQuery $oSQLQuery, Expression $oOnExpression) - { - $this->m_aJoinSelects[] = array( - "jointype" => 'left', - "select" => $oSQLQuery, - "on_expression" => $oOnExpression - ); - } - - // Interface, build the SQL query - public function RenderDelete($aArgs = array()) - { - // 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, $aGroupBy, $oCondition, $aDelTables, $aSetValues, $aSelectedIdFields); - - // Target: DELETE myAlias1, myAlias2 FROM t1 as myAlias1, t2 as myAlias2, t3 as topreserve WHERE ... - - $sDelete = self::ClauseDelete($aDelTables); - $sFrom = self::ClauseFrom($aFrom); - // #@# safety net to redo ? - /* - if ($this->m_oConditionExpr->IsAny()) - -- if (count($aConditions) == 0) -- - { - throw new CoreException("Building a request wich will delete every object of a given table -looks suspicious- please use truncate instead..."); - } - */ - if (is_null($oCondition)) - { - // Delete all !!! - } - else - { - $sWhere = self::ClauseWhere($oCondition, $aArgs); - return "DELETE $sDelete FROM $sFrom WHERE $sWhere"; - } - } - - // Interface, build the SQL query - public function RenderUpdate($aArgs = array()) - { - // 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, $aGroupBy, $oCondition, $aDelTables, $aSetValues, $aSelectedIdFields); - $sFrom = self::ClauseFrom($aFrom); - $sValues = self::ClauseValues($aSetValues); - $sWhere = self::ClauseWhere($oCondition, $aArgs); - return "UPDATE $sFrom SET $sValues WHERE $sWhere"; - } - - // Interface, build the SQL query - public function RenderSelect($aOrderBy = array(), $aArgs = array(), $iLimitCount = 0, $iLimitStart = 0, $bGetCount = false, $bBeautifulQuery = false) - { - $this->m_bBeautifulQuery = $bBeautifulQuery; - $sLineSep = $this->m_bBeautifulQuery ? "\n" : ''; - $sIndent = $this->m_bBeautifulQuery ? " " : null; - - // 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); - - $sFrom = self::ClauseFrom($aFrom, $sIndent); - $sWhere = self::ClauseWhere($oCondition, $aArgs); - if ($bGetCount) - { - if (count($aSelectedIdFields) > 0) - { - $aCountFields = array(); - foreach ($aSelectedIdFields as $sFieldExpr) - { - $aCountFields[] = "COALESCE($sFieldExpr, 0)"; // Null values are excluded from the count - } - $sCountFields = implode(', ', $aCountFields); - $sSQL = "SELECT$sLineSep COUNT(DISTINCT $sCountFields) AS COUNT$sLineSep FROM $sFrom$sLineSep WHERE $sWhere"; - } - else - { - $sSQL = "SELECT$sLineSep COUNT(*) AS COUNT$sLineSep FROM $sFrom$sLineSep WHERE $sWhere"; - } - } - else - { - $sSelect = self::ClauseSelect($aFields); - $sOrderBy = self::ClauseOrderBy($aOrderBy); - if (!empty($sOrderBy)) - { - $sOrderBy = "ORDER BY $sOrderBy"; - } - if ($iLimitCount > 0) - { - $sLimit = 'LIMIT '.$iLimitStart.', '.$iLimitCount; - } - else - { - $sLimit = ''; - } - $sSQL = "SELECT$sLineSep DISTINCT $sSelect$sLineSep FROM $sFrom$sLineSep WHERE $sWhere$sLineSep $sOrderBy$sLineSep $sLimit"; - } - return $sSQL; - } - - // Interface, build the SQL query - public function RenderGroupBy($aArgs = array(), $bBeautifulQuery = false) - { - $this->m_bBeautifulQuery = $bBeautifulQuery; - $sLineSep = $this->m_bBeautifulQuery ? "\n" : ''; - $sIndent = $this->m_bBeautifulQuery ? " " : null; - - // 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, $sIndent); - $sWhere = self::ClauseWhere($oCondition, $aArgs); - $sGroupBy = self::ClauseGroupBy($aGroupBy); - $sSQL = "SELECT $sSelect,$sLineSep COUNT(*) AS _itop_count_$sLineSep FROM $sFrom$sLineSep WHERE $sWhere$sLineSep GROUP BY $sGroupBy"; - return $sSQL; - } - - private static function ClauseSelect($aFields) + protected static function ClauseSelect($aFields) { $aSelect = array(); foreach ($aFields as $sFieldAlias => $sSQLExpr) @@ -394,13 +84,13 @@ class SQLQuery return $sSelect; } - private static function ClauseGroupBy($aGroupBy) + protected static function ClauseGroupBy($aGroupBy) { $sRes = implode(', ', $aGroupBy); return $sRes; } - private static function ClauseDelete($aDelTableAliases) + protected static function ClauseDelete($aDelTableAliases) { $aDelTables = array(); foreach ($aDelTableAliases as $sTableAlias) @@ -411,7 +101,7 @@ class SQLQuery return $sDelTables; } - private static function ClauseFrom($aFrom, $sIndent = null, $iIndentLevel = 0) + protected static function ClauseFrom($aFrom, $sIndent = null, $iIndentLevel = 0) { $sLineBreakLong = $sIndent ? "\n".str_repeat($sIndent, $iIndentLevel + 1) : ''; $sLineBreak = $sIndent ? "\n".str_repeat($sIndent, $iIndentLevel) : ''; @@ -427,14 +117,32 @@ class SQLQuery break; case "inner": case "inner_tree": - $sFrom .= $sLineBreak."INNER JOIN ($sLineBreakLong`".$aJoinInfo["tablename"]."` AS `$sTableAlias`"; - $sFrom .= " ".self::ClauseFrom($aJoinInfo["subfrom"], $sIndent, $iIndentLevel + 1); - $sFrom .= $sLineBreak.") ON ".$aJoinInfo["joincondition"]; + if (count($aJoinInfo["subfrom"]) > 0) + { + $sFrom .= $sLineBreak."INNER JOIN ($sLineBreakLong`".$aJoinInfo["tablename"]."` AS `$sTableAlias`"; + $sFrom .= " ".self::ClauseFrom($aJoinInfo["subfrom"], $sIndent, $iIndentLevel + 1); + $sFrom .= $sLineBreak.") ON ".$aJoinInfo["joincondition"]; + } + else + { + // Unions do not suffer parenthesis around the "table AS alias" + $sFrom .= $sLineBreak."INNER JOIN $sLineBreakLong`".$aJoinInfo["tablename"]."` AS `$sTableAlias`"; + $sFrom .= $sLineBreak." ON ".$aJoinInfo["joincondition"]; + } break; case "left": - $sFrom .= $sLineBreak."LEFT JOIN ($sLineBreakLong`".$aJoinInfo["tablename"]."` AS `$sTableAlias`"; - $sFrom .= " ".self::ClauseFrom($aJoinInfo["subfrom"], $sIndent, $iIndentLevel + 1); - $sFrom .= $sLineBreak.") ON ".$aJoinInfo["joincondition"]; + if (count($aJoinInfo["subfrom"]) > 0) + { + $sFrom .= $sLineBreak."LEFT JOIN ($sLineBreakLong`".$aJoinInfo["tablename"]."` AS `$sTableAlias`"; + $sFrom .= " ".self::ClauseFrom($aJoinInfo["subfrom"], $sIndent, $iIndentLevel + 1); + $sFrom .= $sLineBreak.") ON ".$aJoinInfo["joincondition"]; + } + else + { + // Unions do not suffer parenthesis around the "table AS alias" + $sFrom .= $sLineBreak."LEFT JOIN $sLineBreakLong`".$aJoinInfo["tablename"]."` AS `$sTableAlias`"; + $sFrom .= $sLineBreak." ON ".$aJoinInfo["joincondition"]; + } break; default: throw new CoreException("Unknown jointype: '".$aJoinInfo["jointype"]."'"); @@ -443,7 +151,7 @@ class SQLQuery return $sFrom; } - private static function ClauseValues($aValues) + protected static function ClauseValues($aValues) { $aSetValues = array(); foreach ($aValues as $sFieldSpec => $value) @@ -454,7 +162,7 @@ class SQLQuery return $sSetValues; } - private static function ClauseWhere($oConditionExpr, $aArgs = array()) + protected static function ClauseWhere($oConditionExpr, $aArgs = array()) { if (is_null($oConditionExpr)) { @@ -466,7 +174,7 @@ class SQLQuery } } - private static function ClauseOrderBy($aOrderBy) + protected static function ClauseOrderBy($aOrderBy) { $aOrderBySpec = array(); foreach($aOrderBy as $sFieldAlias => $bAscending) @@ -477,224 +185,4 @@ class SQLQuery $sOrderBy = implode(", ", $aOrderBySpec); return $sOrderBy; } - - // Purpose: prepare the query data, once for all - private function privRender(&$aFrom, &$aFields, &$aGroupBy, &$oCondition, &$aDelTables, &$aSetValues, &$aSelectedIdFields) - { - $sTableAlias = $this->privRenderSingleTable($aFrom, $aFields, $aGroupBy, $aDelTables, $aSetValues, $aSelectedIdFields, '', array('jointype' => 'first')); - $oCondition = $this->m_oConditionExpr; - return $sTableAlias; - } - - private function privRenderSingleTable(&$aFrom, &$aFields, &$aGroupBy, &$aDelTables, &$aSetValues, &$aSelectedIdFields, $sCallerAlias = '', $aJoinData) - { - $aTranslationTable[$this->m_sTable]['*'] = $this->m_sTableAlias; - - // Handle the various kinds of join (or first table in the list) - // - if (empty($aJoinData['righttablealias'])) - { - $sRightTableAlias = $this->m_sTableAlias; - } - else - { - $sRightTableAlias = $aJoinData['righttablealias']; - } - switch ($aJoinData['jointype']) - { - case "first": - $aFrom[$this->m_sTableAlias] = array("jointype"=>"first", "tablename"=>$this->m_sTable, "joincondition"=>""); - break; - case "inner": - case "left": - if (isset($aJoinData["on_expression"])) - { - $sJoinCond = $aJoinData["on_expression"]->Render(); - } - else - { - $sJoinCond = "`$sCallerAlias`.`{$aJoinData['leftfield']}` = `$sRightTableAlias`.`{$aJoinData['rightfield']}`"; - } - $aFrom[$this->m_sTableAlias] = array("jointype"=>$aJoinData['jointype'], "tablename"=>$this->m_sTable, "joincondition"=>"$sJoinCond"); - break; - case "inner_tree": - $sNodeLeft = "`$sCallerAlias`.`{$aJoinData['leftfield']}`"; - $sNodeRight = "`$sCallerAlias`.`{$aJoinData['rightfield']}`"; - $sRootLeft = "`$sRightTableAlias`.`{$aJoinData['rightfield_left']}`"; - $sRootRight = "`$sRightTableAlias`.`{$aJoinData['rightfield_right']}`"; - switch($aJoinData['tree_operator']) - { - case TREE_OPERATOR_BELOW: - $sJoinCond = "$sNodeLeft >= $sRootLeft AND $sNodeLeft <= $sRootRight"; - break; - - case TREE_OPERATOR_BELOW_STRICT: - $sJoinCond = "$sNodeLeft > $sRootLeft AND $sNodeLeft < $sRootRight"; - break; - - case TREE_OPERATOR_NOT_BELOW: // Complementary of 'BELOW' - $sJoinCond = "$sNodeLeft < $sRootLeft OR $sNodeLeft > $sRootRight"; - break; - - case TREE_OPERATOR_NOT_BELOW_STRICT: // Complementary of BELOW_STRICT - $sJoinCond = "$sNodeLeft <= $sRootLeft OR $sNodeLeft >= $sRootRight"; - break; - - case TREE_OPERATOR_ABOVE: - $sJoinCond = "$sNodeLeft <= $sRootLeft AND $sNodeRight >= $sRootRight"; - break; - - case TREE_OPERATOR_ABOVE_STRICT: - $sJoinCond = "$sNodeLeft < $sRootLeft AND $sNodeRight > $sRootRight"; - break; - - case TREE_OPERATOR_NOT_ABOVE: // Complementary of 'ABOVE' - $sJoinCond = "$sNodeLeft > $sRootLeft OR $sNodeRight < $sRootRight"; - break; - - case TREE_OPERATOR_NOT_ABOVE_STRICT: // Complementary of ABOVE_STRICT - $sJoinCond = "$sNodeLeft >= $sRootLeft OR $sNodeRight <= $sRootRight"; - break; - - } - $aFrom[$this->m_sTableAlias] = array("jointype"=>$aJoinData['jointype'], "tablename"=>$this->m_sTable, "joincondition"=>"$sJoinCond"); - break; - } - - // Given the alias, modify the fields and conditions - // before adding them into the current lists - // - foreach($this->m_aFields as $sAlias => $oExpression) - { - $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}`"; - } - foreach($this->m_aValues as $sFieldName=>$value) - { - $aSetValues["`{$this->m_sTableAlias}`.`$sFieldName`"] = $value; // quoted further! - } - - if (!is_null($this->m_oSelectedIdField)) - { - $aSelectedIdFields[] = $this->m_oSelectedIdField->Render(); - } - - // loop on joins, to complete the list of tables/fields/conditions - // - $aTempFrom = array(); // temporary subset of 'from' specs, to be grouped in the final query - foreach ($this->m_aJoinSelects as $aJoinData) - { - $oRightSelect = $aJoinData["select"]; - - $sJoinTableAlias = $oRightSelect->privRenderSingleTable($aTempFrom, $aFields, $aGroupBy, $aDelTables, $aSetValues, $aSelectedIdFields, $this->m_sTableAlias, $aJoinData); - } - $aFrom[$this->m_sTableAlias]['subfrom'] = $aTempFrom; - - return $this->m_sTableAlias; - } - - public function OptimizeJoins($aUsedTables, $bTopCall = true) - { - if ($bTopCall) - { - // Top call: complete the list of tables absolutely required to perform the right query - $this->CollectUsedTables($aUsedTables); - } - - $aToDiscard = array(); - foreach ($this->m_aJoinSelects as $i => $aJoinInfo) - { - $oSQLQuery = $aJoinInfo["select"]; - $sTableAlias = $oSQLQuery->GetTableAlias(); - if ($oSQLQuery->OptimizeJoins($aUsedTables, false) && !array_key_exists($sTableAlias, $aUsedTables)) - { - $aToDiscard[] = $i; - } - } - foreach ($aToDiscard as $i) - { - unset($this->m_aJoinSelects[$i]); - } - - return (count($this->m_aJoinSelects) == 0); - } - - protected function CollectUsedTables(&$aTables) - { - $this->m_oConditionExpr->CollectUsedParents($aTables); - foreach($this->m_aFields as $sFieldAlias => $oField) - { - $oField->CollectUsedParents($aTables); - } - if ($this->m_aGroupBy) - { - foreach($this->m_aGroupBy as $sAlias => $oExpression) - { - $oExpression->CollectUsedParents($aTables); - } - } - if (!is_null($this->m_oSelectedIdField)) - { - $this->m_oSelectedIdField->CollectUsedParents($aTables); - } - - foreach ($this->m_aJoinSelects as $i => $aJoinInfo) - { - $oSQLQuery = $aJoinInfo["select"]; - if ($oSQLQuery->HasRequiredTables($aTables)) - { - // There is something required in the branch, then this node is a MUST - if (isset($aJoinInfo['righttablealias'])) - { - $aTables[$aJoinInfo['righttablealias']] = true; - } - if (isset($aJoinInfo["on_expression"])) - { - $sJoinCond = $aJoinInfo["on_expression"]->CollectUsedParents($aTables); - } - } - } - - return $aTables; - } - - // Is required in the JOIN, and therefore we must ensure that the join expression will be valid - protected function HasRequiredTables(&$aTables) - { - $bResult = false; - if (array_key_exists($this->m_sTableAlias, $aTables)) - { - $bResult = true; - } - foreach ($this->m_aJoinSelects as $i => $aJoinInfo) - { - $oSQLQuery = $aJoinInfo["select"]; - if ($oSQLQuery->HasRequiredTables($aTables)) - { - // There is something required in the branch, then this node is a MUST - if (isset($aJoinInfo['righttablealias'])) - { - $aTables[$aJoinInfo['righttablealias']] = true; - } - if (isset($aJoinInfo["on_expression"])) - { - $sJoinCond = $aJoinInfo["on_expression"]->CollectUsedParents($aTables); - } - $bResult = true; - } - } - // None of the tables is in the list of required tables - return $bResult; - } } -?> \ No newline at end of file diff --git a/core/sqlunionquery.class.inc.php b/core/sqlunionquery.class.inc.php new file mode 100644 index 000000000..238a823e9 --- /dev/null +++ b/core/sqlunionquery.class.inc.php @@ -0,0 +1,166 @@ + + + +/** + * SQLUnionQuery + * build a mySQL compatible SQL query + * + * @copyright Copyright (C) 2015 Combodo SARL + * @license http://opensource.org/licenses/AGPL-3.0 + */ + + +/** + * SQLUnionQuery + * build a mySQL compatible SQL query + * + * @package iTopORM + */ + + +class SQLUnionQuery extends SQLQuery +{ + protected $aQueries; + protected $aGroupBy; + + public function __construct($aQueries, $aGroupBy) + { + parent::__construct(); + + $this->aQueries = array(); + foreach ($aQueries as $oSQLQuery) + { + $this->aQueries[] = $oSQLQuery->DeepClone(); + } + $this->aGroupBy = $aGroupBy; + } + + public function DisplayHtml() + { + $aQueriesHtml = array(); + foreach ($this->aQueries as $oSQLQuery) + { + $aQueriesHtml[] = '

'.$oSQLQuery->DisplayHtml().'

'; + } + echo implode('UNION', $aQueries); + } + + public function AddInnerJoin($oSQLQuery, $sLeftField, $sRightField, $sRightTable = '') + { + foreach ($this->aQueries as $oSubSQLQuery) + { + $oSubSQLQuery->AddInnerJoin($oSQLQuery->DeepClone(), $sLeftField, $sRightField, $sRightTable = ''); + } + } + + public function RenderDelete($aArgs = array()) + { + throw new Exception(__class__.'::'.__function__.'Not implemented !'); + } + + // Interface, build the SQL query + public function RenderUpdate($aArgs = array()) + { + throw new Exception(__class__.'::'.__function__.'Not implemented !'); + } + + // Interface, build the SQL query + public function RenderSelect($aOrderBy = array(), $aArgs = array(), $iLimitCount = 0, $iLimitStart = 0, $bGetCount = false, $bBeautifulQuery = false) + { + $this->m_bBeautifulQuery = $bBeautifulQuery; + $sLineSep = $this->m_bBeautifulQuery ? "\n" : ''; + $sIndent = $this->m_bBeautifulQuery ? " " : null; + + $aSelects = array(); + foreach ($this->aQueries as $oSQLQuery) + { + // Render SELECTS without orderby/limit/count + $aSelects[] = $oSQLQuery->RenderSelect(array(), $aArgs, 0, 0, false, $bBeautifulQuery); + } + $sSelects = '('.implode(")$sLineSep UNION$sLineSep(", $aSelects).')'; + + if ($bGetCount) + { + $sFrom = "($sLineSep$sSelects$sLineSep) as __selects__"; + $sSQL = "SELECT$sLineSep COUNT(*) AS COUNT$sLineSep FROM $sFrom$sLineSep"; + } + else + { + $aSelects = array(); + foreach ($this->aQueries as $oSQLQuery) + { + // Render SELECT without orderby/limit/count + $aSelects[] = $oSQLQuery->RenderSelect(array(), $aArgs, 0, 0, false, $bBeautifulQuery); + } + $sSelect = $this->aQueries[0]->RenderSelectClause(); + $sOrderBy = $this->aQueries[0]->RenderOrderByClause($aOrderBy); + if (!empty($sOrderBy)) + { + $sOrderBy = "ORDER BY $sOrderBy$sLineSep"; + } + if ($iLimitCount > 0) + { + $sLimit = 'LIMIT '.$iLimitStart.', '.$iLimitCount; + } + else + { + $sLimit = ''; + } + $sSQL = $sSelects.$sLineSep.$sOrderBy.' '.$sLimit; + } + return $sSQL; + } + + // Interface, build the SQL query + public function RenderGroupBy($aArgs = array(), $bBeautifulQuery = false) + { + $this->m_bBeautifulQuery = $bBeautifulQuery; + $sLineSep = $this->m_bBeautifulQuery ? "\n" : ''; + $sIndent = $this->m_bBeautifulQuery ? " " : null; + + $aSelects = array(); + foreach ($this->aQueries as $oSQLQuery) + { + // Render SELECTS without orderby/limit/count + $aSelects[] = $oSQLQuery->RenderSelect(array(), $aArgs, 0, 0, false, $bBeautifulQuery); + } + $sSelects = '('.implode(")$sLineSep UNION$sLineSep(", $aSelects).')'; + $sFrom = "($sLineSep$sSelects$sLineSep) as __selects__"; + + $aAliases = array(); + foreach ($this->aGroupBy as $sGroupAlias => $trash) + { + $aAliases[] = "`$sGroupAlias`"; + } + $sSelect = implode(', ', $aAliases); + $sGroupBy = implode(', ', $aAliases); + + $sSQL = "SELECT $sSelect,$sLineSep COUNT(*) AS _itop_count_$sLineSep FROM $sFrom$sLineSep GROUP BY $sGroupBy"; + return $sSQL; + } + + + public function OptimizeJoins($aUsedTables, $bTopCall = true) + { + foreach ($this->aQueries as $oSQLQuery) + { + $oSQLQuery->OptimizeJoins($aUsedTables); + } + } +} diff --git a/core/userrights.class.inc.php b/core/userrights.class.inc.php index bb488b438..133c721cf 100644 --- a/core/userrights.class.inc.php +++ b/core/userrights.class.inc.php @@ -1,5 +1,5 @@ oFilter = $oFilter; $this->iActionCode = $iActionCode; @@ -1125,7 +1125,7 @@ class StimulusChecker extends ActionChecker { var $sState = null; - public function __construct(DBObjectSearch $oFilter, $sState, $iStimulusCode) + public function __construct(DBSearch $oFilter, $sState, $iStimulusCode) { parent::__construct($oFilter, $iStimulusCode); $this->sState = $sState; diff --git a/core/valuesetdef.class.inc.php b/core/valuesetdef.class.inc.php index eabc10952..5e1facc68 100644 --- a/core/valuesetdef.class.inc.php +++ b/core/valuesetdef.class.inc.php @@ -1,5 +1,5 @@ m_aModifierProperties[$sPluginClass][$sProperty] = $value; } - public function AddCondition(DBObjectSearch $oFilter) + public function AddCondition(DBSearch $oFilter) { $this->m_aExtraConditions[] = $oFilter; } @@ -136,7 +136,7 @@ class ValueSetObjects extends ValueSetDefinition } foreach($this->m_aExtraConditions as $oExtraFilter) { - $oFilter->MergeWith($oExtraFilter); + $oFilter = $oFilter->Intersect($oExtraFilter); } foreach($this->m_aModifierProperties as $sPluginClass => $aProperties) { @@ -178,7 +178,7 @@ class ValueSetObjects extends ValueSetDefinition if (!$oFilter) return false; foreach($this->m_aExtraConditions as $oExtraFilter) { - $oFilter->MergeWith($oExtraFilter); + $oFilter = $oFilter->Intersect($oExtraFilter); } foreach($this->m_aModifierProperties as $sPluginClass => $aProperties) { diff --git a/pages/UI.php b/pages/UI.php index 1fe8d783e..4983a5110 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -20,7 +20,7 @@ /** * Main page of iTop * - * @copyright Copyright (C) 2010-2012 Combodo SARL + * @copyright Copyright (C) 2010-2015 Combodo SARL * @license http://opensource.org/licenses/AGPL-3.0 */ @@ -144,7 +144,7 @@ function DisplayDetails($oP, $sClass, $oObj, $id) /** * Displays the result of a search request * @param $oP WebPage Web page for the output - * @param $oFilter DBObjectSearch The search of objects to display + * @param $oFilter DBSearch The search of objects to display * @param $bSearchForm boolean Whether or not to display the search form at the top the page * @param $sBaseClass string The base class for the search (can be different from the actual class of the results) * @param $sFormat string The format to use for the output: csv or html @@ -182,7 +182,7 @@ function DisplaySearchSet($oP, $oFilter, $bSearchForm = true, $sBaseClass = '', * Displays a form (checkboxes) to select the objects for which to apply a given action * Only the objects for which the action is valid can be checked. By default all valid objects are checked * @param $oP WebPage The page for output - * @param $oFilter DBObjectSearch The filter that defines the list of objects + * @param $oFilter DBSearch The filter that defines the list of objects * @param $sNextOperation string The next operation (code) to be executed when the form is submitted * @param $oChecker ActionChecker The helper class/instance used to check for which object the action is valid * @return none diff --git a/pages/audit.php b/pages/audit.php index 31d414d10..c9d0cbd13 100644 --- a/pages/audit.php +++ b/pages/audit.php @@ -1,5 +1,5 @@ GetClass(); $aContextParams = $oAppContext->GetNames(); @@ -93,8 +93,7 @@ function GetRuleResultFilter($iRuleId, $oDefinitionFilter, $oAppContext) if ($oRule->Get('valid_flag') == 'false') { // The query returns directly the invalid elements - $oFilter = $oRuleFilter; - $oFilter->MergeWith($oDefinitionFilter); + $oFilter = $oRuleFilter->Intersect($oDefinitionFilter); } else { diff --git a/setup/runtimeenv.class.inc.php b/setup/runtimeenv.class.inc.php index 465197601..bfdd1362c 100644 --- a/setup/runtimeenv.class.inc.php +++ b/setup/runtimeenv.class.inc.php @@ -1,5 +1,5 @@ log_info("$sClass::GetPredefinedObjects() returned " . count($aPredefinedObjects) . " elements."); - - // Create/Delete/Update objects of this class, - // according to the given constant values - // - $aDBIds = array(); - $oAll = new DBObjectSet(new DBObjectSearch($sClass)); - while ($oObj = $oAll->Fetch()) - { - if (array_key_exists($oObj->GetKey(), $aPredefinedObjects)) - { - $aObjValues = $aPredefinedObjects[$oObj->GetKey()]; - foreach ($aObjValues as $sAttCode => $value) - { - $oObj->Set($sAttCode, $value); - } - $oObj->DBUpdate(); - $aDBIds[$oObj->GetKey()] = true; - } - else - { - $oObj->DBDelete(); - } - } - foreach ($aPredefinedObjects as $iRefId => $aObjValues) - { - if (! array_key_exists($iRefId, $aDBIds)) - { - $oNewObj = MetaModel::NewObject($sClass); - $oNewObj->SetKey($iRefId); - foreach ($aObjValues as $sAttCode => $value) - { - $oNewObj->Set($sAttCode, $value); - } - $oNewObj->DBInsert(); - } - } - } - } + } + + public function UpdatePredefinedObjects() + { + // Constant classes (e.g. User profiles) + // + foreach (MetaModel::GetClasses() as $sClass) + { + $aPredefinedObjects = call_user_func(array( + $sClass, + 'GetPredefinedObjects' + )); + if ($aPredefinedObjects != null) + { + $this->log_info("$sClass::GetPredefinedObjects() returned " . count($aPredefinedObjects) . " elements."); + + // Create/Delete/Update objects of this class, + // according to the given constant values + // + $aDBIds = array(); + $oAll = new DBObjectSet(new DBObjectSearch($sClass)); + while ($oObj = $oAll->Fetch()) + { + if (array_key_exists($oObj->GetKey(), $aPredefinedObjects)) + { + $aObjValues = $aPredefinedObjects[$oObj->GetKey()]; + foreach ($aObjValues as $sAttCode => $value) + { + $oObj->Set($sAttCode, $value); + } + $oObj->DBUpdate(); + $aDBIds[$oObj->GetKey()] = true; + } + else + { + $oObj->DBDelete(); + } + } + foreach ($aPredefinedObjects as $iRefId => $aObjValues) + { + if (! array_key_exists($iRefId, $aDBIds)) + { + $oNewObj = MetaModel::NewObject($sClass); + $oNewObj->SetKey($iRefId); + foreach ($aObjValues as $sAttCode => $value) + { + $oNewObj->Set($sAttCode, $value); + } + $oNewObj->DBInsert(); + } + } + } + } } public function RecordInstallation(Config $oConfig, $sDataModelVersion, $aSelectedModules, $sModulesRelativePath, $sShortComment = null) diff --git a/test/benchmark.php b/test/benchmark.php index ae987983e..d4b802330 100644 --- a/test/benchmark.php +++ b/test/benchmark.php @@ -866,6 +866,6 @@ catch(ZZCoreException $e) $oP->error("Error: '".$e->getHtmlDesc()."'"); } $oKPI->ComputeAndReport('Total execution'); -//MetaModel::RecordQueryTrace(); +//DBSearch::RecordQueryTrace(); $oP->output(); ?> diff --git a/test/replay_query_log.php b/test/replay_query_log.php index bdef4fb73..4b2ba0a03 100644 --- a/test/replay_query_log.php +++ b/test/replay_query_log.php @@ -1,5 +1,5 @@ sSql = MetaModel::MakeSelectQuery($this->oFilter, $this->aOrderBy, $this->aArgs, $this->aAttToLoad, $this->aExtendedDataSpec, $this->iLimitCount, $this->iLimitStart, $this->bGetCount); + $this->sSql = $this->oFilter->MakeSelectQuery($this->aOrderBy, $this->aArgs, $this->aAttToLoad, $this->aExtendedDataSpec, $this->iLimitCount, $this->iLimitStart, $this->bGetCount); } } catch(Exception $e) @@ -130,7 +130,7 @@ class QueryLogEntry { for($i = 0 ; $i < $iRepeat ; $i++) { - $this->sSql = MetaModel::MakeGroupByQuery($this->oFilter, $this->aArgs, $this->aGroupByExpr); + $this->sSql = $this->oFilter->MakeGroupByQuery($this->aArgs, $this->aGroupByExpr); } } catch(Exception $e) diff --git a/test/test.class.inc.php b/test/test.class.inc.php index 9db56ad6c..1a7383c06 100644 --- a/test/test.class.inc.php +++ b/test/test.class.inc.php @@ -1,5 +1,5 @@ __DescribeHTML()."' - Found ".$oObjSet->Count()." items.
\n"; + echo $oMyFilter->ToOQL()."' - Found ".$oObjSet->Count()." items.
\n"; self::show_list($oObjSet); } diff --git a/test/test.php b/test/test.php index b95037514..b062d7ec3 100644 --- a/test/test.php +++ b/test/test.php @@ -90,6 +90,7 @@ function DisplayEvents($aEvents, $sTitle) // Main /////////////////////////////////////////////////////////////////////////////// +date_default_timezone_set('Europe/Paris'); require_once('../approot.inc.php'); require_once(APPROOT.'/application/utils.inc.php'); @@ -98,6 +99,7 @@ require_once('./testlist.inc.php'); require_once(APPROOT.'/core/cmdbobject.class.inc.php'); + $sTodo = utils::ReadParam("todo", ""); if ($sTodo == '') { diff --git a/test/testlist.inc.php b/test/testlist.inc.php index 29e62d238..7ad797ddd 100644 --- a/test/testlist.inc.php +++ b/test/testlist.inc.php @@ -1,5 +1,5 @@ new FieldExpression('column1', 'myTableAlias'), 'column2'=>new FieldExpression('column2', 'myTableAlias')), @@ -56,7 +56,7 @@ class TestSQLQuery extends TestScenarioOnDB ); $oQuery->AddCondition(Expression::FromOQL('DATE(NOW() - 1200 * 2) > \'2008-07-31\'')); - $oSubQuery1 = new SQLQuery( + $oSubQuery1 = new SQLObjectQuery( $sTable = 'myTable1', $sTableAlias = 'myTable1Alias', $aFields = array('column1_1'=>new FieldExpression('column1', 'myTableAlias'), 'column1_2'=>new FieldExpression('column1', 'myTableAlias')), @@ -65,7 +65,7 @@ class TestSQLQuery extends TestScenarioOnDB $aValues = array() ); - $oSubQuery2 = new SQLQuery( + $oSubQuery2 = new SQLObjectQuery( $sTable = 'myTable2', $sTableAlias = 'myTable2Alias', $aFields = array('column2_1'=>new FieldExpression('column2', 'myTableAlias'), 'column2_2'=>new FieldExpression('column2', 'myTableAlias')), @@ -231,6 +231,14 @@ class TestOQLParser extends TestFunction 'SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id NOT BELOW B.id WHERE A.col1 = 2 AND B.id = 3' => true, 'SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id NOT BELOW STRICT B.id WHERE A.col1 = 2 AND B.id = 3' => true, 'SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id = B.id WHERE A.col1 BELOW 2 AND B.id = 3' => false, + + // Unions + // + 'SELECT A UNION SELECT B' => true, + 'SELECT A WHERE A.b = "sdf" UNION SELECT B WHERE B.a = "sfde"' => true, + 'SELECT A UNION SELECT B UNION SELECT C' => true, + 'SELECT A UNION SELECT B UNION SELECT C UNION SELECT D' => true, + 'SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id NOT BELOW B.id WHERE A.col1 = 2 AND B.id = 3 UNION SELECT Device JOIN Site ON Device.site = Site.id JOIN Country ON Site.location = Country.id' => true, ); $iErrors = 0; @@ -622,9 +630,9 @@ class TestMyBizModel extends TestBizModel $oMyChange->Set("userinfo", "test_setattribute / Made by robot #".rand(1,100)); $iChangeId = $oMyChange->DBInsert(); - //MetaModel::StartDebugQuery(); + //DBSearch::StartDebugQuery(); $team->DBUpdateTracked($oMyChange); - //MetaModel::StopDebugQuery(); + //DBSearch::StopDebugQuery(); echo "

Check the modified team

"; $oTeam = MetaModel::GetObject("cmdbTeam", "2"); @@ -954,7 +962,7 @@ class TestQueriesOnFarm extends MyFarm try { //$oOql = new OqlInterpreter($sQuery); - //$oTrash = $oOql->ParseObjectQuery(); + //$oTrash = $oOql->ParseQuery(); //self::DumpVariable($oTrash, true); $oMyFilter = DBObjectSearch::FromOQL($sQuery); } @@ -991,17 +999,17 @@ class TestQueriesOnFarm extends MyFarm //echo "

first pass

\n"; //self::DumpVariable($oMyFilter, true); - $sQuery1 = MetaModel::MakeSelectQuery($oMyFilter); + $sQuery1 = $oMyFilter->MakeSelectQuery(); //echo "

second pass

\n"; //self::DumpVariable($oMyFilter, true); - //$sQuery1 = MetaModel::MakeSelectQuery($oMyFilter); + //$sQuery1 = $oMyFilter->MakeSelectQuery(); $sSerialize = $oMyFilter->serialize(); echo "

Serialized:$sSerialize

\n"; $oFilter2 = DBObjectSearch::unserialize($sSerialize); try { - $sQuery2 = MetaModel::MakeSelectQuery($oFilter2); + $sQuery2 = $oMyFilter2->MakeSelectQuery(); } catch (Exception $e) { @@ -1282,7 +1290,7 @@ class TestItopEfficiency extends TestBizModel $fStart = MyHelpers::getmicrotime(); for($i=0 ; $i < COUNT_BENCHMARK ; $i++) { - $sSQL = MetaModel::MakeSelectQuery($oFilter); + $sSQL = $oFilter->MakeSelectQuery(); } $fDuration = MyHelpers::getmicrotime() - $fStart; $fBuildDuration = $fDuration / COUNT_BENCHMARK; @@ -1399,7 +1407,7 @@ class TestQueries extends TestBizModel $fParsingDuration = MyHelpers::getmicrotime() - $fStart; $fStart = MyHelpers::getmicrotime(); - $sSQL = MetaModel::MakeSelectQuery($oFilter); + $sSQL = $oFilter->MakeSelectQuery(); $fBuildDuration = MyHelpers::getmicrotime() - $fStart; $fStart = MyHelpers::getmicrotime(); @@ -1570,7 +1578,7 @@ $oSearch->AddCondition_PointingTo($oOrgSearch, "org_id"); print_r($oSearch); echo ""; - $sSQL = MetaModel::MakeSelectQuery($oSearch); + $sSQL = $oSearch->MakeSelectQuery(); $res = CMDBSource::Query($sSQL); foreach (CMDBSource::ExplainQuery($sSQL) as $aRow) {