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

Last change on this file since 312 was 312, checked in by jkraaijeveld, 13 years ago
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.