oqlquery.class.inc.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  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-2015 Combodo SARL
  22. * @license http://opensource.org/licenses/AGPL-3.0
  23. */
  24. define('TREE_OPERATOR_EQUALS', 0);
  25. define('TREE_OPERATOR_BELOW', 1);
  26. define('TREE_OPERATOR_BELOW_STRICT', 2);
  27. define('TREE_OPERATOR_NOT_BELOW', 3);
  28. define('TREE_OPERATOR_NOT_BELOW_STRICT', 4);
  29. define('TREE_OPERATOR_ABOVE', 5);
  30. define('TREE_OPERATOR_ABOVE_STRICT', 6);
  31. define('TREE_OPERATOR_NOT_ABOVE', 7);
  32. define('TREE_OPERATOR_NOT_ABOVE_STRICT', 8);
  33. // Position a string within an OQL query
  34. // This is a must if we want to be able to pinpoint an error at any stage of the query interpretation
  35. // In particular, the normalization phase requires this
  36. class OqlName
  37. {
  38. protected $m_sValue;
  39. protected $m_iPos;
  40. public function __construct($sValue, $iPos)
  41. {
  42. $this->m_iPos = $iPos;
  43. $this->m_sValue = $sValue;
  44. }
  45. public function GetValue()
  46. {
  47. return $this->m_sValue;
  48. }
  49. public function GetPos()
  50. {
  51. return $this->m_iPos;
  52. }
  53. public function __toString()
  54. {
  55. return $this->m_sValue;
  56. }
  57. }
  58. /**
  59. *
  60. * Store hexadecimal values as strings so that we can support 64-bit values
  61. *
  62. */
  63. class OqlHexValue
  64. {
  65. protected $m_sValue;
  66. public function __construct($sValue)
  67. {
  68. $this->m_sValue = $sValue;
  69. }
  70. public function __toString()
  71. {
  72. return $this->m_sValue;
  73. }
  74. }
  75. class OqlJoinSpec
  76. {
  77. protected $m_oClass;
  78. protected $m_oClassAlias;
  79. protected $m_oLeftField;
  80. protected $m_oRightField;
  81. protected $m_sOperator;
  82. protected $m_oNextJoinspec;
  83. public function __construct($oClass, $oClassAlias, BinaryExpression $oExpression)
  84. {
  85. $this->m_oClass = $oClass;
  86. $this->m_oClassAlias = $oClassAlias;
  87. $this->m_oLeftField = $oExpression->GetLeftExpr();
  88. $this->m_oRightField = $oExpression->GetRightExpr();
  89. $this->m_oRightField = $oExpression->GetRightExpr();
  90. $this->m_sOperator = $oExpression->GetOperator();
  91. }
  92. public function GetClass()
  93. {
  94. return $this->m_oClass->GetValue();
  95. }
  96. public function GetClassAlias()
  97. {
  98. return $this->m_oClassAlias->GetValue();
  99. }
  100. public function GetClassDetails()
  101. {
  102. return $this->m_oClass;
  103. }
  104. public function GetClassAliasDetails()
  105. {
  106. return $this->m_oClassAlias;
  107. }
  108. public function GetLeftField()
  109. {
  110. return $this->m_oLeftField;
  111. }
  112. public function GetRightField()
  113. {
  114. return $this->m_oRightField;
  115. }
  116. public function GetOperator()
  117. {
  118. return $this->m_sOperator;
  119. }
  120. }
  121. interface CheckableExpression
  122. {
  123. /**
  124. * Check the validity of the expression with regard to the data model
  125. * and the query in which it is used
  126. *
  127. * @param ModelReflection $oModelReflection MetaModel to consider
  128. * @param array $aAliases Aliases to class names (for the current query)
  129. * @param string $sSourceQuery For the reporting
  130. * @throws OqlNormalizeException
  131. */
  132. public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery);
  133. }
  134. class BinaryOqlExpression extends BinaryExpression implements CheckableExpression
  135. {
  136. public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery)
  137. {
  138. $this->m_oLeftExpr->Check($oModelReflection, $aAliases, $sSourceQuery);
  139. $this->m_oRightExpr->Check($oModelReflection, $aAliases, $sSourceQuery);
  140. }
  141. }
  142. class ScalarOqlExpression extends ScalarExpression implements CheckableExpression
  143. {
  144. public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery)
  145. {
  146. // a scalar is always fine
  147. }
  148. }
  149. class FieldOqlExpression extends FieldExpression implements CheckableExpression
  150. {
  151. protected $m_oParent;
  152. protected $m_oName;
  153. public function __construct($oName, $oParent = null)
  154. {
  155. if (is_null($oParent))
  156. {
  157. $oParent = new OqlName('', 0);
  158. }
  159. $this->m_oParent = $oParent;
  160. $this->m_oName = $oName;
  161. parent::__construct($oName->GetValue(), $oParent->GetValue());
  162. }
  163. public function GetParentDetails()
  164. {
  165. return $this->m_oParent;
  166. }
  167. public function GetNameDetails()
  168. {
  169. return $this->m_oName;
  170. }
  171. public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery)
  172. {
  173. $sClassAlias = $this->GetParent();
  174. $sFltCode = $this->GetName();
  175. if (empty($sClassAlias))
  176. {
  177. // Try to find an alias
  178. // Build an array of field => array of aliases
  179. $aFieldClasses = array();
  180. foreach($aAliases as $sAlias => $sReal)
  181. {
  182. foreach($oModelReflection->GetFiltersList($sReal) as $sAnFltCode)
  183. {
  184. $aFieldClasses[$sAnFltCode][] = $sAlias;
  185. }
  186. }
  187. if (!array_key_exists($sFltCode, $aFieldClasses))
  188. {
  189. throw new OqlNormalizeException('Unknown filter code', $sSourceQuery, $this->GetNameDetails(), array_keys($aFieldClasses));
  190. }
  191. if (count($aFieldClasses[$sFltCode]) > 1)
  192. {
  193. throw new OqlNormalizeException('Ambiguous filter code', $sSourceQuery, $this->GetNameDetails());
  194. }
  195. $sClassAlias = $aFieldClasses[$sFltCode][0];
  196. }
  197. else
  198. {
  199. if (!array_key_exists($sClassAlias, $aAliases))
  200. {
  201. throw new OqlNormalizeException('Unknown class [alias]', $sSourceQuery, $this->GetParentDetails(), array_keys($aAliases));
  202. }
  203. $sClass = $aAliases[$sClassAlias];
  204. if (!$oModelReflection->IsValidFilterCode($sClass, $sFltCode))
  205. {
  206. throw new OqlNormalizeException('Unknown filter code', $sSourceQuery, $this->GetNameDetails(), $oModelReflection->GetFiltersList($sClass));
  207. }
  208. }
  209. }
  210. }
  211. class VariableOqlExpression extends VariableExpression implements CheckableExpression
  212. {
  213. public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery)
  214. {
  215. // a scalar is always fine
  216. }
  217. }
  218. class ListOqlExpression extends ListExpression implements CheckableExpression
  219. {
  220. public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery)
  221. {
  222. foreach ($this->GetItems() as $oItemExpression)
  223. {
  224. $oItemExpression->Check($oModelReflection, $aAliases, $sSourceQuery);
  225. }
  226. }
  227. }
  228. class FunctionOqlExpression extends FunctionExpression implements CheckableExpression
  229. {
  230. public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery)
  231. {
  232. foreach ($this->GetArgs() as $oArgExpression)
  233. {
  234. $oArgExpression->Check($oModelReflection, $aAliases, $sSourceQuery);
  235. }
  236. }
  237. }
  238. class IntervalOqlExpression extends IntervalExpression implements CheckableExpression
  239. {
  240. public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery)
  241. {
  242. // an interval is always fine (made of a scalar and unit)
  243. }
  244. }
  245. abstract class OqlQuery
  246. {
  247. public function __construct()
  248. {
  249. }
  250. /**
  251. * Check the validity of the expression with regard to the data model
  252. * and the query in which it is used
  253. *
  254. * @param ModelReflection $oModelReflection MetaModel to consider
  255. * @throws OqlNormalizeException
  256. */
  257. abstract public function Check(ModelReflection $oModelReflection, $sSourceQuery);
  258. }
  259. class OqlObjectQuery extends OqlQuery
  260. {
  261. protected $m_aSelect; // array of selected classes
  262. protected $m_oClass;
  263. protected $m_oClassAlias;
  264. protected $m_aJoins; // array of OqlJoinSpec
  265. protected $m_oCondition; // condition tree (expressions)
  266. public function __construct($oClass, $oClassAlias, $oCondition = null, $aJoins = null, $aSelect = null)
  267. {
  268. $this->m_aSelect = $aSelect;
  269. $this->m_oClass = $oClass;
  270. $this->m_oClassAlias = $oClassAlias;
  271. $this->m_aJoins = $aJoins;
  272. $this->m_oCondition = $oCondition;
  273. parent::__construct();
  274. }
  275. public function GetSelectedClasses()
  276. {
  277. return $this->m_aSelect;
  278. }
  279. public function GetClass()
  280. {
  281. return $this->m_oClass->GetValue();
  282. }
  283. public function GetClassAlias()
  284. {
  285. return $this->m_oClassAlias->GetValue();
  286. }
  287. public function GetClassDetails()
  288. {
  289. return $this->m_oClass;
  290. }
  291. public function GetClassAliasDetails()
  292. {
  293. return $this->m_oClassAlias;
  294. }
  295. public function GetJoins()
  296. {
  297. return $this->m_aJoins;
  298. }
  299. public function GetCondition()
  300. {
  301. return $this->m_oCondition;
  302. }
  303. /**
  304. * Recursively check the validity of the expression with regard to the data model
  305. * and the query in which it is used
  306. *
  307. * @param ModelReflection $oModelReflection MetaModel to consider
  308. * @throws OqlNormalizeException
  309. */
  310. public function Check(ModelReflection $oModelReflection, $sSourceQuery)
  311. {
  312. $sClass = $this->GetClass();
  313. $sClassAlias = $this->GetClassAlias();
  314. if (!$oModelReflection->IsValidClass($sClass))
  315. {
  316. throw new UnknownClassOqlException($sSourceQuery, $this->GetClassDetails(), $oModelReflection->GetClasses());
  317. }
  318. $aAliases = array($sClassAlias => $sClass);
  319. $aJoinSpecs = $this->GetJoins();
  320. if (is_array($aJoinSpecs))
  321. {
  322. foreach ($aJoinSpecs as $oJoinSpec)
  323. {
  324. $sJoinClass = $oJoinSpec->GetClass();
  325. $sJoinClassAlias = $oJoinSpec->GetClassAlias();
  326. if (!$oModelReflection->IsValidClass($sJoinClass))
  327. {
  328. throw new UnknownClassOqlException($sSourceQuery, $oJoinSpec->GetClassDetails(), $oModelReflection->GetClasses());
  329. }
  330. if (array_key_exists($sJoinClassAlias, $aAliases))
  331. {
  332. if ($sJoinClassAlias != $sJoinClass)
  333. {
  334. throw new OqlNormalizeException('Duplicate class alias', $sSourceQuery, $oJoinSpec->GetClassAliasDetails());
  335. }
  336. else
  337. {
  338. throw new OqlNormalizeException('Duplicate class name', $sSourceQuery, $oJoinSpec->GetClassDetails());
  339. }
  340. }
  341. // Assumption: ext key on the left only !!!
  342. // normalization should take care of this
  343. $oLeftField = $oJoinSpec->GetLeftField();
  344. $sFromClass = $oLeftField->GetParent();
  345. $sExtKeyAttCode = $oLeftField->GetName();
  346. $oRightField = $oJoinSpec->GetRightField();
  347. $sToClass = $oRightField->GetParent();
  348. $sPKeyDescriptor = $oRightField->GetName();
  349. if ($sPKeyDescriptor != 'id')
  350. {
  351. throw new OqlNormalizeException('Wrong format for Join clause (right hand), expecting an id', $sSourceQuery, $oRightField->GetNameDetails(), array('id'));
  352. }
  353. $aAliases[$sJoinClassAlias] = $sJoinClass;
  354. if (!array_key_exists($sFromClass, $aAliases))
  355. {
  356. throw new OqlNormalizeException('Unknown class in join condition (left expression)', $sSourceQuery, $oLeftField->GetParentDetails(), array_keys($aAliases));
  357. }
  358. if (!array_key_exists($sToClass, $aAliases))
  359. {
  360. throw new OqlNormalizeException('Unknown class in join condition (right expression)', $sSourceQuery, $oRightField->GetParentDetails(), array_keys($aAliases));
  361. }
  362. $aExtKeys = $oModelReflection->ListAttributes($aAliases[$sFromClass], 'AttributeExternalKey');
  363. $aObjKeys = $oModelReflection->ListAttributes($aAliases[$sFromClass], 'AttributeObjectKey');
  364. $aAllKeys = array_merge($aExtKeys, $aObjKeys);
  365. if (!array_key_exists($sExtKeyAttCode, $aAllKeys))
  366. {
  367. throw new OqlNormalizeException('Unknown key in join condition (left expression)', $sSourceQuery, $oLeftField->GetNameDetails(), array_keys($aAllKeys));
  368. }
  369. if ($sFromClass == $sJoinClassAlias)
  370. {
  371. if (array_key_exists($sExtKeyAttCode, $aExtKeys)) // Skip that check for object keys
  372. {
  373. $sTargetClass = $oModelReflection->GetAttributeProperty($aAliases[$sFromClass], $sExtKeyAttCode, 'targetclass');
  374. if(!$oModelReflection->IsSameFamilyBranch($aAliases[$sToClass], $sTargetClass))
  375. {
  376. throw new OqlNormalizeException("The joined class ($aAliases[$sFromClass]) is not compatible with the external key, which is pointing to $sTargetClass", $sSourceQuery, $oLeftField->GetNameDetails());
  377. }
  378. }
  379. }
  380. else
  381. {
  382. $sOperator = $oJoinSpec->GetOperator();
  383. switch($sOperator)
  384. {
  385. case '=':
  386. $iOperatorCode = TREE_OPERATOR_EQUALS;
  387. break;
  388. case 'BELOW':
  389. $iOperatorCode = TREE_OPERATOR_BELOW;
  390. break;
  391. case 'BELOW_STRICT':
  392. $iOperatorCode = TREE_OPERATOR_BELOW_STRICT;
  393. break;
  394. case 'NOT_BELOW':
  395. $iOperatorCode = TREE_OPERATOR_NOT_BELOW;
  396. break;
  397. case 'NOT_BELOW_STRICT':
  398. $iOperatorCode = TREE_OPERATOR_NOT_BELOW_STRICT;
  399. break;
  400. case 'ABOVE':
  401. $iOperatorCode = TREE_OPERATOR_ABOVE;
  402. break;
  403. case 'ABOVE_STRICT':
  404. $iOperatorCode = TREE_OPERATOR_ABOVE_STRICT;
  405. break;
  406. case 'NOT_ABOVE':
  407. $iOperatorCode = TREE_OPERATOR_NOT_ABOVE;
  408. break;
  409. case 'NOT_ABOVE_STRICT':
  410. $iOperatorCode = TREE_OPERATOR_NOT_ABOVE_STRICT;
  411. break;
  412. }
  413. if (array_key_exists($sExtKeyAttCode, $aExtKeys)) // Skip that check for object keys
  414. {
  415. $sTargetClass = $oModelReflection->GetAttributeProperty($aAliases[$sFromClass], $sExtKeyAttCode, 'targetclass');
  416. if(!$oModelReflection->IsSameFamilyBranch($aAliases[$sToClass], $sTargetClass))
  417. {
  418. throw new OqlNormalizeException("The joined class ($aAliases[$sToClass]) is not compatible with the external key, which is pointing to $sTargetClass", $sSourceQuery, $oLeftField->GetNameDetails());
  419. }
  420. }
  421. $aAttList = $oModelReflection->ListAttributes($aAliases[$sFromClass]);
  422. $sAttType = $aAttList[$sExtKeyAttCode];
  423. if(($iOperatorCode != TREE_OPERATOR_EQUALS) && !is_subclass_of($sAttType, 'AttributeHierarchicalKey') && ($sAttType != 'AttributeHierarchicalKey'))
  424. {
  425. throw new OqlNormalizeException("The specified tree operator $sOperator is not applicable to the key", $sSourceQuery, $oLeftField->GetNameDetails());
  426. }
  427. }
  428. }
  429. }
  430. // Check the select information
  431. //
  432. foreach ($this->GetSelectedClasses() as $oClassDetails)
  433. {
  434. $sClassToSelect = $oClassDetails->GetValue();
  435. if (!array_key_exists($sClassToSelect, $aAliases))
  436. {
  437. throw new OqlNormalizeException('Unknown class [alias]', $sSourceQuery, $oClassDetails, array_keys($aAliases));
  438. }
  439. }
  440. // Check the condition tree
  441. //
  442. if ($this->m_oCondition instanceof Expression)
  443. {
  444. $this->m_oCondition->Check($oModelReflection, $aAliases, $sSourceQuery);
  445. }
  446. }
  447. /**
  448. * Make the relevant DBSearch instance (FromOQL)
  449. */
  450. public function ToDBSearch($sQuery)
  451. {
  452. $sClass = $this->GetClass();
  453. $sClassAlias = $this->GetClassAlias();
  454. $oSearch = new DBObjectSearch($sClass, $sClassAlias);
  455. $oSearch->InitFromOqlQuery($this, $sQuery);
  456. return $oSearch;
  457. }
  458. }
  459. class OqlUnionQuery extends OqlQuery
  460. {
  461. protected $aQueries;
  462. public function __construct(OqlObjectQuery $oLeftQuery, OqlQuery $oRightQueryOrUnion)
  463. {
  464. $this->aQueries[] = $oLeftQuery;
  465. if ($oRightQueryOrUnion instanceof OqlUnionQuery)
  466. {
  467. foreach ($oRightQueryOrUnion->GetQueries() as $oSingleQuery)
  468. {
  469. $this->aQueries[] = $oSingleQuery;
  470. }
  471. }
  472. else
  473. {
  474. $this->aQueries[] = $oRightQueryOrUnion;
  475. }
  476. }
  477. public function GetQueries()
  478. {
  479. return $this->aQueries;
  480. }
  481. /**
  482. * Check the validity of the expression with regard to the data model
  483. * and the query in which it is used
  484. *
  485. * @param ModelReflection $oModelReflection MetaModel to consider
  486. * @throws OqlNormalizeException
  487. */
  488. public function Check(ModelReflection $oModelReflection, $sSourceQuery)
  489. {
  490. $aColumnToClasses = array();
  491. foreach ($this->aQueries as $iQuery => $oQuery)
  492. {
  493. $oQuery->Check($oModelReflection, $sSourceQuery);
  494. $aAliasToClass = array($oQuery->GetClassAlias() => $oQuery->GetClass());
  495. $aJoinSpecs = $oQuery->GetJoins();
  496. if (is_array($aJoinSpecs))
  497. {
  498. foreach ($aJoinSpecs as $oJoinSpec)
  499. {
  500. $aAliasToClass[$oJoinSpec->GetClassAlias()] = $oJoinSpec->GetClass();
  501. }
  502. }
  503. $aSelectedClasses = $oQuery->GetSelectedClasses();
  504. if ($iQuery != 0)
  505. {
  506. if (count($aSelectedClasses) < count($aColumnToClasses))
  507. {
  508. $oLastClass = end($aSelectedClasses);
  509. throw new OqlNormalizeException('Too few selected classes in the subquery', $sSourceQuery, $oLastClass);
  510. }
  511. if (count($aSelectedClasses) > count($aColumnToClasses))
  512. {
  513. $oLastClass = end($aSelectedClasses);
  514. throw new OqlNormalizeException('Too many selected classes in the subquery', $sSourceQuery, $oLastClass);
  515. }
  516. }
  517. foreach ($aSelectedClasses as $iColumn => $oClassDetails)
  518. {
  519. $sAlias = $oClassDetails->GetValue();
  520. $sClass = $aAliasToClass[$sAlias];
  521. $aColumnToClasses[$iColumn][] = array(
  522. 'alias' => $sAlias,
  523. 'class' => $sClass,
  524. 'class_name' => $oClassDetails,
  525. );
  526. }
  527. }
  528. foreach ($aColumnToClasses as $iColumn => $aClasses)
  529. {
  530. foreach ($aClasses as $iQuery => $aData)
  531. {
  532. if ($iQuery == 0)
  533. {
  534. // Establish the reference
  535. $sRootClass = $oModelReflection->GetRootClass($aData['class']);
  536. }
  537. else
  538. {
  539. if ($oModelReflection->GetRootClass($aData['class']) != $sRootClass)
  540. {
  541. $aSubclasses = $oModelReflection->EnumChildClasses($sRootClass, ENUM_CHILD_CLASSES_ALL);
  542. throw new OqlNormalizeException('Incompatible classes: could not find a common ancestor', $sSourceQuery, $aData['class_name'], $aSubclasses);
  543. }
  544. }
  545. }
  546. }
  547. }
  548. /**
  549. * Make the relevant DBSearch instance (FromOQL)
  550. */
  551. public function ToDBSearch($sQuery)
  552. {
  553. $aSearches = array();
  554. foreach ($this->aQueries as $oQuery)
  555. {
  556. $aSearches[] = $oQuery->ToDBSearch($sQuery);
  557. }
  558. $oSearch = new DBUnionSearch($aSearches);
  559. return $oSearch;
  560. }
  561. }