/** * 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'); $aObjKeys = $oModelReflection->ListAttributes($aAliases[$sFromClass], 'AttributeObjectKey'); $aAllKeys = array_merge($aExtKeys, $aObjKeys); if (!array_key_exists($sExtKeyAttCode, $aAllKeys)) { throw new OqlNormalizeException('Unknown key in join condition (left expression)', $sSourceQuery, $oLeftField->GetNameDetails(), array_keys($aAllKeys)); } if ($sFromClass == $sJoinClassAlias) { if (array_key_exists($sExtKeyAttCode, $aExtKeys)) { $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; } if (array_key_exists($sExtKeyAttCode, $aExtKeys)) { $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 // 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)); } } // Check the condition tree // if ($this->m_oCondition instanceof Expression) { $this->m_oCondition->Check($oModelReflection, $aAliases, $sSourceQuery); } }
/** * Note: assumes that class A and B have a common ancestor */ protected static function LowestCommonAncestor(ModelReflection $oModelReflection, $sClassA, $sClassB) { if ($sClassA == $sClassB) { $sRet = $sClassA; } elseif (in_array($sClassA, $oModelReflection->EnumChildClasses($sClassB))) { $sRet = $sClassB; } elseif (in_array($sClassB, $oModelReflection->EnumChildClasses($sClassA))) { $sRet = $sClassA; } else { // Recurse $sRet = self::LowestCommonAncestor($oModelReflection, $sClassA, $oModelReflection->GetParentClass($sClassB)); } return $sRet; }