Source for file RdqlDbEngine.php

Documentation is available at RdqlDbEngine.php

  1. <?php
  2.  
  3. // ----------------------------------------------------------------------------------
  4. // Class: RdqlDbEngine
  5. // ----------------------------------------------------------------------------------
  6.  
  7. /**
  8. * This class performs as RDQL query on a DbModel.
  9. *
  10. * Provided an rdql query parsed into an array of php variables and constraints
  11. * at first the engine generates an sql statement and queries the database for
  12. * tuples matching all patterns from the WHERE clause of the given RDQL query.
  13. * Subsequently the result set is is filtered with evaluated boolean expressions
  14. * from the AND clause of the given RDQL query.
  15. *
  16. * <BR><BR>History:<UL>
  17. * <LI>09-27-2004 : Multiple queries bug fixed</LI>
  18. * <LI>05-12-2004 : Bug in the handling of empty Literals fixed.</LI>
  19. * <LI>08-29-2003 : Function filterTuples(): some bugs fixed:
  20. * - strEqExpr with NE operator
  21. * - regExExpr combined with other expr. and negation (!)
  22. * e.g. !(?x ~~ "/sth/" && ?x > 5)
  23. * <LI>07-27-2003 : First version of this class</LI>
  24. *
  25. * @version V0.9.3
  26. * @author Radoslaw Oldakowski <radol@gmx.de>
  27. *
  28. * @package rdql
  29. * @access public
  30. */
  31.  
  32. Class RdqlDbEngine extends RdqlEngine {
  33.  
  34.  
  35. /**
  36. * Parsed query variables and constraints.
  37. *
  38. * @var array [''selectVars''][] = ?VARNAME
  39. * [''sources''][] = URI
  40. * [''patterns''][][''subject''][''value''] = VARorURI
  41. * [''predicate''][''value''] = VARorURI
  42. * [''object''][''value''] = VARorURIorLiterl
  43. * [''is_literal''] = boolean
  44. * [''l_lang''] = string
  45. * [''l_dtype''] = string
  46. * [''filters''][][''string''] = string
  47. * [''evalFilterStr''] = string
  48. * [''reqexEqExprs''][][''var''] = ?VARNAME
  49. * [''operator''] = (eq | ne)
  50. * [''regex''] = string
  51. * [''strEqExprs''][][''var''] = ?VARNAME
  52. * [''operator''] = (eq | ne)
  53. * [''value''] = string
  54. * [''value_type''] = (''variable'' | ''URI'' | ''Literal'')
  55. * [''value_lang''] = string
  56. * [''value_dtype''] = string
  57. * [''numExpr''][''vars''][] = ?VARNAME
  58. * ( [] stands for an integer index - 0..N )
  59. * @access private
  60. */
  61. var $parsedQuery;
  62.  
  63.  
  64. /**
  65. * When an RDQL query is performed on a DbModel, in first step the engine searches
  66. * in database for triples matching the Rdql-WHERE clause. A recordSet is returned.
  67. * $rsIndexes maps select and filter variables to their corresponding indexes
  68. * in the returned recordSet.
  69. *
  70. * @var array [?VARNAME][''value''] = integer
  71. * [''nType''] = integer
  72. * [''l_lang''] = integer
  73. * [''l_dtype''] = integer
  74. * @access private
  75. */
  76. var $rsIndexes;
  77.  
  78.  
  79. /**
  80. * Perform an RDQL Query on the given DbModel.
  81. *
  82. * @param object DbModel $dbModel
  83. * @param array &$parsedQuery (the same format as $this->parsedQuery)
  84. * @param boolean $returnNodes
  85. * @return array [][?VARNAME] = object Node (if $returnNodes = TRUE)
  86. * OR array [][?VARNAME] = string
  87. * @access public
  88. */
  89. function & queryModel(&$dbModel, &$parsedQuery, $returnNodes = TRUE) {
  90.  
  91. $this->parsedQuery = &$parsedQuery;
  92.  
  93. $sql = $this->generateSql($dbModel->modelID);
  94. $recordSet =& $dbModel->dbConn->execute($sql);
  95. $queryResult = $this->filterQueryResult($recordSet);
  96.  
  97. if ($returnNodes)
  98. return $this->toNodes($queryResult);
  99. else
  100. return $this->toString($queryResult);
  101. }
  102.  
  103.  
  104. /**
  105. * Generate an SQL string to query the database for tuples matching all patterns
  106. * of $parsedQuery.
  107. *
  108. * @param integer $modelID
  109. * @return string
  110. * @access private
  111. */
  112. function generateSql($modelID) {
  113.  
  114. $sql = $this->generateSql_SelectClause();
  115. $sql .= $this->generateSql_FromClause();
  116. $sql .= $this->generateSql_WhereClause($modelID);
  117. return $sql;
  118. }
  119.  
  120.  
  121. /**
  122. * Generate SQL SELECT clause.
  123. *
  124. * @return string
  125. * @throws PHPError
  126. * @access private
  127. */
  128. function generateSql_SelectClause() {
  129.  
  130. $sql_select = ''SELECT'';
  131. $index = 0;
  132. $this->rsIndexes = array();
  133. foreach ($this->parsedQuery[''selectVars''] as $var)
  134. $sql_select .= $this->_generateSql_SelectVar($var, $index);
  135.  
  136. if (isset($this->parsedQuery[''filters''])) {
  137. foreach ($this->parsedQuery[''filters''] as $n => $filter) {
  138.  
  139. // variables from numeric expressions
  140. foreach ($filter[''numExprVars''] as $numVar)
  141. $sql_select .= $this->_generateSql_SelectVar($numVar, $index);
  142. // variables from regex equality expressions
  143. foreach ($filter[''regexEqExprs''] as $regexEqExpr)
  144. $sql_select .= $this->_generateSql_SelectVar($regexEqExpr[''var''], $index);
  145. // variables from string equality expressions
  146. foreach ($filter[''strEqExprs''] as $strEqVar)
  147. $sql_select .= $this->_generateSql_SelectVar($strEqVar[''var''], $index);
  148. }
  149. }
  150.  
  151. return rtrim($sql_select, " , ");
  152. }
  153.  
  154.  
  155. /**
  156. * Generate SQL FROM clause
  157. *
  158. * @return string
  159. * @access private
  160. */
  161. function generateSql_FromClause() {
  162.  
  163. $sql_from = '' FROM'';
  164. foreach ($this->parsedQuery[''patterns''] as $n => $v)
  165. $sql_from .= '' statements s'' .($n+1) .'' , '';
  166. return rtrim($sql_from, '' , '');
  167. }
  168.  
  169.  
  170. /**
  171. * Generate an SQL WHERE clause
  172. *
  173. * @param integer $modelID
  174. * @return string
  175. * @access private
  176. */
  177. function generateSql_WhereClause($modelID) {
  178.  
  179. $sql_where = '' WHERE'';
  180. $count_patterns = count($this->parsedQuery[''patterns'']);
  181. foreach ($this->parsedQuery[''patterns''] as $n => $pattern) {
  182. $sql_where .= '' s'' .($n+1) .''.modelID='' .$modelID .'' AND'';
  183. foreach ($pattern as $key => $val_1)
  184. if ($val_1[''value''] && $val_1[''value'']{0}==''?'') {
  185. $sql_tmp = '' s'' .($n+1) .''.'' .$key .''='';
  186. // find internal bindings
  187. switch ($key) {
  188. case ''subject'':
  189. if ($pattern[''subject''][''value''] == $pattern[''predicate''][''value''])
  190. $sql_where .= $sql_tmp .''s'' .($n+1) .''.predicate AND'';
  191. elseif ($pattern[''subject''][''value''] == $pattern[''object''][''value''])
  192. $sql_where .= $sql_tmp .''s'' .($n+1) .''.object AND'';
  193. break;
  194. case ''predicate'':
  195. if ($pattern[''predicate''][''value''] == $pattern[''object''][''value''])
  196. $sql_where .= $sql_tmp .''s'' .($n+1) .''.object AND'';
  197. }
  198. // find external bindings
  199. for ($i=$n+1; $i<$count_patterns; $i++)
  200. foreach ($this->parsedQuery[''patterns''][$i] as $key2 => $val_2)
  201. if ($val_1[''value'']==$val_2[''value'']) {
  202. $sql_where .= $sql_tmp .''s'' .($i+1) .''.'' .$key2 .'' AND'';
  203. break 2;
  204. }
  205. }else {
  206. $sql_where .= '' s'' .($n+1) .''.'' .$key ."=''" .$val_1[''value''] ."'' AND";
  207. if ($key == ''object'' && isset($val_1[''is_literal''])) {
  208. $sql_where .= '' s'' .($n+1) .".object_is=''l'' AND";
  209. $sql_where .= '' s'' .($n+1) .".l_datatype=''" .$val_1[''l_dtype''] ."'' AND";
  210. $sql_where .= '' s'' .($n+1) .".l_language=''" .$val_1[''l_lang''] ."'' AND";
  211. }
  212. }
  213. }
  214. return rtrim($sql_where, '' AND'');
  215. }
  216.  
  217.  
  218. /**
  219. * Filter tuples containing variables matching all patterns from the WHERE clause
  220. * of an RDQL query. As a result of a database query using ADOdb these tuples
  221. * are returned as an ADORecordSet object, which is then passed to this function.
  222. *
  223. * @param object ADORecordSet &$recordSet
  224. * @return array [][?VARNAME][''value''] = string
  225. * [''nType''] = string
  226. * [''l_lang''] = string
  227. * [''l_dtype''] = string
  228. * @access private
  229. */
  230. function filterQueryResult(&$recordSet) {
  231.  
  232. $queryResult=array();
  233.  
  234. if (isset($this->parsedQuery[''filters''])) {
  235.  
  236. while (!$recordSet->EOF) {
  237.  
  238. foreach ($this->parsedQuery[''filters''] as $filter) {
  239.  
  240. $evalFilterStr = $filter[''evalFilterStr''];
  241.  
  242. // evaluate regex equality expressions of each filter
  243. foreach ($filter[''regexEqExprs''] as $i => $expr) {
  244. preg_match($expr[''regex''], $recordSet->fields[$this->rsIndexes[$expr[''var'']][''value'']], $match);
  245. $op = substr($expr[''operator''], 0,1);
  246. if (($op != ''!'' && !isset($match[0])) || ($op == ''!'' && isset($match[0])))
  247. $evalFilterStr = str_replace("##RegEx_$i##", ''FALSE'', $evalFilterStr);
  248. else
  249. $evalFilterStr = str_replace("##RegEx_$i##", ''TRUE'', $evalFilterStr);
  250. }
  251.  
  252. // evaluate string equality expressions
  253. foreach ($filter[''strEqExprs''] as $i => $expr) {
  254.  
  255. $exprBoolVal = ''FALSE'';
  256.  
  257. switch ($expr[''value_type'']) {
  258.  
  259. case ''variable'':
  260. if (($recordSet->fields[$this->rsIndexes[$expr[''var'']][''value'']] ==
  261. $recordSet->fields[$this->rsIndexes[$expr[''value'']][''value'']] &&
  262. $expr[''operator''] == ''eq'') ||
  263. ($recordSet->fields[$this->rsIndexes[$expr[''var'']][''value'']] !=
  264. $recordSet->fields[$this->rsIndexes[$expr[''value'']][''value'']] &&
  265. $expr[''operator''] == ''ne''))
  266. $exprBoolVal = ''TRUE'';
  267. break;
  268.  
  269. case ''URI'':
  270.  
  271. if (isset($this->rsIndexes[$expr[''var'']][''nType'']) &&
  272. $recordSet->fields[$this->rsIndexes[$expr[''var'']][''nType'']] == ''l'') {
  273.  
  274. if ($expr[''operator''] == ''ne'')
  275. $exprBoolVal = ''TRUE'';
  276. break;
  277. }
  278.  
  279. if (($recordSet->fields[$this->rsIndexes[$expr[''var'']][''value'']] ==
  280. $expr[''value''] && $expr[''operator''] == ''eq'') ||
  281. ($recordSet->fields[$this->rsIndexes[$expr[''var'']][''value'']] !=
  282. $expr[''value''] && $expr[''operator''] == ''ne''))
  283. $exprBoolVal = ''TRUE'';
  284. break;
  285.  
  286. case ''Literal'':
  287.  
  288. if (!isset($this->rsIndexes[$expr[''var'']][''nType'']) ||
  289. $recordSet->fields[$this->rsIndexes[$expr[''var'']][''nType'']] != ''l'') {
  290.  
  291. if ($expr[''operator''] == ''ne'')
  292. $exprBoolVal = ''TRUE'';
  293. break;
  294. }
  295.  
  296. $filterLiteral= new Literal($expr[''value''],$expr[''value_lang'']);
  297. $filterLiteral->setDatatype($expr[''value_dtype'']);
  298. $resultLiteral=new Literal($recordSet->fields[$this->rsIndexes[$expr[''var'']][''value'']]);
  299. $resultLiteral->setDatatype($recordSet->fields[$this->rsIndexes[$expr[''var'']][''l_dtype'']]);
  300. $resultLiteral->setLanguage($recordSet->fields[$this->rsIndexes[$expr[''var'']][''l_lang'']]);
  301. $equal=$resultLiteral->equals($filterLiteral);
  302. if (($equal && $expr[''operator''] == ''eq'') ||
  303. (!$equal && $expr[''operator''] == ''ne''))
  304. $exprBoolVal = ''TRUE'';
  305. else
  306. $exprBoolVal = ''FALSE'';
  307. }
  308.  
  309. $evalFilterStr = str_replace("##strEqExpr_$i##", $exprBoolVal, $evalFilterStr);
  310. }
  311.  
  312. // evaluate numerical expressions
  313. foreach ($filter[''numExprVars''] as $varName) {
  314. $varValue = "''" .$recordSet->fields[$this->rsIndexes[$varName][''value'']] ."''";
  315. $evalFilterStr = str_replace($varName, $varValue, $evalFilterStr);
  316. }
  317.  
  318. eval("\$filterBoolVal = $evalFilterStr; \$eval_filter_ok = TRUE;");
  319. if (!isset($eval_filter_ok))
  320. trigger_error(RDQL_AND_ERR ."''" .htmlspecialchars($filter[''string'']) ."''", E_USER_ERROR);
  321.  
  322. if (!$filterBoolVal) {
  323. $recordSet->MoveNext();
  324. continue 2;
  325. }
  326.  
  327. }
  328. $queryResult[] = $this->_convertRsRowToQueryResultRow($recordSet->fields);
  329. $recordSet->MoveNext();
  330. }
  331. }else
  332. while (!$recordSet->EOF) {
  333. $queryResult[] = $this->_convertRsRowToQueryResultRow($recordSet->fields);
  334. $recordSet->MoveNext();
  335. }
  336. return $queryResult;
  337. }
  338.  
  339.  
  340. /**
  341. * Serialize variable values of $queryResult to string.
  342. *
  343. * @param array &$queryResult [][?VARNAME][''value''] = string
  344. * [''nType''] = string
  345. * [''l_lang''] = string
  346. * [''l_dtype''] = string
  347. * @return array [][?VARNAME] = string
  348. * @access private
  349. */
  350. function toString(&$queryResult) {
  351.  
  352. // if a result set is empty return only variable sames
  353. if (count($queryResult) == 0) {
  354. foreach ($this->parsedQuery[''selectVars''] as $selectVar)
  355. $res[0][$selectVar] = NULL;
  356. return $res;
  357. }
  358.  
  359. $res = array();
  360. foreach ($queryResult as $n => $var)
  361. foreach ($var as $varname => $varProperties)
  362. if ($varProperties[''nType''] == ''r'' || $varProperties[''nType''] == ''b'')
  363. $res[$n][$varname] = ''<'' .$varProperties[''value''] .''>'';
  364. else {
  365. $res[$n][$varname] = ''"'' .$varProperties[''value''] .''"'';
  366. if ($varProperties[''l_lang''] != NULL)
  367. $res[$n][$varname] .= '' (xml:lang="'' .$varProperties[''l_lang''] .''")'';
  368. if ($varProperties[''l_dtype''] != NULL)
  369. $res[$n][$varname] .= '' (rdf:datatype="'' .$varProperties[''l_dtype''] .''")'';
  370. }
  371. return $res;
  372. }
  373.  
  374.  
  375. /**
  376. * Convert variable values of $queryResult to objects (Node).
  377. *
  378. * @param array &$queryResult [][?VARNAME][''value''] = string
  379. * [''nType''] = string
  380. * [''l_lang''] = string
  381. * [''l_dtype''] = string
  382. * @return array [][?VARNAME] = object Node
  383. * @access private
  384. */
  385. function toNodes(&$queryResult) {
  386.  
  387. // if a result set is empty return only variable sames
  388. if (count($queryResult) == 0) {
  389. foreach ($this->parsedQuery[''selectVars''] as $selectVar)
  390. $res[0][$selectVar] = NULL;
  391. return $res;
  392. }
  393.  
  394. $res = array();
  395. foreach ($queryResult as $n => $var)
  396. foreach ($var as $varname => $varProperties)
  397. if ($varProperties[''nType''] == ''r'')
  398. $res[$n][$varname] = new Resource($varProperties[''value'']);
  399. elseif ($varProperties[''nType''] == ''b'')
  400. $res[$n][$varname] = new BlankNode($varProperties[''value'']);
  401. else {
  402. $res[$n][$varname] = new Literal($varProperties[''value''], $varProperties[''l_lang'']);
  403. if ($varProperties[''l_dtype''] != NULL)
  404. $res[$n][$varname]->setDataType($varProperties[''l_dtype'']);
  405. }
  406. return $res;
  407. }
  408.  
  409.  
  410. /**
  411. * Generate a piece of an sql select statement for a variable.
  412. * Look first if the given variable is defined as a pattern object.
  413. * (So you can select the node type, literal lang and dtype)
  414. * If not found - look for subjects and select node label and type.
  415. * If there is no result either go to predicates.
  416. * Predicates are always resources therefore select only the node label.
  417. *
  418. * @param string $varName
  419. * @return string
  420. * @access private
  421. */
  422. function _generateSql_SelectVar ($varName, &$index) {
  423.  
  424. $sql_select = '''';
  425.  
  426. if (array_key_exists($varName, $this->rsIndexes))
  427. return NULL;
  428.  
  429. foreach ($this->parsedQuery[''patterns''] as $n => $pattern)
  430. if ($varName == $pattern[''object''][''value'']) {
  431.  
  432. // select the object label
  433. $sql_select .= " s" .++$n .".object as _" .ltrim($varName, "?") ." , ";
  434. $this->rsIndexes[$varName][''value''] = $index++;
  435. // select the node type
  436. $sql_select .= " s" .$n .".object_is , ";
  437. $this->rsIndexes[$varName][''nType''] = $index++;
  438. // select the object language
  439. $sql_select .= " s" .$n .".l_language , ";
  440. $this->rsIndexes[$varName][''l_lang''] = $index++;
  441. // select the object dtype
  442. $sql_select .= " s" .$n .".l_datatype , ";
  443. $this->rsIndexes[$varName][''l_dtype''] = $index++;
  444.  
  445. return $sql_select;
  446. }
  447.  
  448. foreach ($this->parsedQuery[''patterns''] as $n => $pattern)
  449. if ($varName == $pattern[''subject''][''value'']) {
  450.  
  451. // select the object label
  452. $sql_select .= " s" .++$n .".subject as _" .ltrim($varName, "?") ." , ";
  453. $this->rsIndexes[$varName][''value''] = $index++;
  454. // select the node type
  455. $sql_select .= " s" .$n .".subject_is , ";
  456. $this->rsIndexes[$varName][''nType''] = $index++;
  457.  
  458. return $sql_select;
  459. }
  460.  
  461. foreach ($this->parsedQuery[''patterns''] as $n => $pattern)
  462. if ($varName == $pattern[''predicate''][''value'']) {
  463.  
  464. // select the object label
  465. $sql_select .= " s" .++$n .".predicate as _" .ltrim($varName, "?") ." , ";
  466. $this->rsIndexes[$varName][''value''] = $index++;
  467.  
  468. return $sql_select;
  469. }
  470. }
  471.  
  472. /**
  473. * Converts a single row of ADORecordSet->fields array to the format of
  474. * $queryResult array using pointers to indexes ($this->rsIndexes) in RecordSet->fields.
  475. *
  476. * @param array &$record [] = string
  477. * @return array [?VARNAME][''value''] = string
  478. * [''nType''] = string
  479. * [''l_lang''] = string
  480. * [''l_dtype''] = string
  481. * @access private
  482. */
  483. function _convertRsRowToQueryResultRow(&$record) {
  484.  
  485. // return only select variables (without conditional variables from the AND clause)
  486. foreach ($this->parsedQuery[''selectVars''] as $selectVar) {
  487. $resultRow[$selectVar][''value''] = $record[$this->rsIndexes[$selectVar][''value'']];
  488. if (isset($this->rsIndexes[$selectVar][''nType'']))
  489. $resultRow[$selectVar][''nType''] = $record[$this->rsIndexes[$selectVar][''nType'']];
  490. // is a predicate then
  491. else
  492. $resultRow[$selectVar][''nType''] = ''r'';
  493. if ($resultRow[$selectVar][''nType''] == ''l'') {
  494. $resultRow[$selectVar][''l_lang''] = $record[$this->rsIndexes[$selectVar][''l_lang'']];
  495. $resultRow[$selectVar][''l_dtype''] = $record[$this->rsIndexes[$selectVar][''l_dtype'']];
  496. }
  497. }
  498. return $resultRow;
  499. }
  500. } // end: Class RdqlDbEngine
  501.  
  502. ?>

Documentation generated on Fri, 13 Jan 2006 07:49:12 +0100 by phpDocumentor 1.3.0RC4