oqlquery.class.inc.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. <?php
  2. // Copyright (C) 2010-2015 Combodo SARL
  3. //
  4. // This file is part of iTop.
  5. //
  6. // iTop is free software; you can redistribute it and/or modify
  7. // it under the terms of the GNU Affero General Public License as published by
  8. // the Free Software Foundation, either version 3 of the License, or
  9. // (at your option) any later version.
  10. //
  11. // iTop is distributed in the hope that it will be useful,
  12. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. // GNU Affero General Public License for more details.
  15. //
  16. // You should have received a copy of the GNU Affero General Public License
  17. // along with iTop. If not, see <http://www.gnu.org/licenses/>
  18. /**
  19. * Classes defined for lexical analyze (see oql-parser.y)
  20. *
  21. * @copyright Copyright (C) 2010-2012 Combodo SARL
  22. * @license http://opensource.org/licenses/AGPL-3.0
  23. */
  24. // Position a string within an OQL query
  25. // This is a must if we want to be able to pinpoint an error at any stage of the query interpretation
  26. // In particular, the normalization phase requires this
  27. class OqlName
  28. {
  29. protected $m_sValue;
  30. protected $m_iPos;
  31. public function __construct($sValue, $iPos)
  32. {
  33. $this->m_iPos = $iPos;
  34. $this->m_sValue = $sValue;
  35. }
  36. public function GetValue()
  37. {
  38. return $this->m_sValue;
  39. }
  40. public function GetPos()
  41. {
  42. return $this->m_iPos;
  43. }
  44. public function __toString()
  45. {
  46. return $this->m_sValue;
  47. }
  48. }
  49. /**
  50. *
  51. * Store hexadecimal values as strings so that we can support 64-bit values
  52. *
  53. */
  54. class OqlHexValue
  55. {
  56. protected $m_sValue;
  57. public function __construct($sValue)
  58. {
  59. $this->m_sValue = $sValue;
  60. }
  61. public function __toString()
  62. {
  63. return $this->m_sValue;
  64. }
  65. }
  66. class OqlJoinSpec
  67. {
  68. protected $m_oClass;
  69. protected $m_oClassAlias;
  70. protected $m_oLeftField;
  71. protected $m_oRightField;
  72. protected $m_sOperator;
  73. protected $m_oNextJoinspec;
  74. public function __construct($oClass, $oClassAlias, BinaryExpression $oExpression)
  75. {
  76. $this->m_oClass = $oClass;
  77. $this->m_oClassAlias = $oClassAlias;
  78. $this->m_oLeftField = $oExpression->GetLeftExpr();
  79. $this->m_oRightField = $oExpression->GetRightExpr();
  80. $this->m_oRightField = $oExpression->GetRightExpr();
  81. $this->m_sOperator = $oExpression->GetOperator();
  82. }
  83. public function GetClass()
  84. {
  85. return $this->m_oClass->GetValue();
  86. }
  87. public function GetClassAlias()
  88. {
  89. return $this->m_oClassAlias->GetValue();
  90. }
  91. public function GetClassDetails()
  92. {
  93. return $this->m_oClass;
  94. }
  95. public function GetClassAliasDetails()
  96. {
  97. return $this->m_oClassAlias;
  98. }
  99. public function GetLeftField()
  100. {
  101. return $this->m_oLeftField;
  102. }
  103. public function GetRightField()
  104. {
  105. return $this->m_oRightField;
  106. }
  107. public function GetOperator()
  108. {
  109. return $this->m_sOperator;
  110. }
  111. }
  112. interface CheckableExpression
  113. {
  114. /**
  115. * Check the validity of the expression with regard to the data model
  116. * and the query in which it is used
  117. *
  118. * @param ModelReflection $oModelReflection MetaModel to consider
  119. * @param array $aAliases Aliases to class names (for the current query)
  120. * @param string $sSourceQuery For the reporting
  121. * @throws OqlNormalizeException
  122. */
  123. public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery);
  124. }
  125. class BinaryOqlExpression extends BinaryExpression implements CheckableExpression
  126. {
  127. public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery)
  128. {
  129. $this->m_oLeftExpr->Check($oModelReflection, $aAliases, $sSourceQuery);
  130. $this->m_oRightExpr->Check($oModelReflection, $aAliases, $sSourceQuery);
  131. }
  132. }
  133. class ScalarOqlExpression extends ScalarExpression implements CheckableExpression
  134. {
  135. public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery)
  136. {
  137. // a scalar is always fine
  138. }
  139. }
  140. class FieldOqlExpression extends FieldExpression implements CheckableExpression
  141. {
  142. protected $m_oParent;
  143. protected $m_oName;
  144. public function __construct($oName, $oParent = null)
  145. {
  146. if (is_null($oParent))
  147. {
  148. $oParent = new OqlName('', 0);
  149. }
  150. $this->m_oParent = $oParent;
  151. $this->m_oName = $oName;
  152. parent::__construct($oName->GetValue(), $oParent->GetValue());
  153. }
  154. public function GetParentDetails()
  155. {
  156. return $this->m_oParent;
  157. }
  158. public function GetNameDetails()
  159. {
  160. return $this->m_oName;
  161. }
  162. public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery)
  163. {
  164. $sClassAlias = $this->GetParent();
  165. $sFltCode = $this->GetName();
  166. if (empty($sClassAlias))
  167. {
  168. // Try to find an alias
  169. // Build an array of field => array of aliases
  170. $aFieldClasses = array();
  171. foreach($aAliases as $sAlias => $sReal)
  172. {
  173. foreach($oModelReflection->GetFiltersList($sReal) as $sAnFltCode)
  174. {
  175. $aFieldClasses[$sAnFltCode][] = $sAlias;
  176. }
  177. }
  178. if (!array_key_exists($sFltCode, $aFieldClasses))
  179. {
  180. throw new OqlNormalizeException('Unknown filter code', $sSourceQuery, $this->GetNameDetails(), array_keys($aFieldClasses));
  181. }
  182. if (count($aFieldClasses[$sFltCode]) > 1)
  183. {
  184. throw new OqlNormalizeException('Ambiguous filter code', $sSourceQuery, $this->GetNameDetails());
  185. }
  186. $sClassAlias = $aFieldClasses[$sFltCode][0];
  187. }
  188. else
  189. {
  190. if (!array_key_exists($sClassAlias, $aAliases))
  191. {
  192. throw new OqlNormalizeException('Unknown class [alias]', $sSourceQuery, $this->GetParentDetails(), array_keys($aAliases));
  193. }
  194. $sClass = $aAliases[$sClassAlias];
  195. if (!$oModelReflection->IsValidFilterCode($sClass, $sFltCode))
  196. {
  197. throw new OqlNormalizeException('Unknown filter code', $sSourceQuery, $this->GetNameDetails(), $oModelReflection->GetFiltersList($sClass));
  198. }
  199. }
  200. }
  201. }
  202. class VariableOqlExpression extends VariableExpression implements CheckableExpression
  203. {
  204. public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery)
  205. {
  206. // a scalar is always fine
  207. }
  208. }
  209. class ListOqlExpression extends ListExpression implements CheckableExpression
  210. {
  211. public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery)
  212. {
  213. foreach ($this->GetItems() as $oItemExpression)
  214. {
  215. $oItemExpression->Check($oModelReflection, $aAliases, $sSourceQuery);
  216. }
  217. }
  218. }
  219. class FunctionOqlExpression extends FunctionExpression implements CheckableExpression
  220. {
  221. public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery)
  222. {
  223. foreach ($this->GetArgs() as $oArgExpression)
  224. {
  225. $oArgExpression->Check($oModelReflection, $aAliases, $sSourceQuery);
  226. }
  227. }
  228. }
  229. class IntervalOqlExpression extends IntervalExpression implements CheckableExpression
  230. {
  231. public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery)
  232. {
  233. // an interval is always fine (made of a scalar and unit)
  234. }
  235. }
  236. abstract class OqlQuery
  237. {
  238. protected $m_aJoins; // array of OqlJoinSpec
  239. protected $m_oCondition; // condition tree (expressions)
  240. public function __construct($oCondition = null, $aJoins = null)
  241. {
  242. $this->m_aJoins = $aJoins;
  243. $this->m_oCondition = $oCondition;
  244. }
  245. public function GetJoins()
  246. {
  247. return $this->m_aJoins;
  248. }
  249. public function GetCondition()
  250. {
  251. return $this->m_oCondition;
  252. }
  253. }
  254. class OqlObjectQuery extends OqlQuery
  255. {
  256. protected $m_aSelect; // array of selected classes
  257. protected $m_oClass;
  258. protected $m_oClassAlias;
  259. public function __construct($oClass, $oClassAlias, $oCondition = null, $aJoins = null, $aSelect = null)
  260. {
  261. $this->m_aSelect = $aSelect;
  262. $this->m_oClass = $oClass;
  263. $this->m_oClassAlias = $oClassAlias;
  264. parent::__construct($oCondition, $aJoins);
  265. }
  266. public function GetSelectedClasses()
  267. {
  268. return $this->m_aSelect;
  269. }
  270. public function GetClass()
  271. {
  272. return $this->m_oClass->GetValue();
  273. }
  274. public function GetClassAlias()
  275. {
  276. return $this->m_oClassAlias->GetValue();
  277. }
  278. public function GetClassDetails()
  279. {
  280. return $this->m_oClass;
  281. }
  282. public function GetClassAliasDetails()
  283. {
  284. return $this->m_oClassAlias;
  285. }
  286. /**
  287. * Recursively check the validity of the expression with regard to the data model
  288. * and the query in which it is used
  289. *
  290. * @param ModelReflection $oModelReflection MetaModel to consider
  291. * @throws OqlNormalizeException
  292. */
  293. public function Check(ModelReflection $oModelReflection, $sSourceQuery)
  294. {
  295. $sClass = $this->GetClass();
  296. $sClassAlias = $this->GetClassAlias();
  297. if (!$oModelReflection->IsValidClass($sClass))
  298. {
  299. throw new UnknownClassOqlException($sSourceQuery, $this->GetClassDetails(), $oModelReflection->GetClasses());
  300. }
  301. $aAliases = array($sClassAlias => $sClass);
  302. $aJoinSpecs = $this->GetJoins();
  303. if (is_array($aJoinSpecs))
  304. {
  305. foreach ($aJoinSpecs as $oJoinSpec)
  306. {
  307. $sJoinClass = $oJoinSpec->GetClass();
  308. $sJoinClassAlias = $oJoinSpec->GetClassAlias();
  309. if (!$oModelReflection->IsValidClass($sJoinClass))
  310. {
  311. throw new UnknownClassOqlException($sSourceQuery, $oJoinSpec->GetClassDetails(), $oModelReflection->GetClasses());
  312. }
  313. if (array_key_exists($sJoinClassAlias, $aAliases))
  314. {
  315. if ($sJoinClassAlias != $sJoinClass)
  316. {
  317. throw new OqlNormalizeException('Duplicate class alias', $sSourceQuery, $oJoinSpec->GetClassAliasDetails());
  318. }
  319. else
  320. {
  321. throw new OqlNormalizeException('Duplicate class name', $sSourceQuery, $oJoinSpec->GetClassDetails());
  322. }
  323. }
  324. // Assumption: ext key on the left only !!!
  325. // normalization should take care of this
  326. $oLeftField = $oJoinSpec->GetLeftField();
  327. $sFromClass = $oLeftField->GetParent();
  328. $sExtKeyAttCode = $oLeftField->GetName();
  329. $oRightField = $oJoinSpec->GetRightField();
  330. $sToClass = $oRightField->GetParent();
  331. $sPKeyDescriptor = $oRightField->GetName();
  332. if ($sPKeyDescriptor != 'id')
  333. {
  334. throw new OqlNormalizeException('Wrong format for Join clause (right hand), expecting an id', $sSourceQuery, $oRightField->GetNameDetails(), array('id'));
  335. }
  336. $aAliases[$sJoinClassAlias] = $sJoinClass;
  337. if (!array_key_exists($sFromClass, $aAliases))
  338. {
  339. throw new OqlNormalizeException('Unknown class in join condition (left expression)', $sSourceQuery, $oLeftField->GetParentDetails(), array_keys($aAliases));
  340. }
  341. if (!array_key_exists($sToClass, $aAliases))
  342. {
  343. throw new OqlNormalizeException('Unknown class in join condition (right expression)', $sSourceQuery, $oRightField->GetParentDetails(), array_keys($aAliases));
  344. }
  345. $aExtKeys = $oModelReflection->ListAttributes($aAliases[$sFromClass], 'AttributeExternalKey');
  346. $aObjKeys = $oModelReflection->ListAttributes($aAliases[$sFromClass], 'AttributeObjectKey');
  347. $aAllKeys = array_merge($aExtKeys, $aObjKeys);
  348. if (!array_key_exists($sExtKeyAttCode, $aAllKeys))
  349. {
  350. throw new OqlNormalizeException('Unknown key in join condition (left expression)', $sSourceQuery, $oLeftField->GetNameDetails(), array_keys($aAllKeys));
  351. }
  352. if ($sFromClass == $sJoinClassAlias)
  353. {
  354. if (array_key_exists($sExtKeyAttCode, $aExtKeys)) // Skip that check for object keys
  355. {
  356. $sTargetClass = $oModelReflection->GetAttributeProperty($aAliases[$sFromClass], $sExtKeyAttCode, 'targetclass');
  357. if(!$oModelReflection->IsSameFamilyBranch($aAliases[$sToClass], $sTargetClass))
  358. {
  359. throw new OqlNormalizeException("The joined class ($aAliases[$sFromClass]) is not compatible with the external key, which is pointing to $sTargetClass", $sSourceQuery, $oLeftField->GetNameDetails());
  360. }
  361. }
  362. }
  363. else
  364. {
  365. $sOperator = $oJoinSpec->GetOperator();
  366. switch($sOperator)
  367. {
  368. case '=':
  369. $iOperatorCode = TREE_OPERATOR_EQUALS;
  370. break;
  371. case 'BELOW':
  372. $iOperatorCode = TREE_OPERATOR_BELOW;
  373. break;
  374. case 'BELOW_STRICT':
  375. $iOperatorCode = TREE_OPERATOR_BELOW_STRICT;
  376. break;
  377. case 'NOT_BELOW':
  378. $iOperatorCode = TREE_OPERATOR_NOT_BELOW;
  379. break;
  380. case 'NOT_BELOW_STRICT':
  381. $iOperatorCode = TREE_OPERATOR_NOT_BELOW_STRICT;
  382. break;
  383. case 'ABOVE':
  384. $iOperatorCode = TREE_OPERATOR_ABOVE;
  385. break;
  386. case 'ABOVE_STRICT':
  387. $iOperatorCode = TREE_OPERATOR_ABOVE_STRICT;
  388. break;
  389. case 'NOT_ABOVE':
  390. $iOperatorCode = TREE_OPERATOR_NOT_ABOVE;
  391. break;
  392. case 'NOT_ABOVE_STRICT':
  393. $iOperatorCode = TREE_OPERATOR_NOT_ABOVE_STRICT;
  394. break;
  395. }
  396. if (array_key_exists($sExtKeyAttCode, $aExtKeys)) // Skip that check for object keys
  397. {
  398. $sTargetClass = $oModelReflection->GetAttributeProperty($aAliases[$sFromClass], $sExtKeyAttCode, 'targetclass');
  399. if(!$oModelReflection->IsSameFamilyBranch($aAliases[$sToClass], $sTargetClass))
  400. {
  401. throw new OqlNormalizeException("The joined class ($aAliases[$sToClass]) is not compatible with the external key, which is pointing to $sTargetClass", $sSourceQuery, $oLeftField->GetNameDetails());
  402. }
  403. }
  404. $aAttList = $oModelReflection->ListAttributes($aAliases[$sFromClass]);
  405. $sAttType = $aAttList[$sExtKeyAttCode];
  406. if(($iOperatorCode != TREE_OPERATOR_EQUALS) && !is_subclass_of($sAttType, 'AttributeHierarchicalKey') && ($sAttType != 'AttributeHierarchicalKey'))
  407. {
  408. throw new OqlNormalizeException("The specified tree operator $sOperator is not applicable to the key", $sSourceQuery, $oLeftField->GetNameDetails());
  409. }
  410. }
  411. }
  412. }
  413. // Check the select information
  414. //
  415. $aSelected = array();
  416. foreach ($this->GetSelectedClasses() as $oClassDetails)
  417. {
  418. $sClassToSelect = $oClassDetails->GetValue();
  419. if (!array_key_exists($sClassToSelect, $aAliases))
  420. {
  421. throw new OqlNormalizeException('Unknown class [alias]', $sSourceQuery, $oClassDetails, array_keys($aAliases));
  422. }
  423. $aSelected[$sClassToSelect] = $aAliases[$sClassToSelect];
  424. }
  425. // Check the condition tree
  426. //
  427. if ($this->m_oCondition instanceof Expression)
  428. {
  429. $this->m_oCondition->Check($oModelReflection, $aAliases, $sSourceQuery);
  430. }
  431. }
  432. }
  433. ?>