oqlquery.class.inc.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747
  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. * Determine the class
  260. *
  261. * @param ModelReflection $oModelReflection MetaModel to consider
  262. * @return string
  263. * @throws Exception
  264. */
  265. abstract public function GetClass(ModelReflection $oModelReflection);
  266. /**
  267. * Determine the class alias
  268. *
  269. * @return string
  270. * @throws Exception
  271. */
  272. abstract public function GetClassAlias();
  273. }
  274. class OqlObjectQuery extends OqlQuery
  275. {
  276. protected $m_aSelect; // array of selected classes
  277. protected $m_oClass;
  278. protected $m_oClassAlias;
  279. protected $m_aJoins; // array of OqlJoinSpec
  280. protected $m_oCondition; // condition tree (expressions)
  281. public function __construct($oClass, $oClassAlias, $oCondition = null, $aJoins = null, $aSelect = null)
  282. {
  283. $this->m_aSelect = $aSelect;
  284. $this->m_oClass = $oClass;
  285. $this->m_oClassAlias = $oClassAlias;
  286. $this->m_aJoins = $aJoins;
  287. $this->m_oCondition = $oCondition;
  288. parent::__construct();
  289. }
  290. public function GetSelectedClasses()
  291. {
  292. return $this->m_aSelect;
  293. }
  294. /**
  295. * Determine the class
  296. *
  297. * @param ModelReflection $oModelReflection MetaModel to consider
  298. * @return string
  299. * @throws Exception
  300. */
  301. public function GetClass(ModelReflection $oModelReflection)
  302. {
  303. return $this->m_oClass->GetValue();
  304. }
  305. /**
  306. * Determine the class alias
  307. *
  308. * @return string
  309. * @throws Exception
  310. */
  311. public function GetClassAlias()
  312. {
  313. return $this->m_oClassAlias->GetValue();
  314. }
  315. public function GetClassDetails()
  316. {
  317. return $this->m_oClass;
  318. }
  319. public function GetClassAliasDetails()
  320. {
  321. return $this->m_oClassAlias;
  322. }
  323. public function GetJoins()
  324. {
  325. return $this->m_aJoins;
  326. }
  327. public function GetCondition()
  328. {
  329. return $this->m_oCondition;
  330. }
  331. /**
  332. * Recursively check the validity of the expression with regard to the data model
  333. * and the query in which it is used
  334. *
  335. * @param ModelReflection $oModelReflection MetaModel to consider
  336. * @throws OqlNormalizeException
  337. */
  338. public function Check(ModelReflection $oModelReflection, $sSourceQuery)
  339. {
  340. $sClass = $this->GetClass($oModelReflection);
  341. $sClassAlias = $this->GetClassAlias();
  342. if (!$oModelReflection->IsValidClass($sClass))
  343. {
  344. throw new UnknownClassOqlException($sSourceQuery, $this->GetClassDetails(), $oModelReflection->GetClasses());
  345. }
  346. $aAliases = array($sClassAlias => $sClass);
  347. $aJoinSpecs = $this->GetJoins();
  348. if (is_array($aJoinSpecs))
  349. {
  350. foreach ($aJoinSpecs as $oJoinSpec)
  351. {
  352. $sJoinClass = $oJoinSpec->GetClass();
  353. $sJoinClassAlias = $oJoinSpec->GetClassAlias();
  354. if (!$oModelReflection->IsValidClass($sJoinClass))
  355. {
  356. throw new UnknownClassOqlException($sSourceQuery, $oJoinSpec->GetClassDetails(), $oModelReflection->GetClasses());
  357. }
  358. if (array_key_exists($sJoinClassAlias, $aAliases))
  359. {
  360. if ($sJoinClassAlias != $sJoinClass)
  361. {
  362. throw new OqlNormalizeException('Duplicate class alias', $sSourceQuery, $oJoinSpec->GetClassAliasDetails());
  363. }
  364. else
  365. {
  366. throw new OqlNormalizeException('Duplicate class name', $sSourceQuery, $oJoinSpec->GetClassDetails());
  367. }
  368. }
  369. // Assumption: ext key on the left only !!!
  370. // normalization should take care of this
  371. $oLeftField = $oJoinSpec->GetLeftField();
  372. $sFromClass = $oLeftField->GetParent();
  373. $sExtKeyAttCode = $oLeftField->GetName();
  374. $oRightField = $oJoinSpec->GetRightField();
  375. $sToClass = $oRightField->GetParent();
  376. $sPKeyDescriptor = $oRightField->GetName();
  377. if ($sPKeyDescriptor != 'id')
  378. {
  379. throw new OqlNormalizeException('Wrong format for Join clause (right hand), expecting an id', $sSourceQuery, $oRightField->GetNameDetails(), array('id'));
  380. }
  381. $aAliases[$sJoinClassAlias] = $sJoinClass;
  382. if (!array_key_exists($sFromClass, $aAliases))
  383. {
  384. throw new OqlNormalizeException('Unknown class in join condition (left expression)', $sSourceQuery, $oLeftField->GetParentDetails(), array_keys($aAliases));
  385. }
  386. if (!array_key_exists($sToClass, $aAliases))
  387. {
  388. throw new OqlNormalizeException('Unknown class in join condition (right expression)', $sSourceQuery, $oRightField->GetParentDetails(), array_keys($aAliases));
  389. }
  390. $aExtKeys = $oModelReflection->ListAttributes($aAliases[$sFromClass], 'AttributeExternalKey');
  391. $aObjKeys = $oModelReflection->ListAttributes($aAliases[$sFromClass], 'AttributeObjectKey');
  392. $aAllKeys = array_merge($aExtKeys, $aObjKeys);
  393. if (!array_key_exists($sExtKeyAttCode, $aAllKeys))
  394. {
  395. throw new OqlNormalizeException('Unknown key in join condition (left expression)', $sSourceQuery, $oLeftField->GetNameDetails(), array_keys($aAllKeys));
  396. }
  397. if ($sFromClass == $sJoinClassAlias)
  398. {
  399. if (array_key_exists($sExtKeyAttCode, $aExtKeys)) // Skip that check for object keys
  400. {
  401. $sTargetClass = $oModelReflection->GetAttributeProperty($aAliases[$sFromClass], $sExtKeyAttCode, 'targetclass');
  402. if(!$oModelReflection->IsSameFamilyBranch($aAliases[$sToClass], $sTargetClass))
  403. {
  404. throw new OqlNormalizeException("The joined class ($aAliases[$sFromClass]) is not compatible with the external key, which is pointing to $sTargetClass", $sSourceQuery, $oLeftField->GetNameDetails());
  405. }
  406. }
  407. }
  408. else
  409. {
  410. $sOperator = $oJoinSpec->GetOperator();
  411. switch($sOperator)
  412. {
  413. case '=':
  414. $iOperatorCode = TREE_OPERATOR_EQUALS;
  415. break;
  416. case 'BELOW':
  417. $iOperatorCode = TREE_OPERATOR_BELOW;
  418. break;
  419. case 'BELOW_STRICT':
  420. $iOperatorCode = TREE_OPERATOR_BELOW_STRICT;
  421. break;
  422. case 'NOT_BELOW':
  423. $iOperatorCode = TREE_OPERATOR_NOT_BELOW;
  424. break;
  425. case 'NOT_BELOW_STRICT':
  426. $iOperatorCode = TREE_OPERATOR_NOT_BELOW_STRICT;
  427. break;
  428. case 'ABOVE':
  429. $iOperatorCode = TREE_OPERATOR_ABOVE;
  430. break;
  431. case 'ABOVE_STRICT':
  432. $iOperatorCode = TREE_OPERATOR_ABOVE_STRICT;
  433. break;
  434. case 'NOT_ABOVE':
  435. $iOperatorCode = TREE_OPERATOR_NOT_ABOVE;
  436. break;
  437. case 'NOT_ABOVE_STRICT':
  438. $iOperatorCode = TREE_OPERATOR_NOT_ABOVE_STRICT;
  439. break;
  440. }
  441. if (array_key_exists($sExtKeyAttCode, $aExtKeys)) // Skip that check for object keys
  442. {
  443. $sTargetClass = $oModelReflection->GetAttributeProperty($aAliases[$sFromClass], $sExtKeyAttCode, 'targetclass');
  444. if(!$oModelReflection->IsSameFamilyBranch($aAliases[$sToClass], $sTargetClass))
  445. {
  446. throw new OqlNormalizeException("The joined class ($aAliases[$sToClass]) is not compatible with the external key, which is pointing to $sTargetClass", $sSourceQuery, $oLeftField->GetNameDetails());
  447. }
  448. }
  449. $aAttList = $oModelReflection->ListAttributes($aAliases[$sFromClass]);
  450. $sAttType = $aAttList[$sExtKeyAttCode];
  451. if(($iOperatorCode != TREE_OPERATOR_EQUALS) && !is_subclass_of($sAttType, 'AttributeHierarchicalKey') && ($sAttType != 'AttributeHierarchicalKey'))
  452. {
  453. throw new OqlNormalizeException("The specified tree operator $sOperator is not applicable to the key", $sSourceQuery, $oLeftField->GetNameDetails());
  454. }
  455. }
  456. }
  457. }
  458. // Check the select information
  459. //
  460. foreach ($this->GetSelectedClasses() as $oClassDetails)
  461. {
  462. $sClassToSelect = $oClassDetails->GetValue();
  463. if (!array_key_exists($sClassToSelect, $aAliases))
  464. {
  465. throw new OqlNormalizeException('Unknown class [alias]', $sSourceQuery, $oClassDetails, array_keys($aAliases));
  466. }
  467. }
  468. // Check the condition tree
  469. //
  470. if ($this->m_oCondition instanceof Expression)
  471. {
  472. $this->m_oCondition->Check($oModelReflection, $aAliases, $sSourceQuery);
  473. }
  474. }
  475. /**
  476. * Make the relevant DBSearch instance (FromOQL)
  477. */
  478. public function ToDBSearch($sQuery)
  479. {
  480. $sClass = $this->GetClass(new ModelReflectionRuntime());
  481. $sClassAlias = $this->GetClassAlias();
  482. $oSearch = new DBObjectSearch($sClass, $sClassAlias);
  483. $oSearch->InitFromOqlQuery($this, $sQuery);
  484. return $oSearch;
  485. }
  486. }
  487. class OqlUnionQuery extends OqlQuery
  488. {
  489. protected $aQueries;
  490. public function __construct(OqlObjectQuery $oLeftQuery, OqlQuery $oRightQueryOrUnion)
  491. {
  492. $this->aQueries[] = $oLeftQuery;
  493. if ($oRightQueryOrUnion instanceof OqlUnionQuery)
  494. {
  495. foreach ($oRightQueryOrUnion->GetQueries() as $oSingleQuery)
  496. {
  497. $this->aQueries[] = $oSingleQuery;
  498. }
  499. }
  500. else
  501. {
  502. $this->aQueries[] = $oRightQueryOrUnion;
  503. }
  504. }
  505. public function GetQueries()
  506. {
  507. return $this->aQueries;
  508. }
  509. /**
  510. * Check the validity of the expression with regard to the data model
  511. * and the query in which it is used
  512. *
  513. * @param ModelReflection $oModelReflection MetaModel to consider
  514. * @throws OqlNormalizeException
  515. */
  516. public function Check(ModelReflection $oModelReflection, $sSourceQuery)
  517. {
  518. $aColumnToClasses = array();
  519. foreach ($this->aQueries as $iQuery => $oQuery)
  520. {
  521. $oQuery->Check($oModelReflection, $sSourceQuery);
  522. $aAliasToClass = array($oQuery->GetClassAlias() => $oQuery->GetClass($oModelReflection));
  523. $aJoinSpecs = $oQuery->GetJoins();
  524. if (is_array($aJoinSpecs))
  525. {
  526. foreach ($aJoinSpecs as $oJoinSpec)
  527. {
  528. $aAliasToClass[$oJoinSpec->GetClassAlias()] = $oJoinSpec->GetClass();
  529. }
  530. }
  531. $aSelectedClasses = $oQuery->GetSelectedClasses();
  532. if ($iQuery != 0)
  533. {
  534. if (count($aSelectedClasses) < count($aColumnToClasses))
  535. {
  536. $oLastClass = end($aSelectedClasses);
  537. throw new OqlNormalizeException('Too few selected classes in the subquery', $sSourceQuery, $oLastClass);
  538. }
  539. if (count($aSelectedClasses) > count($aColumnToClasses))
  540. {
  541. $oLastClass = end($aSelectedClasses);
  542. throw new OqlNormalizeException('Too many selected classes in the subquery', $sSourceQuery, $oLastClass);
  543. }
  544. }
  545. foreach ($aSelectedClasses as $iColumn => $oClassDetails)
  546. {
  547. $sAlias = $oClassDetails->GetValue();
  548. $sClass = $aAliasToClass[$sAlias];
  549. $aColumnToClasses[$iColumn][] = array(
  550. 'alias' => $sAlias,
  551. 'class' => $sClass,
  552. 'class_name' => $oClassDetails,
  553. );
  554. }
  555. }
  556. foreach ($aColumnToClasses as $iColumn => $aClasses)
  557. {
  558. foreach ($aClasses as $iQuery => $aData)
  559. {
  560. if ($iQuery == 0)
  561. {
  562. // Establish the reference
  563. $sRootClass = $oModelReflection->GetRootClass($aData['class']);
  564. }
  565. else
  566. {
  567. if ($oModelReflection->GetRootClass($aData['class']) != $sRootClass)
  568. {
  569. $aSubclasses = $oModelReflection->EnumChildClasses($sRootClass, ENUM_CHILD_CLASSES_ALL);
  570. throw new OqlNormalizeException('Incompatible classes: could not find a common ancestor', $sSourceQuery, $aData['class_name'], $aSubclasses);
  571. }
  572. }
  573. }
  574. }
  575. }
  576. /**
  577. * Determine the class
  578. *
  579. * @param ModelReflection $oModelReflection MetaModel to consider
  580. * @return string
  581. * @throws Exception
  582. */
  583. public function GetClass(ModelReflection $oModelReflection)
  584. {
  585. $aFirstColClasses = array();
  586. foreach ($this->aQueries as $iQuery => $oQuery)
  587. {
  588. $aFirstColClasses[] = $oQuery->GetClass($oModelReflection);
  589. }
  590. $sClass = self::GetLowestCommonAncestor($oModelReflection, $aFirstColClasses);
  591. if (is_null($sClass))
  592. {
  593. throw new Exception('Could not determine the class of the union query. This issue should have been detected earlier by calling OqlQuery::Check()');
  594. }
  595. return $sClass;
  596. }
  597. /**
  598. * Determine the class alias
  599. *
  600. * @return string
  601. * @throws Exception
  602. */
  603. public function GetClassAlias()
  604. {
  605. $sAlias = $this->aQueries[0]->GetClassAlias();
  606. return $sAlias;
  607. }
  608. /**
  609. * Check the validity of the expression with regard to the data model
  610. * and the query in which it is used
  611. *
  612. * @param ModelReflection $oModelReflection MetaModel to consider
  613. * @param array $aClasses Flat list of classes
  614. * @return string the lowest common ancestor amongst classes, null if none has been found
  615. * @throws Exception
  616. */
  617. public static function GetLowestCommonAncestor(ModelReflection $oModelReflection, $aClasses)
  618. {
  619. $sAncestor = null;
  620. foreach($aClasses as $sClass)
  621. {
  622. if (is_null($sAncestor))
  623. {
  624. // first loop
  625. $sAncestor = $sClass;
  626. }
  627. elseif ($sClass == $sAncestor)
  628. {
  629. // remains the same
  630. }
  631. elseif ($oModelReflection->GetRootClass($sClass) != $oModelReflection->GetRootClass($sAncestor))
  632. {
  633. $sAncestor = null;
  634. break;
  635. }
  636. else
  637. {
  638. $sAncestor = self::LowestCommonAncestor($oModelReflection, $sAncestor, $sClass);
  639. }
  640. }
  641. return $sAncestor;
  642. }
  643. /**
  644. * Note: assumes that class A and B have a common ancestor
  645. */
  646. protected static function LowestCommonAncestor(ModelReflection $oModelReflection, $sClassA, $sClassB)
  647. {
  648. if ($sClassA == $sClassB)
  649. {
  650. $sRet = $sClassA;
  651. }
  652. elseif (in_array($sClassA, $oModelReflection->EnumChildClasses($sClassB)))
  653. {
  654. $sRet = $sClassB;
  655. }
  656. elseif (in_array($sClassB, $oModelReflection->EnumChildClasses($sClassA)))
  657. {
  658. $sRet = $sClassA;
  659. }
  660. else
  661. {
  662. // Recurse
  663. $sRet = self::LowestCommonAncestor($oModelReflection, $sClassA, $oModelReflection->GetParentClass($sClassB));
  664. }
  665. return $sRet;
  666. }
  667. /**
  668. * Make the relevant DBSearch instance (FromOQL)
  669. */
  670. public function ToDBSearch($sQuery)
  671. {
  672. $aSearches = array();
  673. foreach ($this->aQueries as $oQuery)
  674. {
  675. $aSearches[] = $oQuery->ToDBSearch($sQuery);
  676. }
  677. $oSearch = new DBUnionSearch($aSearches);
  678. return $oSearch;
  679. }
  680. }