source: Dev/branches/rest-dojo-ui/Demo/rdfapi/rdql/RdqlMemEngine.php @ 312

Last change on this file since 312 was 312, checked in by jkraaijeveld, 13 years ago
File size: 18.6 KB
Line 
1<?php
2
3// ----------------------------------------------------------------------------------
4// Class: RdqlMemEngine
5// ----------------------------------------------------------------------------------
6
7/**
8 * This class performes as RDQL query on a MemModel.
9 *
10 * Provided an rdql query parsed into an array of php variables and constraints
11 * at first the engine searches for tuples matching all patterns from the WHERE clause
12 * of the given RDQL query. Then the query result set is filtered with evaluated
13 * boolean expressions from the AND clause of the given RDQL query.
14 *
15 * @version  $Id: RdqlMemEngine.php 268 2006-05-15 05:28:09Z tgauss $
16 * @author   Radoslaw Oldakowski <radol@gmx.de>
17 *
18 * @package rdql
19 * @access public
20 */
21
22Class RdqlMemEngine extends RdqlEngine {
23
24
25/**
26 * Parsed query variables and constraints.
27 *
28 * @var     array   ['selectVars'][] = ?VARNAME
29 *                  ['sources'][] = URI
30 *                  ['patterns'][]['subject']['value'] = VARorURI
31 *                                ['predicate']['value'] = VARorURI
32 *                                ['object']['value'] = VARorURIorLiterl
33 *                                          ['is_literal'] = boolean
34 *                                          ['l_lang'] = string
35 *                                          ['l_dtype'] = string
36 *                  ['filters'][]['string'] = string
37 *                               ['evalFilterStr'] = string
38 *                               ['reqexEqExprs'][]['var'] = ?VARNAME
39 *                                                 ['operator'] = (eq | ne)
40 *                                                 ['regex'] = string
41 *                               ['strEqExprs'][]['var'] = ?VARNAME
42 *                                               ['operator'] = (eq | ne)
43 *                                               ['value'] = string
44 *                                               ['value_type'] = ('variable' | 'URI' | 'Literal')
45 *                                               ['value_lang'] = string
46 *                                               ['value_dtype'] = string
47 *                               ['numExpr']['vars'][] = ?VARNAME
48 *                         ( [] stands for an integer index - 0..N )
49 * @access      private
50 */
51 var $parsedQuery;
52
53
54 /**
55 * Perform an RDQL Query on the given MemModel.
56 *
57 * @param   object  MemModel &$memModel
58 * @param   array   &$parsedQuery  (the same format as $this->parsedQuery)
59 * @param   boolean $returnNodes
60 * @return  array   [][?VARNAME] = object Node  (if $returnNodes = TRUE)
61 *      OR  array   [][?VARNAME] = string
62 *
63 * @access  public
64 */
65 function & queryModel(&$memModel, &$parsedQuery, $returnNodes = TRUE) {
66
67   $this->parsedQuery = $parsedQuery;
68
69   // find tuples matching all patterns
70   $res = $this->findTuplesMatchingAllPatterns($memModel);
71
72   // filter tuples
73   if (isset($parsedQuery['filters']))
74      $res = $this->filterTuples($res);
75
76   // select variables to be returned
77   $res = $this->selectVariables($res);
78
79   if(!$returnNodes)
80     return $this->toString($res);
81
82   return $res;
83 }
84 
85
86 /**
87 * Find triples matching all patterns of an RDQL query and return an array
88 * with variables from all patterns and their corresponding values.
89 * The variable values returned are instances of object Node.
90 *
91 * @param   object  MemModel  &$memModel
92 * @return  array   [][?VARNAME] = object Node
93 *
94 * @access  private
95 */
96 function findTuplesMatchingAllPatterns(&$memModel) {
97
98   $resultSet = $this->findTuplesMatchingOnePattern($memModel, $this->parsedQuery['patterns'][0]);
99   for ($i=1; $i<count($this->parsedQuery['patterns']); $i++) {
100       $rs = $this->findTuplesMatchingOnePattern($memModel, $this->parsedQuery['patterns'][$i]);
101       $resultSet = $this->joinTuples($resultSet, $rs);
102   }
103   return $resultSet;
104 }
105
106
107/**
108 * Find tuples matching one pattern and return an array with pattern
109 * variables and their corresponding values (instances of object Node).
110 *
111 * @param   object  MemModel &$memModel
112 * @param   array  &$pattern ['subject']['value'] = VARorURI
113 *                           ['predicate']['value'] = VARorURI
114 *                           ['object']['value'] = VARorURIorLiterl
115 *                                     ['is_literal'] = boolean
116 *                                     ['l_lang'] = string
117 *                                     ['l_dtype'] = string
118 * @return  array   [][?VARNAME] = object Node
119 *
120 * @access  private
121 */
122 function findTuplesMatchingOnePattern(&$memModel, &$pattern) {
123
124   $resultSet = array();
125   $i = 0;
126   // parameters to be passed to the method findTriplesMatchingPattern
127   foreach ($pattern as $key => $v) {     
128     if ($v['value'] && $v['value']{0} == '?') {
129        if ($key == 'object') {
130            $param['object']['is_a'] = 'ANY';
131            $param['object']['string'] = 'ANY';
132            $param['object']['lang'] = NULL;
133            $param['object']['dtype'] = NULL;
134        } else         
135                $param[$key] = 'ANY';
136        $var[$i]['key'] = $key;
137        $var[$i++]['val'] = $v['value'];
138     }else
139        if (isset($v['is_literal'])) {
140          $param[$key]['is_a'] = 'Literal';
141          $param[$key]['string'] = $v['value'];
142          $param[$key]['lang'] = $v['l_lang'];
143          $param[$key]['dtype'] = $v['l_dtype'];
144        }else{
145          if ($key == 'object') {
146             $param[$key]['is_a'] = 'Resource';
147             $param[$key]['string'] = $v['value'];
148             $param[$key]['lang'] = NULL;
149             $param[$key]['dtype'] = NULL;
150          }else
151            $param[$key] = $v['value'];
152        }
153   }
154   
155   // find pattern internal bindings e.g. (?x, ?z, ?x)
156   $intBindings = NULL;
157   for ($i=0; $i<count($var); $i++)
158       foreach($var as $n => $v)
159         if ($i != $n && $var[$i]['val'] == $v['val'])
160            $intBindings[] = $var[$i]['key'];
161
162   // find triples of the $memModel matching $pattern
163   $resModel = $this->findTriplesMatchingPattern($memModel, $param['subject'],
164                                                            $param['predicate'],
165                                                            $param['object']['is_a'],
166                                                            $param['object']['string'],
167                                                            $param['object']['lang'],
168                                                            $param['object']['dtype'],
169                                                 $intBindings);
170
171   // set values of the pattern variables to be returned
172   if ($pattern['subject']['value']{0} == '?') {
173      $n = 0;
174      foreach ($resModel->triples as $triple)
175        $resultSet[$n++][$pattern['subject']['value']] = $triple->subj;
176   }
177   if ($pattern['predicate']['value']{0} == '?') {
178      $n = 0;
179      foreach ($resModel->triples as $triple)
180        $resultSet[$n++][$pattern['predicate']['value']] = $triple->pred;
181   }
182   if ($pattern['object']['value'] && $pattern['object']['value']{0} == '?') {
183      $n = 0;
184      foreach ($resModel->triples as $triple)
185        $resultSet[$n++][$pattern['object']['value']] = $triple->obj;
186   }
187   return $resultSet;
188 }
189
190
191/**
192 * Search in $memModel for triples matching one pattern from the WHERE clause.
193 * 'ANY' input for $subjLabel..$objLabel, $obj_is will match anything.
194 * NULL input for $objDtype will only match obj->dtype = NULL
195 * NULL input for $objLanguage will match obj->lang = NULL or anything if a
196 * literal is datatyped (except for XMLLiterals and plain literals)
197 * This method also checks internal bindings if provided.
198 *
199 * @param   object MemModel $memModel
200 * @param   string $subjLabel
201 * @param   string $predLabel
202 * @param   string $objLabel
203 * @param   string $obj_is
204 * @param   string $objLanguage
205 * @param   string $objDtype
206 * @param   array  $intBindings [] = string
207 * @return  object MemModel
208 * @access      private
209 */
210 function findTriplesMatchingPattern(&$memModel, $subjLabel, $predLabel, $obj_is,
211                                      $objLabel, $objLang, $objDtype, &$intBindings) {
212
213   $res = new MemModel();
214
215   if($memModel->isEmpty())
216    return $res;
217
218   if ($subjLabel=='ANY')
219   {
220       $subj=NULL;
221   } else
222   {
223       $subj=new Resource($subjLabel);
224   };
225   if ($predLabel=='ANY')
226   {
227       $pred=NULL;
228   } else
229   {
230       $pred=new Resource($predLabel);
231   };
232           
233   if ($objLabel=='ANY')
234   {
235       $obj=NULL;
236   } else
237   {
238       if ($obj_is == 'Literal')
239       {
240           $obj=new Literal($objLabel);
241           $obj->setDatatype($objDtype);
242           $obj->setLanguage($objLang);
243       } else {
244           $obj=new Resource($objLabel);
245       }
246   };
247   
248   $res=$memModel->find($subj,$pred,$obj);
249
250     if ($intBindings)
251              foreach ($res->triples as $triple)
252        {
253            if (!$this->_checkIntBindings($triple, $intBindings))
254            {
255                  $res->remove($triple);
256            }
257        }
258
259  return $res;
260}
261
262 
263/**
264 * Perform an SQL-like inner join on two resultSets.
265 *
266 * @param   array   &$finalRes [][?VARNAME] = object Node
267 * @param   array   &$res      [][?VARNAME] = object Node
268 * @return  array              [][?VARNAME] = object Node
269 *
270 * @access  private
271 */
272 function joinTuples(&$finalRes, &$res) {
273
274   if (count($finalRes) == 0 || count($res) == 0)
275      return array();
276
277   // find joint variables and new variables to be added to $finalRes
278   $jointVars = array();
279   $newVars = array();
280   foreach ($res[0] as $varname => $node) {
281     if (array_key_exists($varname, $finalRes[0]))
282        $jointVars[] = $varname;
283     else
284        $newVars[] = $varname;
285   }
286
287   // eliminate rows of $finalRes in which the values of $jointVars do not have
288   // a corresponding row in $res.
289   foreach ($finalRes as $n => $fRes) {
290     foreach ($res as $i => $r) {
291       $ok = TRUE;
292       foreach ($jointVars as $j_varname)
293         if ($r[$j_varname] != $fRes[$j_varname]) {
294            $ok = FALSE;
295            break;
296         }
297       if ($ok)
298          break;
299     }
300     if (!$ok)
301        unset($finalRes[$n]);
302   }
303
304   // join $res and $finalRes
305   $joinedRes = array();
306   foreach ($res as $i => $r) {
307     foreach ($finalRes as $n => $fRes) {
308       $ok = TRUE;
309       foreach ($jointVars as $j_varname)
310         if ($r[$j_varname] != $fRes[$j_varname]) {
311            $ok = FALSE;
312            break;
313         }
314       if ($ok) {
315          $joinedRow = $finalRes[$n];
316          foreach($newVars as $n_varname)
317            $joinedRow[$n_varname] = $r[$n_varname];
318          $joinedRes[] = $joinedRow;
319       }
320     }
321   }
322   
323   return $joinedRes;
324 }
325 
326
327/**
328 * Filter the result-set of query variables by evaluating each filter from the
329 * AND clause of the RDQL query.
330 *
331 * @param   array  &$finalRes  [][?VARNAME] = object Node
332 * @return  array  [][?VARNAME] = object Node
333 * @access      private
334 */
335 function filterTuples(&$finalRes) {
336
337   foreach ($this->parsedQuery['filters'] as $filter) {
338
339      foreach ($finalRes as $n => $fRes) {
340        $evalFilterStr = $filter['evalFilterStr'];
341
342        // evaluate regex equality expressions of each filter
343        foreach ($filter['regexEqExprs'] as $i => $expr) {
344
345          preg_match($expr['regex'], $fRes[$expr['var']]->getLabel(), $match);
346          $op = substr($expr['operator'], 0,1);
347          if (($op != '!' && !isset($match[0])) || ($op == '!' && isset($match[0])))
348             $evalFilterStr = str_replace("##RegEx_$i##", 'FALSE', $evalFilterStr);
349          else
350             $evalFilterStr = str_replace("##RegEx_$i##", 'TRUE', $evalFilterStr);
351
352        }
353
354        // evaluate string equality expressions
355        foreach ($filter['strEqExprs'] as $i => $expr) {
356
357          $exprBoolVal = 'FALSE';
358         
359          switch ($expr['value_type']) {
360
361            case 'variable':
362                 if (($fRes[$expr['var']] == $fRes[$expr['value']] && $expr['operator'] == 'eq') ||
363                     ($fRes[$expr['var']] != $fRes[$expr['value']] && $expr['operator'] == 'ne'))
364                    $exprBoolVal = 'TRUE';
365                 break;
366                 
367            case 'URI':
368
369                 if (is_a($fRes[$expr['var']], 'Literal')) {
370                    if ($expr['operator'] == 'ne')
371                       $exprBoolVal = 'TRUE';
372                    break;
373                 }
374
375                 if (($fRes[$expr['var']]->getLabel() == $expr['value'] && $expr['operator'] == 'eq') ||
376                     ($fRes[$expr['var']]->getLabel() != $expr['value'] && $expr['operator'] == 'ne'))
377                    $exprBoolVal = 'TRUE';
378                 break;
379                 
380            case 'Literal':
381
382                 if (!is_a($fRes[$expr['var']], 'Literal')) {
383                    if ($expr['operator'] == 'ne')
384                       $exprBoolVal = 'TRUE';
385                    break;
386                 }
387
388                 $filterLiteral= new Literal($expr['value'],$expr['value_lang']);
389                 $filterLiteral->setDatatype($expr['value_dtype']);
390                 
391                $equal=$fRes[$expr['var']]->equals($filterLiteral);
392/*                 if ($fRes[$expr['var']]->getLabel() == $expr['value'] &&
393                     $fRes[$expr['var']]->getDatatype() == $expr['value_dtype']) {
394                     
395                    $equal = TRUE;
396                     
397                    // Lang tags only differentiate literals in rdf:XMLLiterals and plain literals.
398                    // Therefore if a literal is datatyped ignore the language tag.
399                    if ((($expr['value_dtype'] == NULL) ||
400                         ($expr['value_dtype'] == 'http://www.w3.org/1999/02/22-rdf-syntax-ns#XMLLiteral') ||
401                         ($expr['value_dtype'] == 'http://www.w3.org/2001/XMLSchema#string')) &&
402                        (($fRes[$expr['var']]->getLanguage() != $expr['value_lang'])))
403                       
404                        $equal = FALSE;
405                 }else
406                    $equal = FALSE;
407     */               
408                 if (($equal && $expr['operator'] == 'eq') ||
409                     (!$equal && $expr['operator'] == 'ne'))
410                    $exprBoolVal = 'TRUE';
411                 else
412                    $exprBoolVal = 'FALSE';
413
414          }
415          $evalFilterStr = str_replace("##strEqExpr_$i##", $exprBoolVal, $evalFilterStr);
416       }
417
418        // evaluate numerical expressions
419        foreach ($filter['numExprVars'] as $varName) {
420          $varValue = "'" .$fRes[$varName]->getLabel() ."'";
421          $evalFilterStr = str_replace($varName, $varValue, $evalFilterStr);
422        }
423
424        eval("\$filterBoolVal = $evalFilterStr; \$eval_filter_ok = TRUE;");
425        if (!isset($eval_filter_ok))
426           trigger_error(RDQL_AND_ERR ."'" .htmlspecialchars($filter['string']) ."'", E_USER_ERROR);
427
428        if (!$filterBoolVal)
429           unset($finalRes[$n]);
430      }
431   }
432
433   return $finalRes;
434 }
435
436
437/**
438 * Remove all conditional variables from the result-set and leave only variables
439 * specified in the SELECT clause of the RDQL query.
440 *
441 * @param   array  &$finalRes  [][?VARNAME] = object Node
442 * @return  array  [][?VARNAME] = object Node
443 * @access      private
444 */
445 function selectVariables(&$finalRes) {
446
447   // if nothing has been found return only one row of $finalRes
448   // with select variables having empty values
449   if (count($finalRes) == 0) {
450      foreach ($this->parsedQuery['selectVars'] as $selectVar)
451         $finalRes[0][$selectVar] = NULL;
452      return $finalRes;
453   }
454   
455   // return only selectVars in the same order as given in the RDQL query
456   // and reindex $finalRes.
457   $n = 0;
458   foreach($finalRes as $key => $val) {
459     foreach ($this->parsedQuery['selectVars'] as $selectVar)
460       $resultSet[$n][$selectVar] = $val[$selectVar];
461     unset($finalRes[$key]);
462     ++$n;
463   }
464
465   return $resultSet;
466 }
467
468
469/**
470 * Convert the variable values of $finalRes from objects to their string serialization.
471 *
472 * @param   array  &$finalRes  [][?VARNAME] = object Node
473 * @return  array  [][?VARNAME] = string
474 * @access      private
475 */
476 function toString(&$finalRes) {
477
478   foreach ($finalRes as $n => $tuple)
479     foreach ($tuple as $varname => $node) {
480       if (is_a($node, 'Resource'))
481          $res[$n][$varname] = '<' .$node->getLabel() .'>';
482       elseif (is_a($node, 'Literal')) {
483          $res[$n][$varname] = '"' .$node->getLabel() .'"';
484          if ($node->getLanguage())
485             $res[$n][$varname] .= ' (xml:lang="' .$node->getLanguage() .'")';
486          if ($node->getDatatype())
487             $res[$n][$varname] .= ' (rdf:datatype="' .$node->getDatatype() .'")';
488       }else
489          $res[$n][$varname] = $node;
490     }
491   return $res;
492 }
493
494
495/**
496 * Check if the given triple meets pattern internal bindings
497 * e.g. (?x, ?z, ?x) ==> statement subject must be identical with the statement object
498 *
499 * @param   object statement &$triple
500 * @param   array  &$intBindings [] = string
501 * @return  boolean
502 * @access      private
503 */
504 function _checkIntBindings (&$triple, &$intBindings) {
505
506   if (in_array('subject', $intBindings)) {
507      if (in_array('predicate', $intBindings))
508         if ($triple->subj != $triple->pred)
509            return FALSE;
510      if (in_array('object', $intBindings)) {
511         if (is_a($triple->obj, 'Literal'))
512            return FALSE;
513         elseif ($triple->subj != $triple->obj)
514            return FALSE;
515      }
516      return TRUE;
517   }
518   if (in_array('predicate', $intBindings)) {
519      if (is_a($triple->obj, 'Literal'))
520         return FALSE;
521      elseif ($triple->pred != $triple->obj)
522             return FALSE;
523      return TRUE;
524   }
525 }
526
527
528/**
529 * Check if the lang and dtype of the passed object Literal are equal $lang and $dtype
530 * !!! Language only differentiates literals in rdf:XMLLiterals and plain literals (xsd:string).
531 * !!! Therefore if a literal is datatyped ignore the language.
532 *
533 * @param  object  Literal $literal
534 * @param  string  $dtype1
535 * @param  string  $dtype2
536 * @return boolean
537 * @access private
538 */
539 function _equalsLangDtype ($literal, $lang, $dtype) {
540
541   if ($dtype == $literal->getDatatype()) {
542      if (($dtype == NULL ||
543           $dtype == 'http://www.w3.org/2001/XMLSchema#string' ||
544           $dtype == 'http://www.w3.org/1999/02/22-rdf-syntax-ns#XMLLiteral') &&
545          ($lang != $literal->getLanguage()))
546         return FALSE;
547      return TRUE;
548   }
549   return FALSE;
550 }
551 
552 
553} // end: Class RdqlMemEngine
554
555?>
Note: See TracBrowser for help on using the repository browser.