Source for file RdqlDbEngine.php

Documentation is available at RdqlDbEngine.php

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

Documentation generated on Fri, 17 Dec 2004 16:17:27 +0100 by phpDocumentor 1.3.0RC3