/** * Classes defined for lexical analyze (see oql-parser.y) * * @copyright Copyright (C) 2010-2012 Combodo SARL * @license http://opensource.org/licenses/AGPL-3.0 */ // Position a string within an OQL query // This is a must if we want to be able to pinpoint an error at any stage of the query interpretation // In particular, the normalization phase requires this class OqlName { protected $m_sValue; protected $m_iPos; public function __construct($sValue, $iPos) { $this->m_iPos = $iPos; $this->m_sValue = $sValue; } public function GetValue() { return $this->m_sValue; } public function GetPos() { return $this->m_iPos; } public function __toString() { return $this->m_sValue; } } /** * * Store hexadecimal values as strings so that we can support 64-bit values * */ class OqlHexValue { protected $m_sValue; public function __construct($sValue) { $this->m_sValue = $sValue; } public function __toString() { return $this->m_sValue; } } class OqlJoinSpec { protected $m_oClass; protected $m_oClassAlias; protected $m_oLeftField; protected $m_oRightField; protected $m_sOperator; protected $m_oNextJoinspec; public function __construct($oClass, $oClassAlias, BinaryExpression $oExpression) { $this->m_oClass = $oClass; $this->m_oClassAlias = $oClassAlias; $this->m_oLeftField = $oExpression->GetLeftExpr(); $this->m_oRightField = $oExpression->GetRightExpr(); $this->m_oRightField = $oExpression->GetRightExpr(); $this->m_sOperator = $oExpression->GetOperator(); } public function GetClass() { return $this->m_oClass->GetValue(); } public function GetClassAlias() { return $this->m_oClassAlias->GetValue(); } public function GetClassDetails() { return $this->m_oClass; } public function GetClassAliasDetails() { return $this->m_oClassAlias; } public function GetLeftField() { return $this->m_oLeftField; } public function GetRightField() { return $this->m_oRightField; } public function GetOperator() { return $this->m_sOperator; } } interface CheckableExpression { /** * 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 * @param array $aAliases Aliases to class names (for the current query) * @param string $sSourceQuery For the reporting * @throws OqlNormalizeException */ public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery); } class BinaryOqlExpression extends BinaryExpression implements CheckableExpression { public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery) { $this->m_oLeftExpr->Check($oModelReflection, $aAliases, $sSourceQuery); $this->m_oRightExpr->Check($oModelReflection, $aAliases, $sSourceQuery); } } class ScalarOqlExpression extends ScalarExpression implements CheckableExpression { public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery) { // a scalar is always fine } } class FieldOqlExpression extends FieldExpression implements CheckableExpression { protected $m_oParent; protected $m_oName; public function __construct($oName, $oParent = null) { if (is_null($oParent)) { $oParent = new OqlName('', 0); } $this->m_oParent = $oParent; $this->m_oName = $oName; parent::__construct($oName->GetValue(), $oParent->GetValue()); } public function GetParentDetails() { return $this->m_oParent; } public function GetNameDetails() { return $this->m_oName; } public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery) { $sClassAlias = $this->GetParent(); $sFltCode = $this->GetName(); if (empty($sClassAlias)) { // Try to find an alias // Build an array of field => array of aliases $aFieldClasses = array(); foreach($aAliases as $sAlias => $sReal) { foreach($oModelReflection->GetFiltersList($sReal) as $sAnFltCode) { $aFieldClasses[$sAnFltCode][] = $sAlias; } } if (!array_key_exists($sFltCode, $aFieldClasses)) { throw new OqlNormalizeException('Unknown filter code', $sSourceQuery, $this->GetNameDetails(), array_keys($aFieldClasses)); } if (count($aFieldClasses[$sFltCode]) > 1) { throw new OqlNormalizeException('Ambiguous filter code', $sSourceQuery, $this->GetNameDetails()); } $sClassAlias = $aFieldClasses[$sFltCode][0]; } else { if (!array_key_exists($sClassAlias, $aAliases)) { throw new OqlNormalizeException('Unknown class [alias]', $sSourceQuery, $this->GetParentDetails(), array_keys($aAliases)); } $sClass = $aAliases[$sClassAlias]; if (!$oModelReflection->IsValidFilterCode($sClass, $sFltCode)) { throw new OqlNormalizeException('Unknown filter code', $sSourceQuery, $this->GetNameDetails(), $oModelReflection->GetFiltersList($sClass)); } } } } class VariableOqlExpression extends VariableExpression implements CheckableExpression { public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery) { // a scalar is always fine } } class ListOqlExpression extends ListExpression implements CheckableExpression { public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery) { foreach ($this->GetItems() as $oItemExpression) { $oItemExpression->Check($oModelReflection, $aAliases, $sSourceQuery); } } } class FunctionOqlExpression extends FunctionExpression implements CheckableExpression { public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery) { foreach ($this->GetArgs() as $oArgExpression) { $oArgExpression->Check($oModelReflection, $aAliases, $sSourceQuery); } } } class IntervalOqlExpression extends IntervalExpression implements CheckableExpression { public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery) { // an interval is always fine (made of a scalar and unit) } } abstract class OqlQuery { protected $m_aJoins; // array of OqlJoinSpec protected $m_oCondition; // condition tree (expressions) public function __construct($oCondition = null, $aJoins = null) { $this->m_aJoins = $aJoins; $this->m_oCondition = $oCondition; } public function GetJoins() { return $this->m_aJoins; } public function GetCondition() { return $this->m_oCondition; } } class OqlObjectQuery extends OqlQuery { protected $m_aSelect; // array of selected classes protected $m_oClass; protected $m_oClassAlias; 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); } public function GetSelectedClasses() { return $this->m_aSelect; } public function GetClass() { return $this->m_oClass->GetValue(); } public function GetClassAlias() { return $this->m_oClassAlias->GetValue(); } public function GetClassDetails() { return $this->m_oClass; } public function GetClassAliasDetails() { return $this->m_oClassAlias; } /** * Recursively 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) { $sClass = $this->GetClass(); $sClassAlias = $this->GetClassAlias(); if (!$oModelReflection->IsValidClass($sClass)) { throw new UnknownClassOqlException($sSourceQuery, $this->GetClassDetails(), $oModelReflection->GetClasses()); } $aAliases = array($sClassAlias => $sClass); $aJoinSpecs = $this->GetJoins(); if (is_array($aJoinSpecs)) { foreach ($aJoinSpecs as $oJoinSpec) { $sJoinClass = $oJoinSpec->GetClass(); $sJoinClassAlias = $oJoinSpec->GetClassAlias(); if (!$oModelReflection->IsValidClass($sJoinClass)) { throw new UnknownClassOqlException($sSourceQuery, $oJoinSpec->GetClassDetails(), $oModelReflection->GetClasses()); } if (array_key_exists($sJoinClassAlias, $aAliases)) { if ($sJoinClassAlias != $sJoinClass) { throw new OqlNormalizeException('Duplicate class alias', $sSourceQuery, $oJoinSpec->GetClassAliasDetails()); } else { throw new OqlNormalizeException('Duplicate class name', $sSourceQuery, $oJoinSpec->GetClassDetails()); } } // Assumption: ext key on the left only !!! // normalization should take care of this $oLeftField = $oJoinSpec->GetLeftField(); $sFromClass = $oLeftField->GetParent(); $sExtKeyAttCode = $oLeftField->GetName(); $oRightField = $oJoinSpec->GetRightField(); $sToClass = $oRightField->GetParent(); $sPKeyDescriptor = $oRightField->GetName(); if ($sPKeyDescriptor != 'id') { throw new OqlNormalizeException('Wrong format for Join clause (right hand), expecting an id', $sSourceQuery, $oRightField->GetNameDetails(), array('id')); } $aAliases[$sJoinClassAlias] = $sJoinClass; if (!array_key_exists($sFromClass, $aAliases)) { throw new OqlNormalizeException('Unknown class in join condition (left expression)', $sSourceQuery, $oLeftField->GetParentDetails(), array_keys($aAliases)); } if (!array_key_exists($sToClass, $aAliases)) { throw new OqlNormalizeException('Unknown class in join condition (right expression)', $sSourceQuery, $oRightField->GetParentDetails(), array_keys($aAliases)); } $aExtKeys = $oModelReflection->ListAttributes($aAliases[$sFromClass], 'AttributeExternalKey'); if (!array_key_exists($sExtKeyAttCode, $aExtKeys)) { throw new OqlNormalizeException('Unknown external key in join condition (left expression)', $sSourceQuery, $oLeftField->GetNameDetails(), array_keys($aExtKeys)); } if ($sFromClass == $sJoinClassAlias) { $sTargetClass = $oModelReflection->GetAttributeProperty($aAliases[$sFromClass], $sExtKeyAttCode, 'targetclass'); if(!$oModelReflection->IsSameFamilyBranch($aAliases[$sToClass], $sTargetClass)) { throw new OqlNormalizeException("The joined class ($aAliases[$sFromClass]) is not compatible with the external key, which is pointing to $sTargetClass", $sSourceQuery, $oLeftField->GetNameDetails()); } } else { $sOperator = $oJoinSpec->GetOperator(); switch($sOperator) { case '=': $iOperatorCode = TREE_OPERATOR_EQUALS; break; case 'BELOW': $iOperatorCode = TREE_OPERATOR_BELOW; break; case 'BELOW_STRICT': $iOperatorCode = TREE_OPERATOR_BELOW_STRICT; break; case 'NOT_BELOW': $iOperatorCode = TREE_OPERATOR_NOT_BELOW; break; case 'NOT_BELOW_STRICT': $iOperatorCode = TREE_OPERATOR_NOT_BELOW_STRICT; break; case 'ABOVE': $iOperatorCode = TREE_OPERATOR_ABOVE; break; case 'ABOVE_STRICT': $iOperatorCode = TREE_OPERATOR_ABOVE_STRICT; break; case 'NOT_ABOVE': $iOperatorCode = TREE_OPERATOR_NOT_ABOVE; break; case 'NOT_ABOVE_STRICT': $iOperatorCode = TREE_OPERATOR_NOT_ABOVE_STRICT; break; } $sTargetClass = $oModelReflection->GetAttributeProperty($aAliases[$sFromClass], $sExtKeyAttCode, 'targetclass'); if(!$oModelReflection->IsSameFamilyBranch($aAliases[$sToClass], $sTargetClass)) { throw new OqlNormalizeException("The joined class ($aAliases[$sToClass]) is not compatible with the external key, which is pointing to $sTargetClass", $sSourceQuery, $oLeftField->GetNameDetails()); } $aAttList = $oModelReflection->ListAttributes($aAliases[$sFromClass]); $sAttType = $aAttList[$sExtKeyAttCode]; if(($iOperatorCode != TREE_OPERATOR_EQUALS) && !is_subclass_of($sAttType, 'AttributeHierarchicalKey') && ($sAttType != 'AttributeHierarchicalKey')) { throw new OqlNormalizeException("The specified tree operator $sOperator is not applicable to the key", $sSourceQuery, $oLeftField->GetNameDetails()); } } } } // Check the select information // $aSelected = array(); foreach ($this->GetSelectedClasses() as $oClassDetails) { $sClassToSelect = $oClassDetails->GetValue(); if (!array_key_exists($sClassToSelect, $aAliases)) { throw new OqlNormalizeException('Unknown class [alias]', $sSourceQuery, $oClassDetails, array_keys($aAliases)); } $aSelected[$sClassToSelect] = $aAliases[$sClassToSelect]; } // Check the condition tree // if ($this->m_oCondition instanceof Expression) { $this->m_oCondition->Check($oModelReflection, $aAliases, $sSourceQuery); } } } ?>