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.  * @version  $Id: fsource_rdql__rdqlRdqlDbEngine.php.html,v 1.10 2006/06/26 12:34:13 tgauss Exp $
  17.  * @author   Radoslaw Oldakowski <radol@gmx.de>
  18.  *
  19.  * @package rdql
  20.  * @access public
  21.  */
  22.  
  23. Class 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.       return $this->toNodes($queryResult);
  90.    else
  91.       return $this->toString($queryResult);
  92.  }
  93.  
  94.  
  95.  /**
  96.  * Generate an SQL string to query the database for tuples matching all patterns
  97.  * of $parsedQuery.
  98.  *
  99.  * @param   integer $modelID 
  100.  * @return  string 
  101.  * @access    private
  102.  */
  103.  function generateSql($modelID) {
  104.  
  105.    $sql  = $this->generateSql_SelectClause();
  106.    $sql .= $this->generateSql_FromClause();
  107.    $sql .= $this->generateSql_WhereClause($modelID);
  108.    return $sql;
  109.  }
  110.  
  111.  
  112. /**
  113.  * Generate SQL SELECT clause.
  114.  *
  115.  * @return  string 
  116.  * @throws  PHPError
  117.  * @access    private
  118.  */
  119.  function generateSql_SelectClause() {
  120.  
  121.    $sql_select = 'SELECT';
  122.    $index = 0;
  123.    $this->rsIndexes = array();
  124.    
  125.    foreach ($this->parsedQuery['selectVars'] as $var)
  126.      $sql_select .= $this->_generateSql_SelectVar($var, $index);
  127.  
  128.    if (isset($this->parsedQuery['filters'])) {
  129.        foreach ($this->parsedQuery['filters'] as $n => $filter) {
  130.  
  131.          // variables from numeric expressions
  132.          foreach ($filter['numExprVars'] as $numVar)
  133.            $sql_select .= $this->_generateSql_SelectVar($numVar, $index);
  134.          
  135.          // variables from regex equality expressions
  136.          foreach ($filter['regexEqExprs'] as $regexEqExpr)
  137.            $sql_select .= $this->_generateSql_SelectVar($regexEqExpr['var'], $index);
  138.            
  139.          // variables from string equality expressions
  140.          foreach ($filter['strEqExprs'] as $strEqVar)
  141.              $sql_select .= $this->_generateSql_SelectVar($strEqVar['var'], $index);
  142.        }
  143.    }
  144.  
  145.    return rtrim($sql_select, " , ");
  146.  }
  147.  
  148.  
  149. /**
  150.  * Generate SQL FROM clause
  151.  *
  152.  * @return  string 
  153.  * @access    private
  154.  */
  155.  function generateSql_FromClause() {
  156.  
  157.    $sql_from = ' FROM';
  158.    foreach ($this->parsedQuery['patterns'] as $n => $v)
  159.      $sql_from .= ' statements s' .($n+1) .' , ';
  160.      
  161.    return rtrim($sql_from, ' , ');
  162.  }
  163.  
  164.  
  165. /**
  166.  * Generate an SQL WHERE clause
  167.  *
  168.  * @param   integer $modelID 
  169.  * @return  string 
  170.  * @access    private
  171.  */
  172.  function generateSql_WhereClause($modelID) {
  173.  
  174.    $sql_where = ' WHERE';
  175.    $count_patterns = count($this->parsedQuery['patterns']);
  176.    foreach ($this->parsedQuery['patterns'] as $n => $pattern) {
  177.      $sql_where .= ' s' .($n+1) .'.modelID=' .$modelID .' AND';
  178.      foreach ($pattern as $key => $val_1)
  179.        if ($val_1['value'] && $val_1['value']{0}=='?') {
  180.          $sql_tmp = ' s' .($n+1) .'.' .$key .'=';
  181.          // find internal bindings
  182.          switch ($key) {
  183.            case 'subject':
  184.                     if ($pattern['subject']['value'] == $pattern['predicate']['value'])
  185.                        $sql_where .= $sql_tmp .'s' .($n+1) .'.predicate AND';
  186.                     elseif ($pattern['subject']['value'] == $pattern['object']['value'])
  187.                        $sql_where .= $sql_tmp .'s' .($n+1) .'.object AND';
  188.                     break;
  189.            case 'predicate':
  190.                     if ($pattern['predicate']['value'] == $pattern['object']['value'])
  191.                        $sql_where .= $sql_tmp .'s' .($n+1) .'.object AND';
  192.          }
  193.          // find external bindings
  194.          for ($i=$n+1; $i<$count_patterns; $i++)
  195.              foreach ($this->parsedQuery['patterns'][$i] as $key2 => $val_2)
  196.                if ($val_1['value']==$val_2['value']) {
  197.                   $sql_where .= $sql_tmp .'s' .($i+1) .'.' .$key2 .' AND';
  198.                   break 2;
  199.                }
  200.        }else {
  201.           $sql_where .= ' s' .($n+1) .'.' .$key ."='" .$val_1['value'] ."' AND";
  202.           if ($key == 'object' && isset($val_1['is_literal'])) {
  203.               $sql_where .= ' s' .($n+1) .".object_is='l' AND";
  204.               $sql_where .= ' s' .($n+1) .".l_datatype='" .$val_1['l_dtype'] ."' AND";
  205.               $sql_where .= ' s' .($n+1) .".l_language='" .$val_1['l_lang'] ."' AND";     
  206.           }
  207.        }
  208.    }
  209.    return rtrim($sql_where, ' AND');
  210.  }
  211.  
  212.  
  213. /**
  214.  * Filter tuples containing variables matching all patterns from the WHERE clause
  215.  * of an RDQL query. As a result of a database query using ADOdb these tuples
  216.  * are returned as an ADORecordSet object, which is then passed to this function.
  217.  *
  218.  * @param   object ADORecordSet &$recordSet 
  219.  * @return  array  [][?VARNAME]['value']   = string
  220.  *                              ['nType']   = string
  221.  *                              ['l_lang']  = string
  222.  *                              ['l_dtype'] = string
  223.  * @access    private
  224.  */
  225.  function filterQueryResult(&$recordSet) {
  226.  
  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.  */
  423. function _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. ?>

Documentation generated on Mon, 26 Jun 2006 14:25:59 +0200 by phpDocumentor 1.3.0RC6