source: Dev/branches/rest-dojo-ui/server/rdfapi/rdql/RdqlDbEngine.php @ 274

Last change on this file since 274 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

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