expression.class.inc.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749
  1. <?php
  2. // Copyright (C) 2010 Combodo SARL
  3. //
  4. // This program is free software; you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation; version 3 of the License.
  7. //
  8. // This program is distributed in the hope that it will be useful,
  9. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. // GNU General Public License for more details.
  12. //
  13. // You should have received a copy of the GNU General Public License
  14. // along with this program; if not, write to the Free Software
  15. // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  16. /**
  17. * General definition of an expression tree (could be OQL, SQL or whatever)
  18. *
  19. * @author Erwan Taloc <erwan.taloc@combodo.com>
  20. * @author Romain Quetiez <romain.quetiez@combodo.com>
  21. * @author Denis Flaven <denis.flaven@combodo.com>
  22. * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL
  23. */
  24. class MissingQueryArgument extends CoreException
  25. {
  26. }
  27. abstract class Expression
  28. {
  29. // recursive translation of identifiers
  30. abstract public function GetUnresolvedFields($sAlias, &$aUnresolved);
  31. abstract public function Translate($aTranslationData, $bMatchAll = true);
  32. // recursive rendering (aArgs used as input by default, or used as output if bRetrofitParams set to True
  33. abstract public function Render(&$aArgs = null, $bRetrofitParams = false);
  34. // recursively builds an array of class => fieldname
  35. abstract public function ListRequiredFields();
  36. abstract public function IsTrue();
  37. public function RequiresField($sClass, $sFieldName)
  38. {
  39. // #@# todo - optimize : this is called quite often when building a single query !
  40. $aRequired = $this->ListRequiredFields();
  41. if (!in_array($sClass.'.'.$sFieldName, $aRequired)) return false;
  42. return true;
  43. }
  44. public function serialize()
  45. {
  46. return base64_encode($this->Render());
  47. }
  48. static public function unserialize($sValue)
  49. {
  50. return self::FromOQL(base64_decode($sValue));
  51. }
  52. static public function FromOQL($sConditionExpr)
  53. {
  54. $oOql = new OqlInterpreter($sConditionExpr);
  55. $oExpression = $oOql->ParseExpression();
  56. return $oExpression;
  57. }
  58. static public function FromSQL($sSQL)
  59. {
  60. $oSql = new SQLExpression($sSQL);
  61. return $oSql;
  62. }
  63. public function LogAnd($oExpr)
  64. {
  65. if ($this->IsTrue()) return clone $oExpr;
  66. if ($oExpr->IsTrue()) return clone $this;
  67. return new BinaryExpression($this, 'AND', $oExpr);
  68. }
  69. public function LogOr($oExpr)
  70. {
  71. return new BinaryExpression($this, 'OR', $oExpr);
  72. }
  73. }
  74. class SQLExpression extends Expression
  75. {
  76. protected $m_sSQL;
  77. public function __construct($sSQL)
  78. {
  79. $this->m_sSQL = $sSQL;
  80. }
  81. public function IsTrue()
  82. {
  83. return false;
  84. }
  85. // recursive rendering
  86. public function Render(&$aArgs = null, $bRetrofitParams = false)
  87. {
  88. return $this->m_sSQL;
  89. }
  90. public function GetUnresolvedFields($sAlias, &$aUnresolved)
  91. {
  92. }
  93. public function Translate($aTranslationData, $bMatchAll = true)
  94. {
  95. return clone $this;
  96. }
  97. public function ListRequiredFields()
  98. {
  99. return array();
  100. }
  101. }
  102. class BinaryExpression extends Expression
  103. {
  104. protected $m_oLeftExpr; // filter code or an SQL expression (later?)
  105. protected $m_oRightExpr;
  106. protected $m_sOperator;
  107. public function __construct($oLeftExpr, $sOperator, $oRightExpr)
  108. {
  109. if (!is_object($oLeftExpr))
  110. {
  111. throw new CoreException('Expecting an Expression object on the left hand', array('found_type' => gettype($oLeftExpr)));
  112. }
  113. if (!is_object($oRightExpr))
  114. {
  115. throw new CoreException('Expecting an Expression object on the right hand', array('found_type' => gettype($oRightExpr)));
  116. }
  117. if (!$oLeftExpr instanceof Expression)
  118. {
  119. throw new CoreException('Expecting an Expression object on the left hand', array('found_class' => get_class($oLeftExpr)));
  120. }
  121. if (!$oRightExpr instanceof Expression)
  122. {
  123. throw new CoreException('Expecting an Expression object on the right hand', array('found_class' => get_class($oRightExpr)));
  124. }
  125. $this->m_oLeftExpr = $oLeftExpr;
  126. $this->m_oRightExpr = $oRightExpr;
  127. $this->m_sOperator = $sOperator;
  128. }
  129. public function IsTrue()
  130. {
  131. // return true if we are certain that it will be true
  132. if ($this->m_sOperator == 'AND')
  133. {
  134. if ($this->m_oLeftExpr->IsTrue() && $this->m_oLeftExpr->IsTrue()) return true;
  135. }
  136. return false;
  137. }
  138. public function GetLeftExpr()
  139. {
  140. return $this->m_oLeftExpr;
  141. }
  142. public function GetRightExpr()
  143. {
  144. return $this->m_oRightExpr;
  145. }
  146. public function GetOperator()
  147. {
  148. return $this->m_sOperator;
  149. }
  150. // recursive rendering
  151. public function Render(&$aArgs = null, $bRetrofitParams = false)
  152. {
  153. $sOperator = $this->GetOperator();
  154. $sLeft = $this->GetLeftExpr()->Render($aArgs, $bRetrofitParams);
  155. $sRight = $this->GetRightExpr()->Render($aArgs, $bRetrofitParams);
  156. return "($sLeft $sOperator $sRight)";
  157. }
  158. public function GetUnresolvedFields($sAlias, &$aUnresolved)
  159. {
  160. $this->GetLeftExpr()->GetUnresolvedFields($sAlias, $aUnresolved);
  161. $this->GetRightExpr()->GetUnresolvedFields($sAlias, $aUnresolved);
  162. }
  163. public function Translate($aTranslationData, $bMatchAll = true)
  164. {
  165. $oLeft = $this->GetLeftExpr()->Translate($aTranslationData, $bMatchAll);
  166. $oRight = $this->GetRightExpr()->Translate($aTranslationData, $bMatchAll);
  167. return new BinaryExpression($oLeft, $this->GetOperator(), $oRight);
  168. }
  169. public function ListRequiredFields()
  170. {
  171. $aLeft = $this->GetLeftExpr()->ListRequiredFields();
  172. $aRight = $this->GetRightExpr()->ListRequiredFields();
  173. return array_merge($aLeft, $aRight);
  174. }
  175. }
  176. class UnaryExpression extends Expression
  177. {
  178. protected $m_value;
  179. public function __construct($value)
  180. {
  181. $this->m_value = $value;
  182. }
  183. public function IsTrue()
  184. {
  185. // return true if we are certain that it will be true
  186. return ($this->m_value == 1);
  187. }
  188. public function GetValue()
  189. {
  190. return $this->m_value;
  191. }
  192. // recursive rendering
  193. public function Render(&$aArgs = null, $bRetrofitParams = false)
  194. {
  195. if ($bRetrofitParams)
  196. {
  197. $iParamIndex = count($aArgs) + 1; // 1-based indexation
  198. $aArgs['param'.$iParamIndex] = $this->m_value;
  199. return ':param'.$iParamIndex;
  200. }
  201. else
  202. {
  203. return CMDBSource::Quote($this->m_value);
  204. }
  205. }
  206. public function GetUnresolvedFields($sAlias, &$aUnresolved)
  207. {
  208. }
  209. public function Translate($aTranslationData, $bMatchAll = true)
  210. {
  211. return clone $this;
  212. }
  213. public function ListRequiredFields()
  214. {
  215. return array();
  216. }
  217. }
  218. class ScalarExpression extends UnaryExpression
  219. {
  220. public function __construct($value)
  221. {
  222. if (!is_scalar($value))
  223. {
  224. throw new CoreException('Attempt to create a scalar expression from a non scalar', array('var_type'=>gettype($value)));
  225. }
  226. parent::__construct($value);
  227. }
  228. }
  229. class TrueExpression extends ScalarExpression
  230. {
  231. public function __construct()
  232. {
  233. parent::__construct(1);
  234. }
  235. public function IsTrue()
  236. {
  237. return true;
  238. }
  239. }
  240. class FalseExpression extends ScalarExpression
  241. {
  242. public function __construct()
  243. {
  244. parent::__construct(0);
  245. }
  246. public function IsTrue()
  247. {
  248. return false;
  249. }
  250. }
  251. class FieldExpression extends UnaryExpression
  252. {
  253. protected $m_sParent;
  254. protected $m_sName;
  255. public function __construct($sName, $sParent = '')
  256. {
  257. parent::__construct("$sParent.$sName");
  258. $this->m_sParent = $sParent;
  259. $this->m_sName = $sName;
  260. }
  261. public function IsTrue()
  262. {
  263. // return true if we are certain that it will be true
  264. return false;
  265. }
  266. public function GetParent() {return $this->m_sParent;}
  267. public function GetName() {return $this->m_sName;}
  268. // recursive rendering
  269. public function Render(&$aArgs = null, $bRetrofitParams = false)
  270. {
  271. if (empty($this->m_sParent))
  272. {
  273. return "`{$this->m_sName}`";
  274. }
  275. return "`{$this->m_sParent}`.`{$this->m_sName}`";
  276. }
  277. public function ListRequiredFields()
  278. {
  279. return array($this->m_sParent.'.'.$this->m_sName);
  280. }
  281. public function GetUnresolvedFields($sAlias, &$aUnresolved)
  282. {
  283. if ($this->m_sParent == $sAlias)
  284. {
  285. // Add a reference to the field
  286. $aUnresolved[$this->m_sName] = $this;
  287. }
  288. }
  289. public function Translate($aTranslationData, $bMatchAll = true)
  290. {
  291. if (!array_key_exists($this->m_sParent, $aTranslationData))
  292. {
  293. if ($bMatchAll) throw new CoreException('Unknown parent id in translation table', array('parent_id' => $this->m_sParent, 'translation_table' => array_keys($aTranslationData)));
  294. return clone $this;
  295. }
  296. if (!array_key_exists($this->m_sName, $aTranslationData[$this->m_sParent]))
  297. {
  298. if (!array_key_exists('*', $aTranslationData[$this->m_sParent]))
  299. {
  300. // #@# debug - if ($bMatchAll) MyHelpers::var_dump_html($aTranslationData, true);
  301. if ($bMatchAll) throw new CoreException('Unknown name in translation table', array('name' => $this->m_sName, 'parent_id' => $this->m_sParent, 'translation_table' => array_keys($aTranslationData[$this->m_sParent])));
  302. return clone $this;
  303. }
  304. $sNewParent = $aTranslationData[$this->m_sParent]['*'];
  305. $sNewName = $this->m_sName;
  306. $oRet = new FieldExpressionResolved($sNewName, $sNewParent);
  307. }
  308. else
  309. {
  310. $oRet = $aTranslationData[$this->m_sParent][$this->m_sName];
  311. }
  312. return $oRet;
  313. }
  314. }
  315. // Has been resolved into an SQL expression
  316. class FieldExpressionResolved extends FieldExpression
  317. {
  318. public function GetUnresolvedFields($sAlias, &$aUnresolved)
  319. {
  320. }
  321. public function Translate($aTranslationData, $bMatchAll = true)
  322. {
  323. return clone $this;
  324. }
  325. }
  326. class VariableExpression extends UnaryExpression
  327. {
  328. protected $m_sName;
  329. public function __construct($sName)
  330. {
  331. parent::__construct($sName);
  332. $this->m_sName = $sName;
  333. }
  334. public function IsTrue()
  335. {
  336. // return true if we are certain that it will be true
  337. return false;
  338. }
  339. public function GetName() {return $this->m_sName;}
  340. // recursive rendering
  341. public function Render(&$aArgs = null, $bRetrofitParams = false)
  342. {
  343. if (is_null($aArgs))
  344. {
  345. return ':'.$this->m_sName;
  346. }
  347. elseif (array_key_exists($this->m_sName, $aArgs))
  348. {
  349. return CMDBSource::Quote($aArgs[$this->m_sName]);
  350. }
  351. elseif ($bRetrofitParams)
  352. {
  353. //$aArgs[$this->m_sName] = null;
  354. return ':'.$this->m_sName;
  355. }
  356. else
  357. {
  358. throw new MissingQueryArgument('Missing query argument', array('expecting'=>$this->m_sName, 'available'=>$aArgs));
  359. }
  360. }
  361. }
  362. // Temporary, until we implement functions and expression casting!
  363. // ... or until we implement a real full text search based in the MATCH() expression
  364. class ListExpression extends Expression
  365. {
  366. protected $m_aExpressions;
  367. public function __construct($aExpressions)
  368. {
  369. $this->m_aExpressions = $aExpressions;
  370. }
  371. public static function FromScalars($aScalars)
  372. {
  373. $aExpressions = array();
  374. foreach($aScalars as $value)
  375. {
  376. $aExpressions[] = new ScalarExpression($value);
  377. }
  378. return new ListExpression($aExpressions);
  379. }
  380. public function IsTrue()
  381. {
  382. // return true if we are certain that it will be true
  383. return false;
  384. }
  385. public function GetItems()
  386. {
  387. return $this->m_aExpressions;
  388. }
  389. // recursive rendering
  390. public function Render(&$aArgs = null, $bRetrofitParams = false)
  391. {
  392. $aRes = array();
  393. foreach ($this->m_aExpressions as $oExpr)
  394. {
  395. $aRes[] = $oExpr->Render($aArgs, $bRetrofitParams);
  396. }
  397. return '('.implode(', ', $aRes).')';
  398. }
  399. public function GetUnresolvedFields($sAlias, &$aUnresolved)
  400. {
  401. foreach ($this->m_aExpressions as $oExpr)
  402. {
  403. $oExpr->GetUnresolvedFields($sAlias, $aUnresolved);
  404. }
  405. }
  406. public function Translate($aTranslationData, $bMatchAll = true)
  407. {
  408. $aRes = array();
  409. foreach ($this->m_aExpressions as $oExpr)
  410. {
  411. $aRes[] = $oExpr->Translate($aTranslationData, $bMatchAll);
  412. }
  413. return new ListExpression($aRes);
  414. }
  415. public function ListRequiredFields()
  416. {
  417. $aRes = array();
  418. foreach ($this->m_aExpressions as $oExpr)
  419. {
  420. $aRes = array_merge($aRes, $oExpr->ListRequiredFields());
  421. }
  422. return $aRes;
  423. }
  424. }
  425. class FunctionExpression extends Expression
  426. {
  427. protected $m_sVerb;
  428. protected $m_aArgs; // array of expressions
  429. public function __construct($sVerb, $aArgExpressions)
  430. {
  431. $this->m_sVerb = $sVerb;
  432. $this->m_aArgs = $aArgExpressions;
  433. }
  434. public function IsTrue()
  435. {
  436. // return true if we are certain that it will be true
  437. return false;
  438. }
  439. public function GetVerb()
  440. {
  441. return $this->m_sVerb;
  442. }
  443. public function GetArgs()
  444. {
  445. return $this->m_aArgs;
  446. }
  447. // recursive rendering
  448. public function Render(&$aArgs = null, $bRetrofitParams = false)
  449. {
  450. $aRes = array();
  451. foreach ($this->m_aArgs as $oExpr)
  452. {
  453. $aRes[] = $oExpr->Render($aArgs, $bRetrofitParams);
  454. }
  455. return $this->m_sVerb.'('.implode(', ', $aRes).')';
  456. }
  457. public function GetUnresolvedFields($sAlias, &$aUnresolved)
  458. {
  459. foreach ($this->m_aArgs as $oExpr)
  460. {
  461. $oExpr->GetUnresolvedFields($sAlias, $aUnresolved);
  462. }
  463. }
  464. public function Translate($aTranslationData, $bMatchAll = true)
  465. {
  466. $aRes = array();
  467. foreach ($this->m_aArgs as $oExpr)
  468. {
  469. $aRes[] = $oExpr->Translate($aTranslationData, $bMatchAll);
  470. }
  471. return new FunctionExpression($this->m_sVerb, $aRes);
  472. }
  473. public function ListRequiredFields()
  474. {
  475. $aRes = array();
  476. foreach ($this->m_aArgs as $oExpr)
  477. {
  478. $aRes = array_merge($aRes, $oExpr->ListRequiredFields());
  479. }
  480. return $aRes;
  481. }
  482. }
  483. class IntervalExpression extends Expression
  484. {
  485. protected $m_oValue; // expression
  486. protected $m_sUnit;
  487. public function __construct($oValue, $sUnit)
  488. {
  489. $this->m_oValue = $oValue;
  490. $this->m_sUnit = $sUnit;
  491. }
  492. public function IsTrue()
  493. {
  494. // return true if we are certain that it will be true
  495. return false;
  496. }
  497. public function GetValue()
  498. {
  499. return $this->m_oValue;
  500. }
  501. public function GetUnit()
  502. {
  503. return $this->m_sUnit;
  504. }
  505. // recursive rendering
  506. public function Render(&$aArgs = null, $bRetrofitParams = false)
  507. {
  508. return 'INTERVAL '.$this->m_oValue->Render($aArgs, $bRetrofitParams).' '.$this->m_sUnit;
  509. }
  510. public function GetUnresolvedFields($sAlias, &$aUnresolved)
  511. {
  512. $this->m_oValue->GetUnresolvedFields($sAlias, $aUnresolved);
  513. }
  514. public function Translate($aTranslationData, $bMatchAll = true)
  515. {
  516. return new IntervalExpression($this->m_oValue->Translate($aTranslationData, $bMatchAll), $this->m_sUnit);
  517. }
  518. public function ListRequiredFields()
  519. {
  520. return array();
  521. }
  522. }
  523. class CharConcatExpression extends Expression
  524. {
  525. protected $m_aExpressions;
  526. public function __construct($aExpressions)
  527. {
  528. $this->m_aExpressions = $aExpressions;
  529. }
  530. public function IsTrue()
  531. {
  532. // return true if we are certain that it will be true
  533. return false;
  534. }
  535. public function GetItems()
  536. {
  537. return $this->m_aExpressions;
  538. }
  539. // recursive rendering
  540. public function Render(&$aArgs = null, $bRetrofitParams = false)
  541. {
  542. $aRes = array();
  543. foreach ($this->m_aExpressions as $oExpr)
  544. {
  545. $sCol = $oExpr->Render($aArgs, $bRetrofitParams);
  546. // Concat will be globally NULL if one single argument is null !
  547. $aRes[] = "COALESCE($sCol, '')";
  548. }
  549. return "CAST(CONCAT(".implode(', ', $aRes).") AS CHAR)";
  550. }
  551. public function GetUnresolvedFields($sAlias, &$aUnresolved)
  552. {
  553. foreach ($this->m_aExpressions as $oExpr)
  554. {
  555. $oExpr->GetUnresolvedFields($sAlias, $aUnresolved);
  556. }
  557. }
  558. public function Translate($aTranslationData, $bMatchAll = true)
  559. {
  560. $aRes = array();
  561. foreach ($this->m_aExpressions as $oExpr)
  562. {
  563. $aRes[] = $oExpr->Translate($aTranslationData, $bMatchAll);
  564. }
  565. return new CharConcatExpression($aRes);
  566. }
  567. public function ListRequiredFields()
  568. {
  569. $aRes = array();
  570. foreach ($this->m_aExpressions as $oExpr)
  571. {
  572. $aRes = array_merge($aRes, $oExpr->ListRequiredFields());
  573. }
  574. return $aRes;
  575. }
  576. }
  577. class QueryBuilderExpressions
  578. {
  579. protected $m_oConditionExpr;
  580. protected $m_aSelectExpr;
  581. protected $m_aJoinFields;
  582. public function __construct($aSelect, $oCondition)
  583. {
  584. $this->m_oConditionExpr = $oCondition;
  585. $this->m_aSelectExpr = $aSelect;
  586. $this->m_aJoinFields = array();
  587. }
  588. public function GetSelect()
  589. {
  590. return $this->m_aSelectExpr;
  591. }
  592. public function GetCondition()
  593. {
  594. return $this->m_oConditionExpr;
  595. }
  596. public function PopJoinField()
  597. {
  598. return array_pop($this->m_aJoinFields);
  599. }
  600. public function AddSelect($sAttAlias, $oExpression)
  601. {
  602. $this->m_aSelectExpr[$sAttAlias] = $oExpression;
  603. }
  604. //$oConditionTree = $oConditionTree->LogAnd($oFinalClassRestriction);
  605. public function AddCondition($oExpression)
  606. {
  607. $this->m_oConditionExpr = $this->m_oConditionExpr->LogAnd($oExpression);
  608. }
  609. public function PushJoinField($oExpression)
  610. {
  611. array_push($this->m_aJoinFields, $oExpression);
  612. }
  613. public function GetUnresolvedFields($sAlias, &$aUnresolved)
  614. {
  615. $this->m_oConditionExpr->GetUnresolvedFields($sAlias, $aUnresolved);
  616. foreach($this->m_aSelectExpr as $sColAlias => $oExpr)
  617. {
  618. $oExpr->GetUnresolvedFields($sAlias, $aUnresolved);
  619. }
  620. foreach($this->m_aJoinFields as $oExpression)
  621. {
  622. $oExpression->GetUnresolvedFields($sAlias, $aUnresolved);
  623. }
  624. }
  625. public function Translate($aTranslationData, $bMatchAll = true)
  626. {
  627. $this->m_oConditionExpr = $this->m_oConditionExpr->Translate($aTranslationData, $bMatchAll);
  628. foreach($this->m_aSelectExpr as $sColAlias => $oExpr)
  629. {
  630. $this->m_aSelectExpr[$sColAlias] = $oExpr->Translate($aTranslationData, $bMatchAll);
  631. }
  632. foreach($this->m_aJoinFields as $index => $oExpression)
  633. {
  634. $this->m_aJoinFields[$index] = $oExpression->Translate($aTranslationData, $bMatchAll);
  635. }
  636. }
  637. }
  638. ?>